From 618ffc79b71588d2f8a2369f47ca673d9477bef0 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 16 Oct 2023 12:22:50 +0200 Subject: [PATCH 01/66] fix some small bug in the Backend --- .gitignore | 5 +++ CHANGELOG.rst | 7 ++++ lightsim2grid/lightSimBackend.py | 7 +++- lightsim2grid/tests/test_basic_backend_api.py | 37 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 lightsim2grid/tests/test_basic_backend_api.py diff --git a/.gitignore b/.gitignore index fac4b540..671f272f 100644 --- a/.gitignore +++ b/.gitignore @@ -269,3 +269,8 @@ test_issue_58.py test_issue_58_old.py lightsim2grid/gridmodel/pypowsybl.ipynb lightsim2grid/tests/test.mat +output_test.txt +req_test.txt +test.txt +test_output.txt +test_pypower_fdpf.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 147c4ab6..dfd2a15e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,13 @@ Change Log - maybe have a look at suitesparse "sliplu" tools ? - easier building (get rid of the "make" part) +[0.7.6] 2023-xx-yy +-------------------- +- [FIXED] now voltage is properly set to 0. when shunts are disconnected +- [FIXED] now voltage is properly set to 0. when storage units are disconnected +- [FIXED] a bug where non connected grid were not spotted in DC +- [IMPROVED] now making the new grid2op `create_test_suite` + [0.7.5] 2023-10-05 -------------------- - [FIXED] a bug in DC powerflow when asking for computation time: it was not reset to 0. when diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 7a0ce861..92e1893b 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -814,6 +814,8 @@ def runpf(self, is_dc=False): 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.storage_v[self.storage_v == -1.] = 0. # voltage is 0. for disconnected elements in grid2op + self.next_prod_p[:] = self.prod_p if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.): @@ -825,7 +827,7 @@ def runpf(self, is_dc=False): disco = (~np.isfinite(self.prod_v)) | (self.prod_v <= 0.) gen_disco = np.where(disco)[0] self._timer_postproc += time.perf_counter() - beg_postroc - raise DivergingPowerFlow(f"At least one generator is disconnected (check loads {gen_disco})") + raise DivergingPowerFlow(f"At least one generator is disconnected (check gen {gen_disco})") # TODO storage case of divergence ! if self.shunts_data_available: @@ -833,6 +835,8 @@ def runpf(self, is_dc=False): self._fill_theta() + if (self.line_or_theta >= 1e6).any() or (self.line_ex_theta >= 1e6).any(): + raise DivergingPowerFlow(f"Some theta are above 1e6 which should not be happening !") res = True self._grid.unset_topo_changed() self._timer_postproc += time.perf_counter() - beg_postroc @@ -1039,6 +1043,7 @@ def _set_shunt_info(self): res_bus[shunt_bus >= self.__nb_bus_before] = 2 # except the one that are connected to bus 2 res_bus[shunt_bus == -1] = -1 # or the one that are disconnected self.sh_bus[:] = res_bus + self.sh_v[self.sh_v == -1.] = 0. # in grid2op disco element have voltage of 0. and -1. def _disconnect_line(self, id_): self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1 diff --git a/lightsim2grid/tests/test_basic_backend_api.py b/lightsim2grid/tests/test_basic_backend_api.py new file mode 100644 index 00000000..318edc00 --- /dev/null +++ b/lightsim2grid/tests/test_basic_backend_api.py @@ -0,0 +1,37 @@ +# Copyright (c) 2020, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform. + +import unittest + +try: + from grid2op._create_test_suite import create_test_suite + CAN_PERFORM_THESE = True +except ImportError as exc_: + CAN_PERFORM_THESE = False + +from lightsim2grid import LightSimBackend + +if CAN_PERFORM_THESE: + def this_make_backend(self, detailed_infos_for_cascading_failures=False): + return LightSimBackend( + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures + ) + add_name_cls = "test_LightSimBackend" + + res = create_test_suite(make_backend_fun=this_make_backend, + add_name_cls=add_name_cls, + add_to_module=__name__, + extended_test=False, # for now keep `extended_test=False` until all problems are solved + ) +else: + import warnings + warnings.warn("Have you installed grid2op in dev / editable mode ? We cannot make the `create_test_suite` :-(") + +# and run it with `python -m unittest gridcal_backend_tests.py` +if __name__ == "__main__": + unittest.main() From 48405b5a1976966d4eb59e632c4820a08a2e396f Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 16 Oct 2023 17:05:44 +0200 Subject: [PATCH 02/66] first test to init a grid based on pypowsybl directly --- lightsim2grid/lightSimBackend.py | 319 +++++++++++------- lightsim2grid/tests/case_14_iidm/grid.xiidm | 147 ++++++++ lightsim2grid/tests/test_backend_pypowsybl.py | 64 ++++ lightsim2grid/tests/test_issue_66.py | 72 ++++ src/GridModel.h | 3 +- src/main.cpp | 1 + 6 files changed, 486 insertions(+), 120 deletions(-) create mode 100644 lightsim2grid/tests/case_14_iidm/grid.xiidm create mode 100644 lightsim2grid/tests/test_backend_pypowsybl.py create mode 100644 lightsim2grid/tests/test_issue_66.py diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 92e1893b..780f3e0f 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -21,6 +21,11 @@ except ImportError as exc_: from grid2op.Action._BackendAction import _BackendAction +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal + from lightsim2grid.gridmodel import init from lightsim2grid.solver import SolverType @@ -39,6 +44,8 @@ def __init__(self, turned_off_pv : bool=True, # are gen turned off (or with p=0) contributing to voltage or not dist_slack_non_renew: bool=False, # distribute the slack on non renewable turned on (and with P>0) generators use_static_gen: bool=False, # add the static generators as generator gri2dop side + loader_method: Literal["pandapower", "pypowsybl"] = "pandapower", + loader_kwargs : Optional[dict] = None, ): try: # for grid2Op >= 1.7.1 @@ -50,7 +57,9 @@ def __init__(self, tol=tol, turned_off_pv=turned_off_pv, dist_slack_non_renew=dist_slack_non_renew, - use_static_gen=use_static_gen) + use_static_gen=use_static_gen, + loader_method=loader_method, + loader_kwargs=loader_kwargs) except TypeError as exc_: warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, " "you cannot set max_iter, tol nor solver_type arguments.") @@ -64,7 +73,9 @@ def __init__(self, if not self.__has_storage: 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._loader_method = loader_method + self._loader_kwargs = loader_kwargs + self.nb_bus_total = None self.initdc = True # does not really hurt computation time self.__nb_powerline = None @@ -174,20 +185,20 @@ def __init__(self, self._use_static_gen = use_static_gen # TODO implement it # storage data for this object (otherwise it's in the class) - self.n_storage = None - self.storage_to_subid = None - self.storage_pu_to_kv = None - self.name_storage = None - self.storage_to_sub_pos = None - self.storage_type = None - self.storage_Emin = None - self.storage_Emax = None - self.storage_max_p_prod = None - self.storage_max_p_absorb = None - self.storage_marginal_cost = None - self.storage_loss = None - self.storage_discharging_efficiency = None - self.storage_charging_efficiency = None + # self.n_storage = None + # self.storage_to_subid = None + # self.storage_pu_to_kv = None + # self.name_storage = None + # self.storage_to_sub_pos = None + # self.storage_type = None + # self.storage_Emin = None + # self.storage_Emax = None + # self.storage_max_p_prod = None + # self.storage_max_p_absorb = None + # self.storage_marginal_cost = None + # self.storage_loss = None + # self.storage_discharging_efficiency = None + # self.storage_charging_efficiency = None def turnedoff_no_pv(self): self._turned_off_pv = False @@ -386,14 +397,92 @@ def _assign_right_solver(self): self._grid.change_solver(SolverType.KLU) else: self._grid.change_solver(SolverType.SparseLU) + + def load_grid(self, path=None, filename=None): + if self._loader_method == "pandapower": + self._load_grid_pandapower(path, filename) + elif self._loader_method == "pypowsybl": + self._load_grid_pypowsybl(path, filename) + else: + raise BackendError(f"Impossible to initialize the backend with '{self._loader_method}'") + + def _load_grid_pypowsybl(self, path=None, filename=None): + from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow + import pypowsybl.network as pypow_net + loader_kwargs = {} + if self._loader_kwargs is not None: + loader_kwargs = self._loader_kwargs + + full_path = self.make_complete_path(path, filename) + grid_tmp = pypow_net.load(full_path) + self._grid = init_pypow(grid_tmp, gen_slack_id=None) # TODO gen_slack_id ! + self._aux_setup_right_after_grid_init() - 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) + # mandatory for the backend + self.n_line = len(self._grid.get_lines()) + len(self._grid.get_trafos()) + self.n_gen = len(self._grid.get_generators()) + self.n_load = len(self._grid.get_loads()) + self.n_storage = len(self._grid.get_storages()) + self.n_shunt = len(self._grid.get_shunts()) + + df = grid_tmp.get_substations() + from_sub = True + + if "use_buses_for_sub" in loader_kwargs and loader_kwargs["use_buses_for_sub"]: + df = grid_tmp.get_buses() + from_sub = False + self.n_sub = df.shape[0] + self.name_sub = ["sub_{}".format(i) for i, _ in df.iterrows()] + if not from_sub: + self.load_to_subid = np.array([el.bus_id for el in self._grid.get_loads()], dtype=dt_int) + self.gen_to_subid = np.array([el.bus_id for el in self._grid.get_generators()], dtype=dt_int) + self.line_or_to_subid = np.array([el.bus_or_id for el in self._grid.get_lines()] + + [el.bus_hv_id for el in self._grid.get_trafos()], + dtype=dt_int) + self.line_ex_to_subid = np.array([el.bus_ex_id for el in self._grid.get_lines()] + + [el.bus_lv_id for el in self._grid.get_trafos()], + dtype=dt_int) + self.storage_to_subid = np.array([el.bus_id for el in self._grid.get_storages()], dtype=dt_int) + self.shunt_to_subid = np.array([el.bus_id for el in self._grid.get_shunts()], dtype=dt_int) + else: + # TODO get back the sub id from the grid_tmp.get_substations() + raise NotImplementedError("TODO") + + # the names + self.name_load = np.array([f"load_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_loads())]) + self.name_gen = np.array([f"gen_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_generators())]) + self.name_line = np.array([f"{el.bus_or_id}_{el.bus_ex_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_lines())] + + [f"{el.bus_hv_id}_{el.bus_lv_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_trafos())]) + self.name_storage = np.array([f"storage_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_storages())]) + self.name_shunt = np.array([f"shunt_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_shunts())]) + + # complete the other vectors + self._compute_pos_big_topo() + + self.__nb_powerline = len(self._grid.get_lines()) + self.__nb_bus_before = len(self._grid.get_buses()) + self.shunts_data_available = True + + # init this + self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) + self.next_prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) + + # now handle the legacy "make as if there are 2 busbars per substation" + # as it is done with grid2Op simulated environment + if "double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]: + bus_init = self._grid.get_buses() + bus_doubled = np.concatenate((bus_init, bus_init)) + self._grid.init_bus(bus_doubled, 0, 0) + for i in range(self.__nb_bus_before): + self._grid.deactivate_bus(i + self.__nb_bus_before) + self.nb_bus_total = len(self._grid.get_buses()) + + # and now things needed by the backend (legacy) + self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)] + self._aux_finish_setup_after_reading() + + def _aux_setup_right_after_grid_init(self): self._handle_turnedoff_pv() self.available_solvers = self._grid.available_solvers() @@ -413,6 +502,13 @@ def load_grid(self, path=None, filename=None): self._check_suitable_solver_type(self.__current_solver_type) self._grid.change_solver(self.__current_solver_type) + def _load_grid_pandapower(self, path=None, filename=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._aux_setup_right_after_grid_init() + 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 @@ -494,106 +590,107 @@ def load_grid(self, path=None, filename=None): self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)] self._compute_pos_big_topo() + + 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 + 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 + + self._aux_finish_setup_after_reading() + def _aux_finish_setup_after_reading(self): # set up the "lightsim grid" accordingly + cls = type(self) + 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:]) + self._grid.set_load_pos_topo_vect(cls.load_pos_topo_vect) + self._grid.set_gen_pos_topo_vect(cls.gen_pos_topo_vect) + self._grid.set_line_or_pos_topo_vect(cls.line_or_pos_topo_vect[:self.__nb_powerline]) + self._grid.set_line_ex_pos_topo_vect(cls.line_ex_pos_topo_vect[:self.__nb_powerline]) + self._grid.set_trafo_hv_pos_topo_vect(cls.line_or_pos_topo_vect[self.__nb_powerline:]) + self._grid.set_trafo_lv_pos_topo_vect(cls.line_ex_pos_topo_vect[self.__nb_powerline:]) + + self._grid.set_load_to_subid(cls.load_to_subid) + self._grid.set_gen_to_subid(cls.gen_to_subid) + self._grid.set_line_or_to_subid(cls.line_or_to_subid[:self.__nb_powerline]) + self._grid.set_line_ex_to_subid(cls.line_ex_to_subid[:self.__nb_powerline]) + self._grid.set_trafo_hv_to_subid(cls.line_or_to_subid[self.__nb_powerline:]) + self._grid.set_trafo_lv_to_subid(cls.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) + self._grid.set_storage_to_subid(cls.storage_to_subid) + self._grid.set_storage_pos_topo_vect(cls.storage_pos_topo_vect) nm_ = "load" - for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect): + for load_id, pos_big_topo in enumerate(cls.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): + for gen_id, pos_big_topo in enumerate(cls.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): + for l_id, pos_big_topo in enumerate(cls.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): + for l_id, pos_big_topo in enumerate(cls.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): + for l_id, pos_big_topo in enumerate(cls.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) + self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int) if self.shunts_data_available: - self.shunt_topo_vect = np.ones(self.n_shunt, dtype=dt_int) + self.shunt_topo_vect = np.ones(cls.n_shunt, dtype=dt_int) # shunts - self.sh_p = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_q = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_v = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_bus = np.full(self.n_shunt, dtype=dt_int, fill_value=-1) - - 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.sh_p = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) + self.sh_q = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) + self.sh_v = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) + self.sh_bus = np.full(cls.n_shunt, dtype=dt_int, fill_value=-1) + + self.p_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.q_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.v_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.a_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.p_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.q_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.v_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.a_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + + self.load_p = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) + self.load_q = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) + self.load_v = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) + + self.prod_p = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) + self.prod_q = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) + self.prod_v = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) - self.line_or_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN) - self.line_ex_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN) - self.load_theta = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN) - self.gen_theta = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN) + self.line_or_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.line_ex_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) + self.load_theta = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) + self.gen_theta = np.full(cls.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.storage_theta = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_p = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_theta = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) self._count_object_per_bus() self._grid.tell_topo_changed() self.__me_at_init = self._grid.copy() - self.__init_topo_vect = np.ones(self.dim_topo, dtype=dt_int) + self.__init_topo_vect = np.ones(cls.dim_topo, dtype=dt_int) self.__init_topo_vect[:] = self.topo_vect def assert_grid_correct_after_powerflow(self): @@ -680,19 +777,20 @@ def apply_action(self, backendAction): self.topo_vect[chgt] = backendAction.current_topo.values[chgt] # 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) + try: + 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) + except RuntimeError as exc_: + # see https://github.com/BDonnot/lightsim2grid/issues/66 (even though it's not a "bug" and has not been replicated) + raise BackendError(f"{exc_}") + 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) @@ -714,24 +812,6 @@ def apply_action(self, backendAction): 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) - - # 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) - # 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 self._handle_dist_slack() @@ -927,7 +1007,8 @@ def copy(self): "_idx_hack_storage", "_timer_preproc", "_timer_postproc", "_timer_solver", "_my_kwargs", - "_turned_off_pv", "_dist_slack_non_renew" + "_turned_off_pv", "_dist_slack_non_renew", + "_loader_method", "_loader_kwargs" ] for attr_nm in li_regular_attr: if hasattr(self, attr_nm): diff --git a/lightsim2grid/tests/case_14_iidm/grid.xiidm b/lightsim2grid/tests/case_14_iidm/grid.xiidm new file mode 100644 index 00000000..3b0bb3dc --- /dev/null +++ b/lightsim2grid/tests/case_14_iidm/grid.xiidm @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py new file mode 100644 index 00000000..9ee745df --- /dev/null +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -0,0 +1,64 @@ +# Copyright (c) 2023, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +import unittest +import warnings +import os +from lightsim2grid import LightSimBackend +import grid2op + + +class BackendTester(unittest.TestCase): + """issue is still not replicated and these tests pass""" + def setUp(self) -> None: + dir_path = os.path.dirname(os.path.realpath(__file__)) + self.path = os.path.join(dir_path, "case_14_iidm") + self.file_name = "grid.xiidm" + + def _aux_prep_backend(self, backend): + backend.set_env_name("case_14_iidm") + backend.load_grid(self.path, self.file_name) + backend.load_storage_data(self.path) + backend.load_redispacthing_data(self.path) + backend.assert_grid_correct() + + def _aux_get_loader_kwargs(self): + return {"use_buses_for_sub": True, "double_bus_per_sub": True} + + def test_load_grid(self): + backend = LightSimBackend(loader_method="pypowsybl", + loader_kwargs=self._aux_get_loader_kwargs()) + self._aux_prep_backend(backend) + cls = type(backend) + assert cls.n_line == 20, f"wrong number of line {cls.n_line} vs 20" + assert cls.n_load == 11, f"wrong number of load {cls.n_load} vs 11" + assert cls.n_gen == 5, f"wrong number of gen {cls.n_gen} vs 5" + assert cls.n_sub == 14, f"wrong number of gen {cls.n_sub} vs 14" + assert cls.n_storage == 0, f"wrong number of storage { cls.n_storage} vs 0" + assert cls.n_shunt == 1, f"wrong number of shunt { cls.n_shunt} vs 1" + assert cls.shunts_data_available + + assert (backend._LightSimBackend__init_topo_vect == 1).all() + assert backend._LightSimBackend__nb_powerline == 17 + assert backend._LightSimBackend__nb_bus_before == 14 + assert backend.nb_bus_total == 28 + + def test_runpf(self): + backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=self._aux_get_loader_kwargs()) + self._aux_prep_backend(backend) + # AC powerflow + conv, exc_ = backend.runpf() + assert conv + # DC powerflow + conv, exc_ = backend.runpf(is_dc=True) + assert conv + +# TODO env tester +if __name__ == "__main__": + unittest.main() + \ No newline at end of file diff --git a/lightsim2grid/tests/test_issue_66.py b/lightsim2grid/tests/test_issue_66.py new file mode 100644 index 00000000..aa418733 --- /dev/null +++ b/lightsim2grid/tests/test_issue_66.py @@ -0,0 +1,72 @@ +# Copyright (c) 2023, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +import unittest +import warnings +from lightsim2grid import LightSimBackend +import grid2op + +class Issue66Tester(unittest.TestCase): + """issue is still not replicated and these tests pass""" + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend()) + return super().setUp() + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def test_disco_load(self): + """test i can disconnect a load""" + obs = self.env.reset() + act = self.env.action_space({"set_bus": {"loads_id": [(0, -1)]}}) + obs, reward, done, info = self.env.step(act) + assert done + # should not raise any RuntimeError + + def test_disco_gen(self): + """test i can disconnect a load""" + obs = self.env.reset() + act = self.env.action_space({"set_bus": {"generators_id": [(0, -1)]}}) + obs, reward, done, info = self.env.step(act) + assert done + # should not raise any RuntimeError + + def test_change_bus_load(self): + """test i can disconnect a load""" + obs = self.env.reset() + act = self.env.action_space({"set_bus": {"loads_id": [(9, 2)], + "lines_or_id": [(14, 2)]}}) + obs, reward, done, info = self.env.step(act) + assert not done + # should not raise any RuntimeError + + # isolate the load + act = self.env.action_space({"set_bus": {"lines_or_id": [(14, 1)]}}) + obs, reward, done, info = self.env.step(act) + assert done + + def test_change_bus_gen(self): + """test i can disconnect a gen""" + obs = self.env.reset() + act = self.env.action_space({"set_bus": {"generators_id": [(0, 2)], + "lines_ex_id": [(0, 2)]}}) + obs, reward, done, info = self.env.step(act) + assert not done + # should not raise any RuntimeError + + # isolate the load + act = self.env.action_space({"set_bus": {"lines_ex_id": [(0, 1)]}}) + obs, reward, done, info = self.env.step(act) + assert done + +if __name__ == "__main__": + unittest.main() + \ No newline at end of file diff --git a/src/GridModel.h b/src/GridModel.h index a32e0920..c2fcf374 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -267,7 +267,8 @@ class GridModel : public DataGeneric const DataLoad & get_storages() const {return storages_;} const DataSGen & get_static_generators() const {return sgens_;} const DataShunt & get_shunts() const {return shunts_;} - + const RealVect & get_buses() const {return bus_vn_kv_;} + //deactivate a powerline (disconnect it) void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); } void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, topo_changed_); } diff --git a/src/main.cpp b/src/main.cpp index 75e4e088..65048590 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -649,6 +649,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_shunts", &GridModel::get_shunts, DocGridModel::get_shunts.c_str()) .def("get_storages", &GridModel::get_storages, DocGridModel::get_storages.c_str()) .def("get_loads", &GridModel::get_loads, DocGridModel::get_loads.c_str()) + .def("get_buses", &GridModel::get_buses, DocGridModel::_internal_do_not_use.c_str()) // modify the grid .def("turnedoff_no_pv", &GridModel::turnedoff_no_pv, "Turned off (or generators with p = 0) generators will not be pv buses, they will not maintain voltage") From 49609386123df5bc326b837e562576d99128baca Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 17 Oct 2023 13:29:57 +0200 Subject: [PATCH 03/66] fixing broken things after refacto with iidm reader --- lightsim2grid/lightSimBackend.py | 62 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 780f3e0f..5dfd8d25 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -647,45 +647,45 @@ def _aux_finish_setup_after_reading(self): self._big_topo_to_obj[pos_big_topo] = (l_id, nm_) # 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.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int).reshape(-1) - self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int) + self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int).reshape(-1) if self.shunts_data_available: self.shunt_topo_vect = np.ones(cls.n_shunt, dtype=dt_int) # shunts - self.sh_p = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_q = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_v = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN) - self.sh_bus = np.full(cls.n_shunt, dtype=dt_int, fill_value=-1) - - self.p_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.q_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.v_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.a_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.p_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.q_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.v_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.a_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - - self.load_p = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) - self.load_q = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) - self.load_v = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) - - self.prod_p = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) - self.prod_q = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) - self.prod_v = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) + self.sh_p = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.sh_q = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.sh_v = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.sh_bus = np.full(cls.n_shunt, dtype=dt_int, fill_value=-1).reshape(-1) + + self.p_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.q_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.v_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.a_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.p_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.q_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.v_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.a_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + + self.load_p = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.load_q = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.load_v = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1) + + self.prod_p = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.prod_q = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.prod_v = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1) - self.line_or_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.line_ex_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN) - self.load_theta = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN) - self.gen_theta = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN) + self.line_or_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.line_ex_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.load_theta = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.gen_theta = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1) # TODO storage check grid2op version and see if storage is available ! if self.__has_storage: - self.storage_p = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) - self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) - self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) - self.storage_theta = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN) + self.storage_p = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1) + self.storage_theta = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1) self._count_object_per_bus() self._grid.tell_topo_changed() From b9adae0707d6632115d1cf2355d3d6caa2ba4087 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Wed, 18 Oct 2023 16:19:38 +0200 Subject: [PATCH 04/66] making a backend from pypowsybl directly, having a script to convert pp grid to lightsim2grid --- docs/conf.py | 6 +- lightsim2grid/__init__.py | 2 +- lightsim2grid/gridmodel/from_pypowsybl.py | 53 ++-- lightsim2grid/lightSimBackend.py | 7 +- .../chronics/2019-01-12/load_p.csv.bz2 | Bin 0 -> 2729 bytes .../2019-01-12/load_p_forecasted.csv.bz2 | Bin 0 -> 2396 bytes .../chronics/2019-01-12/load_q.csv.bz2 | Bin 0 -> 2412 bytes .../2019-01-12/load_q_forecasted.csv.bz2 | Bin 0 -> 1987 bytes .../chronics/2019-01-12/prod_p.csv.bz2 | Bin 0 -> 1912 bytes .../2019-01-12/prod_p_forecasted.csv.bz2 | Bin 0 -> 1787 bytes .../chronics/2019-01-12/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-12/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-12/start_datetime.info | 1 + .../chronics/2019-01-12/time_interval.info | 1 + .../chronics/2019-01-13/load_p.csv.bz2 | Bin 0 -> 2645 bytes .../2019-01-13/load_p_forecasted.csv.bz2 | Bin 0 -> 2221 bytes .../chronics/2019-01-13/load_q.csv.bz2 | Bin 0 -> 2311 bytes .../2019-01-13/load_q_forecasted.csv.bz2 | Bin 0 -> 1884 bytes .../chronics/2019-01-13/prod_p.csv.bz2 | Bin 0 -> 1867 bytes .../2019-01-13/prod_p_forecasted.csv.bz2 | Bin 0 -> 1710 bytes .../chronics/2019-01-13/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-13/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-13/start_datetime.info | 1 + .../chronics/2019-01-13/time_interval.info | 1 + .../chronics/2019-01-14/load_p.csv.bz2 | Bin 0 -> 2914 bytes .../2019-01-14/load_p_forecasted.csv.bz2 | Bin 0 -> 2423 bytes .../chronics/2019-01-14/load_q.csv.bz2 | Bin 0 -> 2523 bytes .../2019-01-14/load_q_forecasted.csv.bz2 | Bin 0 -> 2043 bytes .../chronics/2019-01-14/prod_p.csv.bz2 | Bin 0 -> 2003 bytes .../2019-01-14/prod_p_forecasted.csv.bz2 | Bin 0 -> 1894 bytes .../chronics/2019-01-14/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-14/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-14/start_datetime.info | 1 + .../chronics/2019-01-14/time_interval.info | 1 + .../chronics/2019-01-15/load_p.csv.bz2 | Bin 0 -> 2810 bytes .../2019-01-15/load_p_forecasted.csv.bz2 | Bin 0 -> 2383 bytes .../chronics/2019-01-15/load_q.csv.bz2 | Bin 0 -> 2509 bytes .../2019-01-15/load_q_forecasted.csv.bz2 | Bin 0 -> 2009 bytes .../chronics/2019-01-15/prod_p.csv.bz2 | Bin 0 -> 1914 bytes .../2019-01-15/prod_p_forecasted.csv.bz2 | Bin 0 -> 1829 bytes .../chronics/2019-01-15/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-15/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-15/start_datetime.info | 1 + .../chronics/2019-01-15/time_interval.info | 1 + .../chronics/2019-01-16/load_p.csv.bz2 | Bin 0 -> 2934 bytes .../2019-01-16/load_p_forecasted.csv.bz2 | Bin 0 -> 2438 bytes .../chronics/2019-01-16/load_q.csv.bz2 | Bin 0 -> 2508 bytes .../2019-01-16/load_q_forecasted.csv.bz2 | Bin 0 -> 2036 bytes .../chronics/2019-01-16/prod_p.csv.bz2 | Bin 0 -> 2041 bytes .../2019-01-16/prod_p_forecasted.csv.bz2 | Bin 0 -> 1946 bytes .../chronics/2019-01-16/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-16/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-16/start_datetime.info | 1 + .../chronics/2019-01-16/time_interval.info | 1 + .../chronics/2019-01-17/load_p.csv.bz2 | Bin 0 -> 2849 bytes .../2019-01-17/load_p_forecasted.csv.bz2 | Bin 0 -> 2362 bytes .../chronics/2019-01-17/load_q.csv.bz2 | Bin 0 -> 2456 bytes .../2019-01-17/load_q_forecasted.csv.bz2 | Bin 0 -> 2031 bytes .../chronics/2019-01-17/prod_p.csv.bz2 | Bin 0 -> 2116 bytes .../2019-01-17/prod_p_forecasted.csv.bz2 | Bin 0 -> 2044 bytes .../chronics/2019-01-17/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-17/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-17/start_datetime.info | 1 + .../chronics/2019-01-17/time_interval.info | 1 + .../chronics/2019-01-18/load_p.csv.bz2 | Bin 0 -> 2884 bytes .../2019-01-18/load_p_forecasted.csv.bz2 | Bin 0 -> 2453 bytes .../chronics/2019-01-18/load_q.csv.bz2 | Bin 0 -> 2502 bytes .../2019-01-18/load_q_forecasted.csv.bz2 | Bin 0 -> 2066 bytes .../chronics/2019-01-18/prod_p.csv.bz2 | Bin 0 -> 1900 bytes .../2019-01-18/prod_p_forecasted.csv.bz2 | Bin 0 -> 1793 bytes .../chronics/2019-01-18/prod_v.csv.bz2 | Bin 0 -> 116 bytes .../2019-01-18/prod_v_forecasted.csv.bz2 | Bin 0 -> 116 bytes .../chronics/2019-01-18/start_datetime.info | 1 + .../chronics/2019-01-18/time_interval.info | 1 + .../tests/case_14_storage_iidm/config.py | 40 +++ .../convert_from_grid2op.py | 293 ++++++++++++++++++ .../difficulty_levels.json | 58 ++++ .../tests/case_14_storage_iidm/grid.xiidm | 155 +++++++++ .../case_14_storage_iidm/grid_layout.json | 58 ++++ .../case_14_storage_iidm/prods_charac.csv | 7 + .../storage_units_charac.csv | 3 + lightsim2grid/tests/test_backend_pypowsybl.py | 58 +++- src/DCSolver.tpp | 3 +- src/DataConverter.cpp | 2 +- 84 files changed, 719 insertions(+), 40 deletions(-) create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2 create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info create mode 100644 lightsim2grid/tests/case_14_storage_iidm/config.py create mode 100644 lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py create mode 100644 lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json create mode 100644 lightsim2grid/tests/case_14_storage_iidm/grid.xiidm create mode 100644 lightsim2grid/tests/case_14_storage_iidm/grid_layout.json create mode 100644 lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv create mode 100644 lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv diff --git a/docs/conf.py b/docs/conf.py index 8438a0ad..b57b808c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,11 +22,7 @@ author = 'Benjamin DONNOT' # The full version, including alpha/beta/rc tags -<<<<<<< HEAD -release = "0.7.5.dev0" -======= -release = "0.7.4" ->>>>>>> master +release = "0.7.6.dev0" version = '0.7' # -- General configuration --------------------------------------------------- diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py index 145b6f3c..577629d8 100644 --- a/lightsim2grid/__init__.py +++ b/lightsim2grid/__init__.py @@ -5,7 +5,7 @@ # you can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -__version__ = "0.7.5" +__version__ = "0.7.6.dev0" __all__ = ["newtonpf", "SolverType", "ErrorType", "solver"] diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 3afc031a..ee23402e 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -11,33 +11,36 @@ from lightsim2grid_cpp import GridModel -def init(net : pypo.network, gen_slack_id: int = None): +def init(net : pypo.network, + gen_slack_id: int = None, + sn_mva = 100., + f_hz = 50.): model = GridModel() # for substation # network.get_voltage_levels()["substation_id"] # network.get_substations() # network.get_busbar_sections() - - # initialize and use converters - sn_mva_ = 100. # TODO read from net - f_hz = 50. # TODO read from net - # assign unique id to the buses - bus_df = net.get_buses().copy() + bus_df = net.get_buses().sort_index().copy() bus_df["bus_id"] = np.arange(bus_df.shape[0]) - model.set_sn_mva(sn_mva_) + model.set_sn_mva(sn_mva) model.set_init_vm_pu(1.06) model.init_bus(net.get_voltage_levels().loc[bus_df["voltage_level_id"].values]["nominal_v"].values, 0, 0 # unused ) # do the generators - df_gen = net.get_generators() + df_gen = net.get_generators().sort_index() + min_q = df_gen["min_q"].values.astype(np.float32) + max_q = df_gen["min_q"].values.astype(np.float32) + # to handle encoding in 32 bits and overflow when "splitting" the Q values among generators + min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min / 2. + 1. + max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max / 2. - 1. model.init_generators(df_gen["target_p"].values, df_gen["target_v"].values / net.get_voltage_levels().loc[df_gen["voltage_level_id"].values]["nominal_v"].values, - df_gen["min_q"].values, - df_gen["max_q"].values, + min_q, + max_q, 1 * bus_df.loc[df_gen["bus_id"].values]["bus_id"].values ) # TODO dist slack @@ -47,14 +50,14 @@ def init(net : pypo.network, gen_slack_id: int = None): model.add_gen_slackbus(gen_slack_id, 1.) # for loads - df_load = net.get_loads() + df_load = net.get_loads().sort_index() model.init_loads(df_load["p0"].values, df_load["q0"].values, 1 * bus_df.loc[df_load["bus_id"].values]["bus_id"].values ) # for lines - df_line = net.get_lines() + df_line = net.get_lines().sort_index() # TODO add g1 / b1 and g2 / b2 in lightsim2grid # line_h = (1j*df_line["g1"].values + df_line["b1"].values + 1j*df_line["g2"].values + df_line["b2"].values) # per unit @@ -62,7 +65,7 @@ def init(net : pypo.network, gen_slack_id: int = None): branch_to_kv = net.get_voltage_levels().loc[df_line["voltage_level2_id"].values]["nominal_v"].values # only valid for lines with same voltages at both side... - # branch_from_pu = branch_from_kv * branch_from_kv / sn_mva_ + # branch_from_pu = branch_from_kv * branch_from_kv / sn_mva # line_r = df_line["r"].values / branch_from_pu # line_x = df_line["x"].values / branch_from_pu # line_h_or = (1j*df_line["g1"].values + df_line["b1"].values) * branch_from_pu @@ -73,13 +76,13 @@ def init(net : pypo.network, gen_slack_id: int = None): # for right formula v1 = branch_from_kv v2 = branch_to_kv - line_r = sn_mva_ * df_line["r"].values / v1 / v2 - line_x = sn_mva_ * df_line["x"].values / v1 / v2 + line_r = sn_mva * df_line["r"].values / v1 / v2 + line_x = sn_mva * df_line["x"].values / v1 / v2 tmp_ = np.reciprocal(df_line["r"].values + 1j*df_line["x"].values) - b1 = df_line["b1"].values * v1*v1/sn_mva_ + (v1-v2)*tmp_.imag*v1/sn_mva_ - b2 = df_line["b2"].values * v2*v2/sn_mva_ + (v2-v1)*tmp_.imag*v2/sn_mva_ - g1 = df_line["g1"].values * v1*v1/sn_mva_ + (v1-v2)*tmp_.real*v1/sn_mva_ - g2 = df_line["g2"].values * v2*v2/sn_mva_ + (v2-v1)*tmp_.real*v2/sn_mva_ + b1 = df_line["b1"].values * v1*v1/sn_mva + (v1-v2)*tmp_.imag*v1/sn_mva + b2 = df_line["b2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.imag*v2/sn_mva + g1 = df_line["g1"].values * v1*v1/sn_mva + (v1-v2)*tmp_.real*v1/sn_mva + g2 = df_line["g2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.real*v2/sn_mva line_h_or = (b1 + 1j * g1) line_h_ex = (b2 + 1j * g2) model.init_powerlines_full(line_r, @@ -91,7 +94,7 @@ def init(net : pypo.network, gen_slack_id: int = None): ) # for trafo - df_trafo = net.get_2_windings_transformers() + df_trafo = net.get_2_windings_transformers().sort_index() # TODO net.get_ratio_tap_changers() # TODO net.get_phase_tap_changers() shift_ = np.zeros(df_trafo.shape[0]) @@ -101,7 +104,7 @@ def init(net : pypo.network, gen_slack_id: int = None): # per unit trafo_from_kv = net.get_voltage_levels().loc[df_trafo["voltage_level1_id"].values]["nominal_v"].values trafo_to_kv = net.get_voltage_levels().loc[df_trafo["voltage_level2_id"].values]["nominal_v"].values - trafo_to_pu = trafo_to_kv * trafo_to_kv / sn_mva_ + trafo_to_pu = trafo_to_kv * trafo_to_kv / sn_mva # tap tap_step_pct = (df_trafo["rated_u1"] / trafo_from_kv - 1.) * 100. has_tap = tap_step_pct != 0. @@ -118,7 +121,7 @@ def init(net : pypo.network, gen_slack_id: int = None): 1 * bus_df.loc[df_trafo["bus2_id"].values]["bus_id"].values) # for shunt - df_shunt = net.get_shunt_compensators() + df_shunt = net.get_shunt_compensators().sort_index() shunt_kv = net.get_voltage_levels().loc[df_shunt["voltage_level_id"].values]["nominal_v"].values model.init_shunt(-df_shunt["g"].values * shunt_kv**2, -df_shunt["b"].values * shunt_kv**2, @@ -126,7 +129,7 @@ def init(net : pypo.network, gen_slack_id: int = None): ) # for hvdc (TODO not tested yet) - df_dc = net.get_hvdc_lines() + df_dc = net.get_hvdc_lines().sort_index() df_sations = net.get_vsc_converter_stations() bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values @@ -146,7 +149,7 @@ def init(net : pypo.network, gen_slack_id: int = None): ) # storage units (TODO not tested yet) - df_batt = net.get_batteries() + df_batt = net.get_batteries().sort_index() model.init_storages(df_batt["target_p"].values, df_batt["target_q"].values, 1 * bus_df.loc[df_batt["bus_id"].values]["bus_id"].values diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 5dfd8d25..5e99f0c2 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -412,9 +412,12 @@ def _load_grid_pypowsybl(self, path=None, filename=None): loader_kwargs = {} if self._loader_kwargs is not None: loader_kwargs = self._loader_kwargs - + full_path = self.make_complete_path(path, filename) grid_tmp = pypow_net.load(full_path) + gen_slack_id = None + if "gen_slack_id" in loader_kwargs: + gen_slack_id = int(loader_kwargs["gen_slack_id"]) self._grid = init_pypow(grid_tmp, gen_slack_id=None) # TODO gen_slack_id ! self._aux_setup_right_after_grid_init() @@ -481,6 +484,8 @@ def _load_grid_pypowsybl(self, path=None, filename=None): # and now things needed by the backend (legacy) self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)] self._aux_finish_setup_after_reading() + self.prod_pu_to_kv = 1.0 * self._grid.get_buses()[[el.bus_id for el in self._grid.get_generators()]] + self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float) def _aux_setup_right_after_grid_init(self): self._handle_turnedoff_pv() diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..cb68d027506b1bad8e60a550c63e8e8e417ad1ea GIT binary patch literal 2729 zcmV;a3Rd+(T4*^jL0KkKS@U|>2LK$%TL1tM00DpK005)};1Lk60*wJcXaySh`asER zXo@`bR26v4K$zBGs#R3*)ba*ZQxgHIeh5mbiGfWrF)=X&nn+Bln3x4BPbf-g&;(Md zf9I-HnE+hRfdnC`jy!t2&rFN)3q~W<@#`}eR9;BJ+~>k@$cn{WXXVQ#hmigUfzD^% z&w2*EoD<--b)BxKRvXtNuJ)06*3yDw2AJ7o731J-36(wEr`7i23sjlIG;}QuhWd-E z>SwZq$Ou{~81vhB>`Ok=3^U1~not+sz~|ox02}ZXK?D{kBASz`$8EJ48%VadYuT}g zFzh%Gtb6Sm=iFZN?&|M-e&P4KsQ}gIp1#d*fiVGO8RN6wK!rVuAy*iAq!$F-_CFL_ve}4oDV8bk2R4`kU78 zJoWC#KV``Y#sNF_o-47o&aU{cxH5{qAXB96&ME zOea{14yyxB<4Xo}7^~Mia{?(DDK>MKpqJHjGv=1Zb5vS$iiS!=&ja$dg8ZD9v|k zcfX$S_m6pBq4rk%yZIlAQ4#N%SbMI!guFG(TcIzVhDzWLrS|~q(-9N?gKs{)qrwpU%UeK*d=JI+1pS@h^=_if`LH8ta5oDThYS% zx2usci-www&}q`d(OFX>%9ZMeBpL$==!u_6(lL=aODhU2<0BY=rzD(LM^1!3Vo8C7 zoE+1>b6fU(dhmPW!3VxodS}PohqZAk3!Fjw*o6$;({-tc(d?<|4Zvv_c^NVv5ZWDO z3Rqhu5a7X#${dm^=$jX(VL2h71NeP@`bajvqgO5#v%`Y_eS`U#QpDzp7T;JFLYo?9skoCO!v9@2aN3*l+E$1DZS2CFJv(MUGsIhuil14+x zNj!_GJhCDj5fKpvZiyGr+)!dCLU59k*uJ}N9{9_KK!(0)W8f&wV4mKP7(@sXc@e(} z1^{v9FQO*ZE8^pAv&pcOutg*+4vdH_Ml(?CScs16WNSr@XsB$SzJ~~d1dl>|vQHX< zXWSdDao;`GD2W%m0P2c>!0-lHgtkgbC+^&@QOr27yt%Ym+MO#s6OuoPAh2P^eti94 zNI^-=Vv-aRabUm^A4ir5LSkSgBnY`ZFoc(%2fQbMe0#@&7zerM#1c1--LBo<$|v8w zD&EoJY5>uZ5uNq19HklM>8iVi;yD27D$K3#!K|-K{GtZUb6rHMEuYek!cZ*M8755>3ri_&mfMh=fgm4kqyuwmiin_$ zXU7aM$Y4y&vW0CZcHvz^zUOvEWs!qA%%&YIVp!`r!z#0^aWPC}$jOohW<*gk5NfD2 z35H}u(N~2~BLP0hkXAXWnr`N1IUr=PvJ#kJh@p@SGZO-2xSV7&ra6?cnCBejkux#Q zRw`!hVXMF*79byC879Ls1knR=lPJ%<(^A~Cmkd>wSjt^mZ&S%HR&(u_i7wUeyUg{HiYX<%K|E>;NkXR% z=#X(dizQaB(LC?ZdBZA1LJ!>%&Qs<}oyU1T`kX+)r!uhD6Q@}u&~r|}=EcM@i-!}x zaaWseAC1B6o7IUI!`tn>v61t<+b{qB- zGt*rmHVgRBm}H1ZNy`L!c}WVyjpb!goGWzimZFw2@Ei(GH9gA6KF&7p)Sb6X);RX% zeHHgf-uutxvgRf;FS&8V3DTbo9hsu^feg|Qi$!>orl_f9%Z@qGAG2a~~^t{bG+i+Q&%X#OxS5@ONcDiLn^6OZ-(=Roe70mYT=*Lv@F^7~lJ0|)L z<3($$b_F5_RDs4a9L%o4(NT8qaks>7jnKWh8-CU{GaW&LIyh!hj<9yk;fm5p3T6(f z5$mnavh!gir@YN`_M293vN1cC9&)K7v&UX;k*{Y=7uYO16~PyKOrA%#Cxf*2OFZe! z=%2cB8m!6HgPdI*7{PS}b4IRqcX+HSyR4fLxWrmAa7dz!Y(ax7dzm(? z#DaW557s!gUBS9rAymD(9D$ie$m;2??1t^&li^NYO@_> z`mOGEaBh0~y8Oej$(%2?bSu>EAA1P*ZC@9Zb}GXvhz@w`jWFJB_%&unG4*!ai(L<(ct5vIN zwi9frm~wMFo=-fgxYv87@%g_gb~m1cyD&TC755({o;t+KbR5wtQs&nlVu)vTz-Jn7 zyLvr*G-pmX+bXqEmg&5woX&O5_~G^+v~aRlk|fIIr8*gQ=d)$VB`32LX7fj5ao&xY zxog`!VaJyAnqfWOK3D}OPkL23aouf>@-j&4aqfu)_1xI~hIMA%C*HcZa#O1_jT~1^ zsCSlUxdIhnFvErqc?I#Squ+-O-3kip0Sijwah((d;e18}jrS@#_8D(x4w8$}M&Dla jlI!dYDB;4(Zkhe}hv&RL!~4blr~F;X6yZWc&Ff$s$!ao$ literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..19c21de8bee86689649d464db037c90b59dc0b44 GIT binary patch literal 2396 zcmV-i38VHxT4*^jL0KkKSx(&28UP&1TL1tM00DpK005)};13;{_iI!VyJu|aZL@8f zpJ5~dYDZbNuY0}EGYgwQ>X|Oy8lHiQs$yUylVpumF)%5nMkXd8lS&$yRWUFTC#s4~ z0MH^q2}*51-r33|ZXZ=Ta(XBHzlwf3zVYSph{}sDnE3Uha(RwFU=xE2>k?&4bgA7iYgPpa`Y{U=#2`Wta1R?5 zM1Yo!T5|N@AjJ5X09c(gSKiCa%kO(UkFrt;r0-z^fQ0f01_bO`4Z?4zz(|537M#~`K_d^KWGNoUG5g);uetZ%Ie7EC?ejX`$+LF@ zCyEGt^Xh<{G*CIfx;t7-h}m1&gwO?&b%-Ak^dREQHDd-02-!tLd>mMvP~zRGM~pjt z%x-g#na+Z)O-{skt2~6u4Ba_C>U%K7<3Q;Rq~TOhQa2jYu96 zJOZ_83ll55uw0#;+wRKeyk&dWT@P!Y9x}n}6TPVRVZCs)R&rFM#-s9`28NgD#6I)z zL4y&IVupl>YJ?`3A0ANnTWni-&97g6>BZ6-hzEl30l#^9djdf~K`?^IFrO@_`!?v! z4j7s@CWTsLEvU=s6M#m*ihU$4Iti$Gk!gx?gkd#Cc=EgpiNG)k14m5uT+?p5ho`1y z>$YHnHt8B)Oh>6|)3jB4$gtKogeY;5l{|&pO?JcTy&UZX@m&RwkxmOZFvX1DQT3QpV7|#4#~L2E%bo0mJY$ z!NFQ-ia|tzC`2$A0~o?UhzXo{8oCH;hCI>eDu?fBM1UuI)4fDMD4_`wfRsIiA|e2T zSTQsv+G#LD2AdGT9!7-F4PAi?L;29SvYYB+6)#N1;_o#cZ%FEkIpBK;Z;v zT{ShZDJh|Cw#W{7!#?DhM~Iaf?}Km1nF<^J4I9Ao(PrvZ2qAW428IU zX#TDlW*D?4M*nl^kbx&C%xDbSbH?0+zza_Sv*$s~iql+yuwr;H$6=UB!Rv63A)GV} zAVO7fI_ep?!imd>(P_T*RwJj8M@-x>(w2g*odNOF8qU_Ne8Nvf7NjDX*WMEYBRYxI9!@sg^zDpmlH5YcPc2 zbX*l7czE9ZZxoW(2gULj^X}#|w8C(rV`fo-S){OaQFaC{Ly1@qc=8Ht%$9(^CqeCb zYqT`)nCw^CvCi@g7w>o=Mo+Pf9}tp|PTBizK6O0sM1+^=*(Qaw(9+s@*`}tK^>S3E z;7AI<(L8D%r4MMQ64|Ro;`??i^`T@-rRm`niX41cmv$?w1qLPEirRCISh}ZtXij44 z_ZHT63j`aNV)CwOp$*%yOSNGFM zP*(Q?VrrYcEi_aTQ&a12O7z#n=(01c$|uKS9-e%i?C;rzVUys%Reing2d|J^x^h^D z4uQn^;lXP=nwH$A9dON>k2OLibD~5ouQV@n2D8-{Bx)meMpMY4MpCWk(Ts>Y#T7xp zZJxZm*{RyD9N#6nEIXJI3bwU%!>+OA%+neU{+v}e9-Lg)oii_zOf=tDGCBG26 z6Hvg#wB(Kj!w;^52PM=p5W&2TIOS1w0u)#kRFejak>l4xspqTN)E(}enXANxCP8Ea zoV)o%L*9vzw^~HeG?225k!0y1@y`K-k6;dIspt|Bk|gtxI9mvH+sCO)gDm?As5E!F zy;KK#+(C%Qi_Ei+lQbZ74T=^*te4X22^@{{E0i$H^qIFCoi3}EsgxT^bw1%s*S*7W z?oRF(!!t1S+Sjy2OS(zu-ObnBk_{CD%*o_h0 zmTsC;$wy7!8ndP3GX0|AxrVy0!o{9sU<6QZU5jHzWN|!aazi)Vei^g2E3djq z&hqyWoVo3~DC&LE;ZW?S+AdmD+CN$CeXVNy<5y<%Rqo2Dd&ym_)1gLDJ(TL!8@bs= z`!?0#Z@;XlP~NIG<4}@w{L%x`cwKJSbxfY O#oUoj6eJV3G=_jk@?`M< literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..35d398131afc5122e48cffd2ceb6fdc0f2b119d3 GIT binary patch literal 2412 zcmV-y36u6hT4*^jL0KkKS+_42{s0?8TL1tM00DpK005)};15MEK9*hbU6o?613Jd57|~nwK1GIs3n=Y|NWM4NGQ;L*Wm5^KjjcBf_4m8A zB5p+`KK13}MKzHIA=NTju_eKR`?2XGlPcBv+XkKDt~|uu1Ujc`vBQHWVH9UjgBEd4 zRcSr8^k_^axiz5Gf?*1PU9%hFBjdAxTTkb`pE|f1IDslSr&E* zsDK07sv?J}0AhWkGjev3OU9WUQmAaRp}-qk2tov3!%%wImfl=&=g)9%A(N$ADt zFhHyca#%IQ+ z4+nk5k8QksRMfN9&TsYN+`<}uiU?Ue!4+ZQ zVHQCU4yqcGY@bL?*g=6aCym}-8m!344^`KI=Bz?|z+sF;U1;$@AUW*thRYbwbVhX& zU@}e+83PH_wWPMPKBkl?Lc{B<-JcLPSV)kKeh3+@_2K3BFL%m(4<@*00Uh1m6NkQ{ zUmO;CZTEb{Ms^l&ThIoNK%|n4U>=M{3W&!S?#z~A#bCO<{YS34hO0NfRFK!Ejhg27 zOg!;~z~0=K?nmjyoujnzkBt=MFI4qC*rNDcU{|1oI8>HcdWmAoJ>spYx92nWmHwyS z)itK8O=@N)>q|}AX}e6q+h)z%ZVEr2YLBPSn%3;uyIU~L+ih*S{y5`E(@u3$rWalL zeJHxTU%L^SiQ4mjW%tjwPBYEJtRJM#q;j1 znzm`9OfcQHz8vY!Q(9`fuC6Ed?=Mr;TUs;`@I&*nWqy6?d`8WyReo{QFXH> zp7H8=8IpSL9IRw@*G^1j9n-6ojH>Q*;LJ(ayn6Iz74Vh#${SC|)$(+X<;B%q`Q_mB zr>DGLFH*esuNQ>!hbL&(y6WdH z;_kbNLX-_i5Qfjr{!h!_ZN}76qL28>S|=gK!3zZs(m%Hl{Ncfx8{F<$u4?9mDA5=( zjgD(soux9%Q&o^;DY))HS?BHZs|wY0xG*wtmMn%H$4sF;4W&-p@IhQYTe}Y&o82$5 z+DKej4iVVso~O|JN(CPu8-R>PRPBAO%tH}=M)@qv`{eq;QhYyi*e;J9`@H*g6&ZJa zb*iwrb)up*l{W_L^d$8-SX|s2$wU!&;b5o&31mA!jai+;t+GKcbUWGZ5Fsg0UMQ-6^f+x+nwI<&2d-TDMP}|!I_TpSm<#?H+MW_D14sAhYy zva^Fu?sT)hkX`RR_5|#l+wLX>zPDGv!<*fPa4;r1=X5mgS`%`mN80gO6-!O@(`&fk zV#gXUrA#jB`PVvTbBUQ1Tk9lAT;-OP``AeYc%=leD`AYxixv%QC{abKD5$E<-gkS@ z+f|HFK@~O|?&o`&tgcnAcmhEnlVVZY1UB^6zdY=8h3@WMZFXI69d%cnnlB)Y77?Y&mA$<7_7W-G&DiRrx1(LLew1jeTcQ)sI_;TTI@QhH!E1|L zNZV5L8!+o?@zynaN3HEH%Z0*5jn7Sc5rC0vyECZE-AfZUdw}#HoDPXyw@!b8JCsZT;W;HEw2NMw zLSud1lU{kNjb~%0sPnxGbnZ4YJz~f%l`1hNm}A=A8`(=};AlkRS0?KnIMKveR4y*@ z_1b55Cw4b4-O}fu78X9|lm?EY~?(%bgE@Awb{}(A-)gXAwDLiCQ{PY7W@ejiR0(S z9Az(FquvAE51!|=%Ef!EJZ*G*F`ifNlM=J-tGm6Q3j5zKwPEe=5<-E(5h3@7A)UL| zVAH_i4Dh2xBv+f8J_Oz#H{)GmX7s%j>iz9(7IhfVG_j^wBsSApqX?QLd9i8Y63p=8 zZ_G(}!4&)%zY};7Q2TVn?K2HXr5{zdK$twqOBOBfbmE?gzRZCn5;hY=pCpDe+;Y^7 zc4;Y$9W#&`$gCjXf<#WP%nLHE!$bs#NQHHQK2+W-I%00DpK005)};0*nWzCP~iZ8f^>vykOb z1c7&2)p&ETbe8ST?X;Z@PY^X#F)$J%MK-FYCIvLe#KgoB5gKZeXkb#K)ez;V? zu18M~Q!Az8P?u+G)7N8t2_cwPZ?I5pcDI>F5z2op&ezoDTBfGxHguhq3jn6mowVo( zJQq-pxDye{1WxO~g1Yd>^-RUS1|U4SV#3ZR!fMrvL(9Hu#n8b#MGZ#+DnvU1Fkujk zhQdT5k3It-s(C}?4DWQmbluAi9pycWp-k}L@5OcKe7}Mz56=^aSjSr#2V6SjTppJw z3HC1w_15lg9iwLEY3klhq=FZU({VMNjim_9Z5UyA&Yc?pzMUBu2x)W}Kp;W^ zA*C3vka(mRnRhv#zJuiMl~D5wLq;5@L`>q?KAj;HdY^~SjY@`{iw@HfwgQ?oAVee( zffq`w2L(EWLL`2(*Y7m_(!pLtJ6Yh2(q3Jm@gNHz|rFjBP7IXuvQF zNNocKC#i`h1O!1a8clSM>2o7~)32R#GXvC&Bu?EkOiyW?FC)q+>w5HLl8hIqV245S zsn>vf07R)FplYc42rwsk)-l> z^(Tz3Na5YzoYr1?ZDdrc?UwA3>Bq24)#ZZl_(c8We0T;Ykt`UAK=cSk5-f+`ISvj( z7oVhfyH&)vcfU|c=_DlE$jPdlZi~7sZpLcfv76xO=ny#uK7R*>cm^I8pwJ&kHiZ}j zPe`zWu|fdie57Yx9C0yj9=&ACdGwY>dJ`Pg(avdBI*y!p7S~Y0O6(B@} z2od5nDpBpzrlLGmSFO@|dS%BKdQgEFM^VgiFhZpYrE1g)+U$*{4Fq{aJ>2YGSv-~Q zG-x`VF2%CNly>cQr#P0@8CjZY=OXRZ(B;TGb#ys$55cBca^jCttK*70Pj7EiL75pE zhGmOVWvfOZjIhvSEgFnvjYeA3urac-jb>sel_l$ic8_mP!=&Ija!xpoPE(Fbtc7{P z6dYiYfH8`&(W20)Q7KY_(xp{tTBy*WK^O#61yScB?0Gzn)Z}nob#_j^=2=!)%M`{j zs+!iBMzy0*)?t-dYgSj4RXuW(vUzyBE7h23HJWH_!z)>)rffB{Ctd1x?`yYRFC{7K z@SfaqeewAx;eO}yhL8y94WU(tP^lO@N;`)F3?S9*RSV@pBl*sdArw{0t+LdjiZ05- zpyiiUgF6h18j?sf;df>5>@-ulv<+=G9!EGaPYV`8V+O@X=TpRq^nn;|tb?RFN=25{ zsatKR(+kkjNui3X0)$w|8Q&I;KcNDBCQG+SEykK#h%F*EQxi?4S$6MC&hL)+cxQ3@r*ke% z2!pG`mwHGpI4CMNC_FhZ&SgJ9lrKqvU=w2K?F>1ca@*}YhG!sj9EJnLqX4Y+F~hqS z-oC+v&j^o|OcXt?!79$%o1;Zk8LXb{DC!t7ikuN60Vs|Jd zR^+>v5>n_;trs+z5>PC`m`!fxW_IPu?(Ny2gV)^B=&HofG?XM2Lu$73C0%AC9_1_{ zcMA@i5Ojm2+vIiGzNgol?C{J|TdfdY7unh+i}o&DH#DGvz$;;b%6rlo~jOkq$qD6RRd7RtV5=>Nz;`bY{L@oI zU5*2M#g_#ZUso705gl|$RD_Ubl0w<6kv6=?92x13sIO5{WD(Uh3rfO~NlOYuj^vAA1&6{^DwkNRv=G+qrsqkSF)vDlhSI=NaGyc zP03AzM$;*%*kS`q(3prphloIgier4+IU@$jgJ|PmbK9$K*zsdz1}4$eJV@jUblQdx zAS>Ff2ayDL)HK%OT4;_7 zokyBIR@MrY9*UJft1zWa`%q)L+Ks(Mvg zzU_cpGw+27N>xNPJcCVClhkN6JWT~u^h^mFRXsovf=^XY^(G@mf)s*CGy+W$l4%%6 zeBQB-Mfdt9byoZ=PPFNnk14!w zY2Cf&!NZShVRRNPYn;NR?Qv(ZZo0;H3dsmI!ziTDWrl?gC)PO+QY*j@cw^oe9_h#% z9i%8pkcf0=O9KcV6j1|6N7!JdrSsjrb2D1*r!yh&&m7xG^>Rbi$@qqZHJGdCtVZ(( z$P;;7yd5r?wi=4KLU^&*HrX4#&QK^7iDi zXRPaZbjcB-Pj+`{8B+o*+FG6t9hpOAB0wY6a-Ay*U@m(Mch>oF9SmX;7V>sk%fudB zz_9Ma1k5(mzV+bYEKx%{vil~}rUV3#LmF;+1~kag8Y3hb7=Z9T?!^VlVc@1U?IawW zD;BNSuT~A1eV#8mcV%!P@gquHXo*4&i0m%~f^1n31vW)mL8_#M zy2e?jF;RKK$2o$-ktWJSw48Fov&K8j_)b-Gm1c#(4XX zC{aNay_>J9mYQfTcgJyPLfU(WN_>W!te_M<3-J}UG%Kyj-Kz#%K=}}7!Qtvd=mJ6S zV$3K6$N(4T1~)4+i^dvZ4F<Oi%Mfi*>}eZAFk7X9S{M+@kzkg? zVzv?xJWyVero`h!ahSlv2`+bT;EZ0=aZXq>h6RF&+Tlflvash0CoKy(g++0&mcwfo z0C7NiN}A+Eo90nkns6Yza||Y^rdT8@Tu|E>X`*Zz3mG${ljSKwq)m#AGe)CqmS)S6033IcAzK!dsNW!) zLo$Pg7|{jA#6cCs8BGUGVaq68CaA2IO^nM)q@q%pwKADmrll)1+Ga~7nW>qTBQ}(> z&6%YuG|ZHxl_r#?QkydyB{b4mQ#8_PDM->%kuxSt%NeCG$uee2LJ2gakuofr14LOd z6kN8}?$x=&tmYR(TGXW#mLW!EHo{WkHnn97C@@BrL9&z>SToWs09i~yp@fwoM5IA7 zQfzK*wOwwq-Ki!-Dx^`O$nt__Q4L)zX26&>j++LsrVwIBQ80`OS`fW{@b|s>^X>io zH*5I^!10|O%_j;o_yZ#Rdp;xBX=;2wJW){rx)57mTWhb=43A<%6*2u0;Y54H_;`EB!J=4my$kHwS}olT5uGmIUuv?O zTatFmpr>ovCYcz~7V4fkm$cIy80`=m8&GvFQxvUfp=H%$cKab&7HhQ3H>U7RW!`r8 za$BU*kyN@X1&D7(-Igx-#a26^9dL4|eEM4=RI!)Q5Y|L#3+VF{wMzG_B}0-f?nQf7 zd$OU!e#SR3bIQnW<78=D)wJ}wuDiLdvhS6r4F!ifCh9Yk6PS(;zIS{Qs-zi54j*LJ zJuhxgVqUZBYT1vC-Z~PT1Z~c|Wm)qpRay&z!|t+?2CYzmF;@erru68-b8nUC>MBdT zCg$CVX;niO7Ks(hdsL=u?e3jwd0=89avnk2%_&+FE`z3B(%s$aqkGZUQ8fnVLh+7t zDQs5uXv~zL?8QXPIJ()XX+#1}NF%}~DVp~I(E8n!Xh7ni!^7uh**FKEN*E`=6G z?4id^^QW!6W#Y(6gy7y%EIISivw08;i}TBu^@>q!wgU5e@m?jd@i^Hnf)navwL yS7uDEMvP(I>@qyLFL-Q6rp-q-l-2u3;XgLL`aSphK>Z&7i@744C`cW=wwZt~W_%|A literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..c793514416cc7ad3304d210c8a16df098d4bf58b GIT binary patch literal 1787 zcmV%7s-OS?2_+Om)Bz@$l_?7K;t(|>HcaGCqIO@xcN}^*h z$zAKTj#FT*FBhHWgwphS&NFV5+#+LOtYdtgOqDQ z+||o=s_2*Lv@zkC=d%qGct@azo`~Vzu^dOGX_bRKlsyL9d}Ibj7NZF$^^6?0j}I_# z?6!3-<~V~CLp`?MfMO=i#DM3oe5(!ZH{2aq_l4H8$VP^-VR#@Q(hh_j+8Qg;^HF+G zlHg*7_H<}ZiWJO&uZpPi=%*WQGkblW8x=|o4TE@Zk3|#*F(}9-Br1L%I@1d^hW2`>e48o5j1O!=-Z8nxi!~^#3U%&+}ax0`o?oI=XC+xWKfU)Df zFRt?&7|o90jos)YFQK|M%Y-gd7{XC$g}BmiV~lgB5j)=mL4UFZ_Ml$9xE zEYzr`TV`t|CQ~y~nRTQG|aP0HdKtv%!xLV%p|5IGc=Ji zBM{j#h@~isNP}XMh_x6Kp4zuY+juvKQJ6%c0Skw`<0Gae6B`grV*!n4Xu@VQA4s67 zkur>siVGVNsRBg!2#36QbA#r`CNpI08RSE21U+{`k=HR7ZElETw_ItGXuCEXbmyNU zE&BY$@{i{IQT!L@$@p@aPFt3n_al}d=_cr7Tg;T|aPFhPmP1JsODvWyla@^!29A=o z6p&pCjmjE3n$0Xt2-_)QGaX?x6w7joCIEz@5&|L`q23;N$?dz|p5I_lvik0qUQ><} zI-a}s*g}sc#Wu>cs%fi*?2d-tCx}}iLb?~z9V{8d*_P_g_TqtZYr6(@4x^K8(VI6i zCsMAhXFbG43sm;bbuIuwjeH9jS>T4>>Fba4=nWn};l3RlY!kgplqn;z`>Y z^#$dyeqNX~Xiuwmh7hjmVbSb{Hm^dOOEk8sc}nqEns+VsYr{`xIiphI6rG8uv@~$J zG|>(RAtGs}nmAm!Mz15&EowZ5K22`Am?jSsVql&VgCUWb(75JgvR7?jRjQf7vk4)U zdy|F2dLCAd=`>imMXW<}4iVuDL81>DmrS*pTsvbJ+1ppbZ*?)kD(hnH@Tw;%B5mB; dnVZS`OOk%5`bW>|52Sy^+>uTcBoh^?qX1ekR`>t_ literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info new file mode 100644 index 00000000..5e520426 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info @@ -0,0 +1 @@ +2019-01-11 23:55 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info new file mode 100644 index 00000000..beb9b901 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..488a31b2a6bc45530b0558582ef23207e2360991 GIT binary patch literal 2645 zcmV-b3aa%&T4*^jL0KkKS-LXnoB$l`+W-I%00DpK005)};1HrYqJc#K6i|8aXaTBF zO!Yk!0)e*8t1SX03x=nVGOC`4VAVeaB~-+~rkNO+n1W3xCe>8Lz(rHrRZu1)AX2G6 z6*Vej)i82=_-owo-QFsPs_QiO^W}wRj8oma7Y#gmVeO%KdC<9RRJ*K=!<=egNH%!5 zF&dRqEQDRBIj7ogZzG*7B@Gj)VKw{P)Qr%vdFCnCtZuQHk9FnMK*8A`C$3h_FKz z3I&8Ll@)Lyudvqh>)HdrLo<}gL3#&BZ8#sXRTTZz znIVi~%wn<0CTKudf~bfwJJu*O<|vS`mO$JlNe&mh^dvH-N)L=&v{2+wiyG1;&@*mZWG#IA_$7XWbBiq#YP*{lTZr-06zN+ zF$1&3_qomGJZJ-lz3mU$c`-*lYpSM~?@yA6MwzijprUJOmQ<*dhk|%Ktk5V!$bqE* z>N0gj6fy?n)D0?q;D8g}@Rs?cHjZfqk4(Nfy?7|T9`xyUJI$9{*}&+}spg*h>P7L^ zhLWXI6Ov8_vk8n~8;lVIlh^_C7_+SSMJ=`kO~PscqDo8vKJnh0R(3a{k+*m6cnS>~ zo$R!M6mT;Wd|OK^Wcf($yU_EkkD5^6(4mlz}s zd&l1AxzD^^{ZpLRuinG3fueJA07#% zRYhQUN#rAF6=bq;i7;8vSUC@FO(`a%z=rzm*0lX++{4+^+{c^j-sw1!oUfhp-F)-o z9hIeMHzI56;PVBaP4wk~v3JUa%Sy@2fr?~eh$yleix~_+RY@%4BE|<+BhgVN6H-kv zA!L~cckX6CedvzfY}Q$dRd{hGA3*BV@w5Y%NFPzSBjO$}8IV;Cm>Bj4LnbVk#Z^hE zBEeOdf-pubSb;)t`4v<{u~mzQjjSe^FwrJW?*{k*cq=zD*D#krS>iz|R7Zh>%U5|+ z@~NnKKJ_;;Xjo}pVs!2SiUugeuAwoOHl#sFjEe+D1rAP+z=(8bw*zVcqd|u9^30!l zAOpM>yDF)&o*A0f532!(mZbJtV0`@Jt(`}6XwEa2!1(av&j9&UGOS+z z#gQP(hYI5vOev{=AOnsEfNBUB8@g(=8pcy8W-`i|hD(x%?|cW8bB;`vjACM0j2NbT ze52-8SWIE@Pm|^{jAfWi%Q9a+ckBbtAul|YUU-W0#7FES*b^UqQRF@J_X23pSuHFE zB$iT=QiYJVSp)veF+U-eSYI$6Wy|m*V2?r{kA5lO_ek^4Z*8jzYssrkuf~pe9CgLI zt4+2$oF`py?r}~!Qn59_uoDfRl&|WxYsMnTc*vERPOGaoGoistt+m(hNxdfN3SCFSg*6}`!;BL%=&BAAXIicXlOcp@Cv?l{w>t+YS?FEQT{9 zD){((shO8m2j3!fPHFHTIezV>ZMH^5j5Vf&rj`VvAs}`zITB#PV8&a1badNmc50>e zkbB`@3Gc6OeNL&ZTcgbl(=(S_#U3V zWm8h`*q@}l>o_#+tNG_on@^+KwNDQ@){84$ceW4PezWNF=%?Qr4eQ|y&(`*=KXV{F z1Ou*(%}l`t9T|rL6Sm95u9sJsF8b@N*=JL)HHLPbd~`Y&M`{z^8#8|6gwV|(gSoqX zaW7j(#68Ii>9}=<40#J9@#z=Z%vT9H z+4U;0`<4T>G68%k#h2!jik?aR?A?tVDt(qU7(>2-n@~cO^oKbJ=<}XXT+A`BnQ2w^ zc@wl03N*AVP1tz2)o57tj$PB5mzJLqe$54trdCh8G`{a+7klG(&d9eXoWF94>I70$ zgc~)ZT=$-ReR%?~)s_wQ@e0B^*`#aY)kzj>9 z?DT}Qg0TH_VeeI=8GI?hr(*Klvo<%CFDwV1_WPdh@IZ_pli*WC{E0;lDW?wZF$=HW zOIRj?-;hn8nPqp6%#~l(eGH*S3uqxb*EpEi-R8=pR!%PhUgg7rIKyOZ+i`ket_nh2g&Wge^1@5A;ql>hYlEoKPtg)+*<>(5g;3WR#gQp^xYUiFF6La&ur{OZ1})Pg$u#558b`q zxfvS|@gdt=P%2Rb5`E(h z&i9F1ntE5x6)A_Te0g1`k6NZ zk$8Fg3Iwgsk;9lZJIS@A%hJAq1Iz;OWI7roX=(e1^ zMrIap_ZZ_DeZu>AW__dF&cU1Pp$;|=D;wV0>y%4PUhOEKFF7>q4>PmU7B=cSFi>2{ z>3y%P#WdAOwT`^#xFm^S<;Qt^v5<9MblO$!X~Ac@_ftE3qBm;p5^4)>=A6B^47A)b zKD*(O;pVYT#qIOH?-*&F=oBxp*S@1v_uItI_;kI`do9h&6n%;FFz!gM-So&#ZwL|V zh;jBX^Uiv3S|Ga-+{3aN%XQO#XYr|L-5*{P2HN|BJaIoG3_L8Ffwo DhZ7Z6 literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..8e341212e850a9d8610c1a7cd293d6dee6f528d3 GIT binary patch literal 2221 zcmV;e2vYY#T4*^jL0KkKS<1~5%>W$mTL1tM00DpK005)};0|8iGwa)XZH*qay=8X3 z2D*3IAZR;2pE*9fUiNZ{+30gItTf3nl~OPSkkrDeVqg@iKT$;?wE#(^l6k7CVqhXj z$WcuIM5alys+gE~50Q-e%v^U*1E6n43cXj89d&_ zb+;RL9A>*U_NZ%_)x&b-R{EwJc^`0CZR9yl2e<~AC1tmpDS>aJjJx{iyWB?&k#_Ti zKQ`fKQv)Kxat@3M0s{oGIUsTnN>rgdiSE1P_h18ZdxmMUVYaY+(~;5?R3tKI!ANlO z8+wNTFhe9mz<9`#F-aoe86fzC0)Z$UE8rg;akDCg4(#Tm=I+9@ob}vUS3<*38xX7Q zR~#7ZA;@V7bz==j#&?tI`NdV%90qjyEi-YMTT2e#}2PAf*G`r+>OasR5tj*s* z%M)!n=|u2tH!~H9`tWN?ZuehwRmU zx%AWYWmy$IZh#au)_ptTvdG&f9XHAubq8kcvqs#4BaPuWfXo^&uB*U%-q*_V`$an|QBnT&!EKOlF zj6+nRDMN{%o1Xg)=e?Ed`!=r*;m)FwVsO&5qf4&T2XNuZUwTg-g_)?zEatP|JWi;x z2T}x65ViP>H6%zy&?s6LiK-mVUHduhbDEfHX>o{IM;}epPjQsMKf)Yg0NVoFrS~9H0t0}dm zg}qbQvFyEzS(6pDtB^z| zrFr62=Zc5qq4r8M=cMwT@jh8#QkFqsEwU7{z!3;i(4UIT(^AI1{UmY-_<;KR=%i0r z13W%L)Gb7?jfJI&OOF-9mnDIgWZ}siIdVC~T$daia5%YgILWO`V;TqJu-b0LQv?J# z$x4&~#}f(>OBkCF(8e*KV+K+d2@R&bS7_?H-t_9amw4gIaV|RRw^OQh*Lymvw7Pe$ z*C;QPp%_3VDJ#2Hh9ua=h9uh>XhUooViRo)8c1woXd4<}(32ZDaE#c&#~F5tK0H&h z=M&M^wKW-<$ZJ?&&6u+>i)Ph(dc9LIrjygv#X;X7;yc%E2GcAJjc9s$J&R_THfg1` zmKVOs>TLY3ONq`oIGiPRlm2 zyJkz6vxAbU5Hx27f-=JFdh*&%6P@9wG)_Z9Z-G*@+N7dPE(Ltn*mRXt6{T_Ox3qY` z%?(+w@}Gn7dG=|C4a~`dmvY&+?9Mu)Y4()AVe_JF&)gh08!BhgQ=Ugsa9fgU^Q~o< z3Pz4f)-FZhP6~ixz{QiW>=BFF*R@{aFL3po6f&=_!Xt4gg7w%MBFh$@8EquDT)IgI z5Zi$8%Tzo9eQ=7gWI!dVd{C8b3MMwJfjh_uh?_4w8N)4R`e)hrI0a_Qw44eZKXAC6 z3^M2IMiqerP>r~m5<#GL6!0o~P-65f*l^lPgM%ePI_XYHu+rSN7G5E%zIWikdr!s> z=?|s281y+FO2wUIRQn82$QUdTW~hubW3$pfRgY>>6f)7ayk@2AW&cfL{c(CVgmtUJ!D2iVL9h8)7$TFhE0$#T#P;h_j2Af%cZ$} zC|O-yot$y&7T3M)zhw_sdv6(jxsE40wdYIq<_G8PFP_aY#goEkFApK)-X+-j6Vn&l z`!4hMyO}iLoZEYv|G=6%CKGIo0*w53l(C;i%Q^fJ-~Cs!Fdkq`g-)g zdGpPz*lvA>H{M>*V=jtJ30unQ=Rq&DA`L0%3p=i>mcfnmEd%PHcun=XVhJVQF6o=- zJ3)<%b+=gQ- zo@l^}f-VEsFJLCnV|FHz4oHoVwo6;CV=H2)M~gPq)Ju(zQRF;t-+js=@VVxikFYD3 vP;t{4ShJ`M%*=Y+_5Fc`95`8R+ds8K@~C}*_(guCe+#)HoG3_TW{Kv2nEx+F literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..887ff90b5c5059820a46bc975d3ecdf6c0037e26 GIT binary patch literal 2311 zcmV+i3HbIxT4*^jL0KkKS;M1_ z=fUrqoWn6nrBtxc+IVVt21<%(162GGHB~V%5;YWnXaY?r%1>2O69E!U6Vjt0pi-vQ zM3i6;m222d52X1#NWr)?i}n(5_9D&-7$3_~J&uu9e9H!`5mqUEzPMj2)X>~c`^QpV zSnC$SU#zbxdizI<$+k3FH<-X(73>=it{)tIuO%2X=6IOsBpcytomfkcX%zFL!|Sy$ zUWg_WZE6IUQ-OI_k}?3~CN@MTV_yKk1HpVji|9g-JtXlVX?MSI^}Kn*nNv*&ACK0) z7o6u0p@t~`r5r(jg4S}*6D##Qo!V!RAL*N&b zo$O;0bo0P_#-FMYEMSokH6%e4zZqxL4Fg0#_zx-yho1?^hZtEHAS7VOr66sBiYh}% z3B(R6Z$n}bT2#s?$0V$)=Fuhmd(Utj_ceL7=w;pSc-~FhNo4pZ;T0cKj5sV2gk(Y> zg3e?ixJP32S{*1vEEml1m34S>w%3ww1V3L|@o%?JEUxlpIU5JE{T#niYLi3hJc=pm zEad#y2V__zU?Bj6BzPgjwGEqQ8f|QPWgPZM6C>No5T}| zvv`wGz@}X(GA^PM)2Zl#5uAv5067u~5+M|Z$Y}_hN3sJ*XcLI}le0T~)XzJb#0>AS z@8JoI8g-W*Xd8%H7Iw3-Lvlh1oyrO#PTNYl2oNI>IHszdkn<3*oRS(6Z@uR;7+v3& z>-zBgYVS^1nR=Nxsnbl(QEDbcN4+5+N*IGoBO*2hmIG8F79JoAo}(_kcQ*R?;aktw z--yRHX+>d)n6ncL5Z??$e5&whL1KV|1`xs+2UZZWh{9~J4@g8kN;{Te?~keTQ;qYI zVEnght+KNmEJr2?te9!}BRr@)Em9F+sT73kl2{!G(IN2)0fFA|@KTGllH=TP+^gzg zBzP>(XbA&e<1(O&|BgfDYVT~Yn9W6 z(qmJl&V?lf3?PwU5P(>SC%dHr(&vUHDQGMhu>oRGstg&BUkLplt!S-mX)Wz*qGr|=yfg31L+7EkXC0TkJDjXlKE#j4;=Y|%%f97dYlEse zw6Yo0>aKEhUawKz9ZR~ML!X&4L+U8}-r+v9eD&oX9(s8W>hB(_oH}}XPIPhN^p5E| zPIR44IJ%ds$aTjhq*LS6y04Sx6ziOmuHw4qMb+Id?n{S|@#V`=SkTM|ziV@5coif6zcJ)^~#kH|uwFRn&Qoi{G#fa>7yAjU7@?Q1@ z>SvPQ2AKBZz3JGAnK6lo9e#ZMjok~IPq#C9FrdrxP-3v7ZErpWP7OLIcWjP&GS23B z?OmfQOxq7&Se-KDd0}?#i;eronJ3DiqN4#Au!rOuWr8B83J27x5}<<>RUNfel8A~b zD+Lw`AfmA!%&1g|qX7|!k>9<0_wRi+ULd1z$<*#`F?p z-Nf*3mZ#^bz?YnQJGUuudR%(#iThY%D(f8rNIoPI1DDmZ%k|rXToFzMlE|WHUS`*& z^y;qd)d*WUPR&+s1KXNA1zYA__Dj6n5b)Q|&pKwtWO64-XChkXvJH6egIOt~X05W} z%)B#!AgfY(f(i3Lt1>_E_~~y(0y-nQf1Px-cjx?4-<%Apz7Dt zE?(WE-uWwO8cmakPdrVCAxo-TzI=Va*1BkB-qSm#8)k0~&@!`bKIu+YCWM^`M_-A< z!j|z1LC)#78GNCHXkx>+*S7`Ly5SLx9WPjKs$uJyuuFTW)SP_hQr+uTrd~7cVZRvQ zw}&qudhldD6SV0OPirht)x8pdZ)U=$9v0cSKFe z`IyC2-8sPGDyGfTyk<GpT3;`@h2b&-vJ5#!#o!!ieE zACJe|l6P9#bJibryD)DJHlK~O?;j8z0+K|6Cu-D7fobag->4*ag+cM__jbpD*s6Z6 zI#(_D`l?>IGOz3e&5*0e&g zqgqv7%JRH8)08RizhBqxxP{ZNU3r76p)Y(jlUqkU7-I-6E*2K^RtU&KqQ8HyHFu9`Qy3+U+&>G-iZFOP5PE~@BZS?Hz h>-0A&Zf<1E&1Cqe@}Hso2jZXN?ntK!5*gTa5g-h~W+?yw literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..74f6595f6bffd4c371ba1cd3ada710da571c2040 GIT binary patch literal 1884 zcmV-i2c!5xT4*^jL0KkKS#<)>PXHTu+W-I%00DpK005)};0#@-udrEm?XtUWxgteS z19#TFx5P%JRw?(f9Prab*)>!~0VFg@`l^#A14@3d)9K*y zVeP(hQ40 z>L*DjWAUfE+V%J#Z4SH;N5)KFGJF|JMS}K93zuM`TWq>6hzsCuHQwio(V0JzhiQT1$jU(1+VI!}k`x z@7@9zwlTU+O$U;=2Tq9aFj8f$ZTS zZJBVlRK7Nix@cH<>O7N@Bf(%F6IDecDisLWM1sVOSrx@_aTujk=!Z|Z_U-kVynwd~ zlC=WWLgbBEwsJ>KN0MOhfuX9b8f21@Mlpme9B>$=Md~k4xbM4@-0huh`0^Y&k>Lx+ z&K3#HhSDacq1Ui$RVadkAY_J2fC&PCHbQzkYgm`O!t&Fpq$cJ8ny}L$7k)kywK=J7 zS&5Xr98Xl*KSIp;WA2`}*=M&r6daS_I^1S@Dc z76Abwl8I-`uYJs4ch4>o>0VtmO<}DxxSHvqgS~qPwLAh;Y?IXdN5c8x_Bl>DY@4;U zX`@tWsf?yrSYXMOlM@sLKQf}?;6a3pNWu_|2>zQ^v?^7pUHnlPehA2bvI>ogA4Mt8 zy_EaBBk0!3zTMqL&9qIl)h)AG4P>opk710CqMn5xPSAMTm_$rPwF)ar1w<%NY5_{6 z5v@XsMToScKnqr=f#Qhsvq^a6c#A28ZkD53G{VASW)=~xskCm(Xv#88Xw_T0D}qlM zEs>d33I(W*N>mD!sI*`NSxXG1h6@%H!IX=sK*9r2d?Lpbgcv}C2z;e!Yiz8wsji&q zbCjM$uW}6szOQa0x^-NKanqG`$4->fV^np=4vDK)((A4`t(!I{I&sP3se6RkJw1B4 zM@~Armrfk6M$Ju=j&yW7>CRU=>C$oBIZorNlIxCMDOaqa>Uw(~j;^@j!tNbi4t3GW z>#i3|yQt#pjxK|{t|vI<$m^XRN~_o7^XKEUd*|}cKaOS6GfYuk`E&evzs_MvhhDXQ za(s(Q#MCkzM4t3yM4BQxDNfT(n#e@}P-$L?VFD#f-4jElQT6l=>T$%(8N5jv+!?^Z&F;Brp3J>%zS#XUqFFfX7>+MV z5E?N(2d9DJ3x4n4cRt^`_ul6E0BIPIg^Ys;i6Mgl1}MUOF+pNMREUDGWSS`zkU-vd zbGBjLZTxfLzMp;9JTG)!zA9cwInB5*d(b<8K{(xQj$O&MjqAGJwcwy(h6G|$tbo>n zYe@uvHU?-=w`nV6je3q7BQYSfx9UArk8$I+TC{+>(uL7SE@N$`tI(A`w7Jz zw;dOo)sJ%V-e=W>Qd#bBBrCkQmeLf(vpx_ zfMO-*#eA0>=P`^JuAfal@;;2TPCnPbh9d>Tcg!M@c}oKyyfBpnV#KJ3%a>qvL>e%6 z6fAP5rWQEFcBH(S;h6J-4aiI4KOCW#D5=RT;K0vyJ8LVpGk{^98POs{&@eYBY0oH; z;V|1@4Aj6e$(4L0V_@2pxf8*skA;g7@h@jvWwSiwk;&})15KSec=vX3{i+|8 WL+lU2EB2^=3%MekC`dYiXQzP4OppQq literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..a8f9567a0e415f8a0936aba62e2649f08d4cca1e GIT binary patch literal 1867 zcmV-R2ekM?T4*^jL0KkKSz#ifWB?DuTL1tM00DpK004r4U<;)u3}jM;Qr`lVP>2wx z-teM^gC#{YFx2pYQ~*%ZOaKT`5;Z^r5Fs;Bppz*ms0<4}ef##HUv&gaUk&|v ztXB5i_4ic=yR{iB-I0g5@0x>Y(Y@S?H>+`BsnszCdhSdFF14qc;a68jw^J1ms?K@Q zF;K0WW?pWi#l6yjtm?^R-i zhK}xK$;q68_HyP#VDcqf8Npn(mHCVIk_7?p4+Q*f{LYzK(b(?nTf*OlcxZch%KMJa zlS9dIli0}v*fQC4w=VgoC^;kSi{EfPov12+r?51RN*Ra1^EZR@Eb-(`S1T%E_6_N3*<$5os4^34~EPxh)%aWH>=O@UG5um~8cd0m3rI zBKkuajx7hsuP+lo;q<4h?5L_~l{M2uBi5mU_~ON>e8We)Bcheqoi<>dqv%^yUV zMQ-VIM-mAnA6g`m0f2#4CTjMGtN_7W@(c?E3RIy%D#5ldEXwBC*;+$XJA^1|AfHq8 z2}y8wpFz}?_>!rNqRXX(k|FXH>ls5+99VW*K`^)=f+xUBeFKs-u7#4J%^;dKFc?A<(e>&Z zQ_pcB894?$p`%hHl(1X(F&!8jL81f%0u9l7S{Ji+@K?EIS)pi9O+F|b2rNy9UJV5i zRHtz}0goG{oEd&teL&hVAQhl2kV@icP$ZIkQ4J3-QHcVzQ_8vMIMLI(;KNAdRtWJ?$KEdvV-JK5^J#&~fPQMLp5;0gb-`ZLT+%`9P==190x!lk2!l za!FgmxW{;|4<{R<6NopC@*a1vb?1>p+qP-P2Q@(+qA!F4!Q>po^LE`KX)mqhAoN`q zIjoZrvGW19(F}3I# z8OdS9Md<<>aNz|8cEH;bV4|fm)B-K35f)B%<&Hul3kh{KQQMwK?Fi1f=DJwDS`-!DT-zZs)7lLBA}XxnJ6kC zrh+1fnxQBPVu@lRpqZ(nN??$pfQTxZ3L-)!C7MWz0Ej3kB1nn~7>J0bj-W4)fxz!+ z$!HO_R0O{EwsFp5bP#Y^Io+~=iY_|AIWm)h3uB(*!35z5B^OT3*12{_ZYv$2uH$y( zoJEl}hPA$_OA^vORb37oOv4;U2xA8)YNExoIrzMAbDXw-mD%QUa>bGEvBQ*LmC+3u zOCY4dBv?Tp1H7(ys^D^|*<8cBR7S)q$DL|_8;o`6$JPscwWoD{ido*TGB?z$w8QI^ z^J=wKHY+q&E}64(b2^mgbR>MF6{KkzXquv_r<6HRO<@DTpL@f=a%X|^qH4Gv8*mp` z!KUtJmg}y`kS@&Z+%WYAT`xl$kS97*a2nu6MV`W>yh4)D6od(!8D&VDFCR;4j z)Y;8eA)Ip1%n}HuSmk(!7l9`7S1`M{Z(7H7)p>Go+;mOo<|>A6nDL_yUdPumtBqB+ zRX3GRdEK=GxcM&S0)cd~Df8^q;36E$ z7ds)gL8(L*s+H#F8C6}7X+VnBWv$b-vBjX19y-ILheWmu8J>N)J!C>BE+a3PqvUR) ze0>PcL$;r*X^bJeW7vH)iWAuTcBp67j FSpeJUU9JEC literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..8a51198581e1d45d07188d2ed11b4e0c2347e795 GIT binary patch literal 1710 zcmV;f22uG!T4*^jL0KkKS?B#m_y7;Y+W-I%00DpK004r4U<%P67R_p^Oi#K1e1s4U z>)6GZ+3q{xriqxUrY6v7geIt_hA3&K00f?@sL=eR076MAKxhP-ki{whj9^Q{PtN)E z2X8RF!|QQt_UD81`?2{YLt0OivP!hgpgoH_&sJs*Bq_sJU$`T%1u?-mdGhV!cwW5A z_bl84lscHh1pk1`jwHI9OhdDpoAfbs*9X5^`hZb`x{cMSwhLlj^C` zj^$t#mcX}2$lzFqhY5oQ;1_P!=wN06p8X%Px-x1S+L6eMU6$&qccn@y2C>iLK1Q8x%xSeagn* z(D)Ho+pg@Y4GYD@a||r7vWoR9K^bFS!mBeP7=hGN4?7rGsw<(2qO(Aa?(E&6A6wzd z=AG?W(ZVo0IXiCPZtl%Z1Vw3g4S-Dr>8vt0ShzTBhe+me|^3#DmCL zRw9KUDLStUG&I97$weke8YNy*IFmB_$;=Y6={fRRdG||7n1yie74Dl1((v7!=-q)G zLcP_MLhC7S1iOG%3}-JF8>=B8Ia(0;`=7M1qOSv!DqnyKjiILX;P%&h1i78Sk5Q}x z2RI}_Q6Le(4li!KW;>KIzEhN?Hw&rj(&VDL-@9C=zaHO??oYlw=rwW*CKgEejpy^JqFPj3=^iN8Vqa{6;BF}p&jC4Jt|^j zZc!gdG?){h@Ieq;7`aq@K!@)7d~9ae!34z`-%F=Ms^?(q71P|}?UpQvpP{2_HKtaR zQk9x&WtnW6r8bhJ%%+txibD*nkjxTEEE15&OC+)If*hVXEn_Chv-Wo;+Z;}4<1A*y zCq_6e5yXZdI*qyqaA!hNIP517$2Esp-qfi|5lm@`Fr^umDJ0TmB+`_VMv}%XZIX#2 zW|}0V%`#Dq8Wg0AnVL+S7@1>587&r)EL5S0qJ|-$Ao&%4C0`HUIomFhakXG`Mn4Q2 zi?HwqVeZQ@*ra0vq>~avkYsxX4l6@BHqjV6ZZt`Q7|zFR@>?w6*z1Ra*zd&y;YII1 zEY6_VDahgpr)6;kUvtaR8wQdNQOQQ>s+dr8e%_a#)I!kf4_>AL`e4_p-$RP+_%GKop>>y^NEG2R2I)zO*HjaOBq zTu4$^&bTm(?|VEJzd6qYk&)gTCIYj(@3!svW!MC;fgX3HkQ-wo$UGp+9mlb4q8L6S z74jMg{aA;>8VLPb>#jD1f?w5%f@jr9@R~T|iGwY)(ZsR0Qw7<_7iSxp#d23ExtAwf zxx1WesMitPiLM-gAW4SqApHlpd93E`YP%cPlziOX8^RT)Q}$*_Z-tEZIeD^a551v! z%Ts#qG4x^f&cy3@13d*6zG69i+2;yP}J2XgE>7dq<_64E#l_4Y$Vi?nkyLtPwg>R6D+0I49! zOj9aGLcu``f;<6++nm)UAggFGA-#K3R^+#PPYxzjy+?O|O8WpeT8 zOiRc+HMYDq+udM$&}XqmNUC2AQ9|)Ck;X-HoSm&*S)hzU34n)7fez3}Hv)7d0^&o# zY2!x#I114)+9u4a0H1Eon#EA%Ih!|*t@;bOe4s)#n8&SmEd(RV+wiZb*>xy+B0A>X` za^C{{;H7UCi(8j)NLg3!KSRq&twG1UnwN1Xgw%qx#F&OsmE^|~;RzFjn%Yk<10(~| zFG=bl0uUrmM1&7PBz;hfUdBmiUzg4StW@K=(GF$K=U|f|T=mJUbvPO_=M(EE5kVGUrh=jn~bQ!9SJb~bRhooJbl1Lk< zo8Cal6BELD@##fIQ7g$z9>y4*4w$9`JOHuY94r99e8(MCl_UnXaY%L3y}D5gyx6!p z+X5QcT$|bC&Q(24_9d&iP_jnG!^z;2Wx}38kcB{y;fZ7D&iOmj(VnOdEtX;JgP;!# zbwb9A8aK)L&ph#7jOR)MnW2PAFwya>yh@2!5!6}GlzbNjV+JtT3=1_F6*yE}jBpIv zL^TLaxE#pBPSsQA-A=|Hj7cQ!4@Usa1`Cbos>@S$C$BX5@ho#tA4jw(0~G<~Y#2-% zb1}wMKA2HZuKQqaTLOcI^`Oo_KK?7rRK-rs^$m9@g{`X+m=sSbY#Ws;0w+m?M4PEb z1Vq=4rVqh4V?t7+Bnb$cBElvlQVb$?1_S{Q5kWs(;I1j2Pd<8hVQ6K%4TlCCSGRVY zu&_F8gc2jAZDLvlDQv5mQ%FFNAhCob2&tukAQ1z$JtyDN0x~G9fCxw;G6;bFkspKs zG-Ut~9=FzMT=+N~P7{C|_C1CWTAfde(+uhl=8%Iif-%?~POF14&IvTy6{ZFiZMy1i zn3@~AuHAi97xOa=48_b5A4EVtKA}Rw^gKRdIOW(jQ$-=F@(M|d0p&`6zngyL~@(uz>LQ1`oD?_@R}l1y4SJn@;2a+!Bz=%?5aPGpLv{}01?*fWQft8t-T2ADd#3>J zUJ3f2X)*~=UvTFRWrYzNy=XHf0nVI|?*UN!ML_t7oecnb7of`Y(rgHCIl@hc5*yyl zEZDU0{O1j?5r(u-8c6eI-6^n-G*B8C^MScEJ42f`>u?r8%+ z2U-B33N&a#K$<(lhRy&)A3p17KqwdjNu_$Qa8TJbJ1_zpZklRpI#;iH3GkipK?Cjr z+9sO$;o#PRPmjam{o5G;DGj#EK<)2B>zZ^Ach2k`>Xx^oj=|N^FO)zs08*z;obJU9 z2DP9Qdvxef(W5u8O&DI0y@i%-9p25m6D&HL2;yMvD_dGB_<(`*z)lUTZ!zi{A7yDl z`(DEcKqL=$O?1#S(6zYKpm}7306JC-Ai-MR!vmtd>0m<88(%j-)HE>QNhQc6fcR(K zHCx@i<#T&)SMJoS?Wfu$(a*pWm^1ll#2791z z+QUpVF%M@qdo5|Bn<%4!+}82ky0qP>H=|>=p55h+Mgu|x*f?5vZ`O9KJeU^UpxtKN zlqp%2ypMAZrE3dxYLQ?XgJS9;;^k)b1JF$vO{#WSI=yBA4HuWtF;pye`k^~UKK8)! zX@uPe)wv-fS{%!gb8gmiT5C!5UoG6-QZ_>~*)H15xNRuyBSk>aCPze;I@mA=cV@8G z9O9W7i^jG!s9Qs=(Rx%v8m2QYvU_zW>S|n=s5(NWRP+Y#@LOm zvyYG{aa&e>+l$-K>5BEvm@Gzd=VyicnOa#~> zqT|u_ikXmmSWz8Eln#mTEb!KgLLO`&85%~Qug;a^vXTXNm>wh~k_L$!&@ib(chI zBK5stgHO?2jX80g5c#vjRWWQ@!l70aM5leMH)D3257zaUUWiY zGBOQEwHY;AEw;69On9c}8MED5r)w0|+*(S&AV4T+00B@S!7&6tLSVE+X(Tj)0%qz= z(yNy*cKKHKUn6+4lRJQ2?CafX$U;0hSWeNtXBk|C!5SR&#!t~i*^~wW9dwEIKdS+;_?y?)m>qsCvl+hgd(_74xiPwA7 z=49=rUT+=jGHbk~pz6MUsByiWaACStg6(xc)!3QM5Y}GR_RjBB$JSA5>{*J7RWzPb z2C#L_Q!4T99J3p|bn}D)epDRq&C#Ps>L8T z&B@n(T4Rf1DR9K-T+52*X>4TFo>+uEtrf84&f2^$&(q%Jrr%eMK)@ABU|yU}XHIy@ zm(+kWa0RtApO0FtzF7Kqt8PncT&h@Sy_jxpWizO)#Ws&rxzKtYE0z{-+lgM}Pq$UJ zM~>^&9hm|VkeZ3s;(S(ejt*L&U0p%9&2zX;rI_+f?#t*g)5nfECCB1NX&nb_UL=1P Maz!{$koqn)zIZZ0ZU6uP literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..1db273e5f037fe09f0f2b016bed1ac2f09225051 GIT binary patch literal 2423 zcmV--35fPWT4*^jL0KkKS)B#Q;Q$>iTL1tM00DpK005)};13^Hw(e7FNllqkvr+er zYEmPUNwZEI?{0*&6Dh4y&|e8n5ZP5sOaxMo8d9l=fXM;s0gy;SPb#U2fQpBbN_kY% z0U}Harce}xZLA){8(hB|J}zI5$N~YyxJn9N2AP!>JZfiom|a0Aou@CWu1PKXeaSlZ z`nbC1T>03Q9FWk%fv8FjJ>`h2=BZ9rP-!O&X|(v!AsmFW~WA>E7?5N5WJpi3s!i zgls<3QE6ySjTuUeBudc-IGljx$QK3B5V<)d7z`*AV=9!D$)geYB9Jlq{JV7Xd$zr| zijq~n>$dNL;XWjQxwgE;%Z(3%gH$y}8fBqb85%4bidqGxnuKU&RZqNl@@#E#La}2* z7}k%t_+C;5A7*FUneOZq3adLajlyWwi-!XWr-l3!7+^LE*z_$U7bIvRSEfCUa-NitlaO}yt*CZ# zOa?sh)1L<%%qLQj98rndEjTGH1YmK?M6T>LV*$31a3Hi|Ya44A4-nWZW6-cMat#11 z;-O5)y!YPkT2^N08CZ8hMW-JFXWuQVvNA?smJXEA4MC__#M>L%vkHC(UxR_<>Ixb_ z;*_c$6G^e?*cjcUS~NkclHe6DdZe6(y^}piHuJJi1j$@68fiIrV<$*{;&Bg_WfcXV z23M7^I1Whiuwr*<#-iZFaA;~ZBSE8NP+&b9UpL_vXtZ2~ zxgRyNS-nzpGFV-ZfeH?BNvUvfxw=ZAQ9_{QVZouOGXq75|!b=5m1&qK$u%jjVc<_93;aWEjP6lpFD7Y2sszLF`aw3i8c zgGxx$maSBmg$3_j<9b~=?|GQ>trtcB9nmV&H6yg;5es0@-sxqYBzXq04nr2BQqZ)T z5h#XmNV%riV$!b>zDy29ha*>mP#U+%4hK`6l{9!+p?AOpgutFcPhhdazhd7do{c;` z3Of3&u(ZLt-uJ(S)`ZX@mij)AUo>Wq5;{sEj3G2LX)8q8cR*QlHw~cH zCT3GvlZ|Tm*pWYS_8wk7A}F3CxK0;tS^xCXxIggdQ19qwQL8rFJ1}beiDr`49UU=3N4HJgmB52DV0!Kzk#Ug0AO2wg}!cHU^Yf7|B zX<{G{0Yi!ffO@12HqdMU>}<41qa)=g4Mt2Y7J^c;nMoL;0RD@VU02a}uC?xbNZ9oG+5FC6(Tz-$nGCn{x z@P?0}Nc#jze_>qbycZ}@f+qHYL_MhC%UcnW5!kLOiezNj!)RXp{8Ish`si1 z)>)mbt*4UXp{}vq4YbokV@&WrM#R|%>J+Amq}&fzN(y0A=S@QQubuR8Ms61SC|^NU z+P%SZJYC*BL4oQ~-&kcgQ~`sAOAAneJ%J)J_HB?eFRG>964xv=D^47<%WmuA>@|&@ zWuF*fl?oLme9j$Ipz+)lh{RZmE1-bvw5%Vqe2K*eI)KJl7)A37a)dJi%p)i-6lqPO4N)8XAEl*9ItaDbVQ;l)-k**E;>JV(8 z;?PKNjzp6_M+=_^V{Z27D0CH>7lqSosf6#QuQW=++YSkBP*`F{h~>|hK01)WkRs%c z7cs7Fpc`n^am;E7P)edClpvI-RFITJjLeh}Q$1EDpV|!vxT4AxHmwIHju{0B3i4Ej zGM&fLNQk(BArzvBrx2n+rc@cxkZfUZf;2XgzJ$6P7<7upK!X_3Ok+mQ>N*VN&8t^; zeRhM%9;PQv3`GN;eNm-Pw@-HWj4ra3Q&ZAkCik}!_IJ6>Ciuo`aRM)| z2j}fxFiVDCx+^DptzTW;U7c@YYNqR|@Z*jodFhMx>auU%vv`!0q@@J3!4k$An8mBdTcsXc0-N4>xccTKu^6dg)4SEoUFyEW zn3N$mmex)vM5rA?QwEm4jH%1o&ZM)094I%NSZ8t7GD>7MCnK93MMQWR-PEwUv8ys6 zM&;ey4B`x9I|$*NdfVx#I$S0DWASfo_c<-mk;m2aS+{pMAG&9{u4KHYF?`he=1E&s zsR&3yOmY#D5&~N3wE1_U@NBVpcQEopWu`+&F-nl{t04)>>a|S42}*+{LFjJP3YUtd zyA2~xO&HP${qG!yXn8d*EjP%X-0DXjWUVT{8@PdP84i?i-@JOfN5WgqCJAAPqluI1Uewa?=`?E1=BH%bnCjL%~TiKh6UX8Sx37rtv@^V!%A(+g1BI+FKU%FlQ|5ev>iLFV&G zVd$O+7eke3$-XS)z^Al$R&+6RJnryw9ry@74v1sO1cUS>oL+$xc&`q7e)+29PMz7` zdvN>8COyi_DPXGvD?;>wvy)xdB~+7BT@6o3(BzE|ERse;t43r-Fxo0hAcc0*74ji{MN6Y2#mKy{yiwHo-d~pt;Tom(`Y!4ZyP8U_`<$4j??~YWG#3gGfVh@mt zEtt7^qlyE0h#~W3TH9IG%94BZljQU0%d% z7OL!C83=(P(~JWm!bJl|z<5K!t7nM9LMxGyc@7;46rtcAd-hp-ep~ZB9K~ZkD#}+v zR+d|?%sxUqgl$wMO$`m*O^lKA$D!;TOeSC!(IX9>7!c#*5(aA9>8y2>_-J2Te4qUYpj8ym)ar!LLm< zrtwU8uIsg;6{KA%ioO&;`tuM@^!ro6;WvIXT7@r7)jQN%XcmAyt(*el2~O!1-XJjP zMimYK8?`G$F4lz+ySfItJ_JDc;4U7})Ph6LwKUWn({(h}9W*yY*HkxmT{K)1fE%n@ zlt8;u)drhJ(GuDK#LWKHY8R8 zG$KYS;fP=&9T5cg0Wd)6-iKDIR+KA5;&B2HgcOVD)Z@MD;}7*IKI>;-zpY6(~XkKq*>KpfZJj4=ljArK2~453s241C{=7k^(}crMbT!pQl* zWKiyG|1#?vqIU_p2^8LJqRitX@FXs zDh#^D;FmP*^&US}WsW+mGa-}Mh6scrRAXt<)Q>c`C!(>?5L*}oaysL0uuNIZ#QjU) z*IGoHa%pJ-dM4E{^F2x;md2Z6>d{yU_XABj>JYbf)K-F!w(~J%b>vWx3XZ9<{M!%* zHqe{;?#u|=8=(Tna&Q4<#34z@j6oHR8GbEf$MkEI1tp3DX&c1HZ?C(1lk;Gj*jGqH zTS?ol%MFPB@db`5B^Br}fMN{%?D6Zk`!rM9Z-J+Uc$Gw-%!I*mf{8%I)^J!)KoK#ZPincdwPkydtQ^(S%!>OwMzS z&W~F6!@!hLSbRbhP)Y!#v~W~}QveEsAP|xf+XC*QR9?iApj3Bm_GWAMNA7+`tO95- zD4HB-8YGM>HdwgyP|!iF1RGLnd@Ti=sM9aqE_rt0GkR|iv)j{1(JrTQ)6>s&1gr?X z7CF7zr*+GB&y^Usl&_nGZ;*L;M2SXcqQ0(Xb{k{abzfn)+3ioWL{xjBtR(`OpwyP5 zN(9aF<-Ir{#Yky(e78 z@r?ps1^1OvK!a$ZtH6{u0r6OeL+034MqilsZ}TFbsjntvXA}slNJnz9BSSSQ1jJ$l?js>BG@J&yMS!6vubo98%g!}d zCR3b8&=Y;Qgm?hdVNyMwVbJ4AQAVQRp8AtM3}rc zP$x+6@J?R5AV7)8`){uW=Rk)n7t-+ot3KBIJZT4JVJDY~+vNkw>g7FDcg_)H`9M}3 zm+u|Zw>J7uU(5`DA_^=T2yXpAre)qd^l4j8I}tAfZa$c&$8aO(3t7SQ5)9<0eFV=Y_6KGU%>CXtU)0 zEP{yh3R6RoZxOgY+Kj_Fn6VVKXA;e=_@(c5d@-*-2vWRSym>{t1e1=f4k2HS5sN#g lNjum01{84NhDmV$RS(*s_y_3~{Zv2V?ntK!5)q=vKC literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..4b46e816e295d7fbb89be6f89a997a413f44954d GIT binary patch literal 2043 zcmVdVnIR{i-OW#4tpu z2C*b4UrT!MIzSe6!e$TY{-#5oP?k@Pd;l2uH zp-`MSh_F1v&@ymU+=1^WR1@V-tZF?DFRU#Jlq(S{Z^efU?fqut+^NIi>~u{6$^oJ3 z_z02=NQ(z$dT%q{SaD0tiAl*qJG?nL=L3RcHqOhoZo}j0@p}n{CU7zpNE*1{8lNh3 zctGM>c)QUKC7Kg7VdWOId2vM>fQmaK65>I4fr4SM!&lr7KUaXVkT1bU%m~J47y*jv z{HIgO^!e+>@y$@?_2*=7RO}RC(*`2yNvc#%@GO2IaTGpXiS8lbeu9G+P zFD;!$j{Qk)gCuqux`wM|$St7~1*a3M;Sah2+qAs%D=t}E$U@MD#smdtvkLI6c+Vl> zdBF|v2VhwQdcpb^kcCPgC4qyE1tYLo5N#ktvyVmcoe}L-rRN?z+3d_d<$>=MqsY#0 zeDbFC%yl8B!S@3V4MSAJvU}ku@HCC1;ovx!;jNbmAX~-iK3C_WCKq;iJufbynRB^v zCw;1qU_Ixdk2)TN?uM}IJ@+^Z~AWUZp9|6W1Pl18yUTHo>(EL7v5l7CfF ziRzZZu_Nk$c%CSSzK+OjFRCi_5CaUL1(^~v5V_G51ff+Cj2j(>)=oEw6$C}=Mh!-sv-9C)ED9N=hLI~`XzvIkm=K>6ywEPyx_{GdB`MNhBAkP;s94-L=e0wNn`@#6DPp|ex54x=TPg>_(dQPfCRA?ScP&B2tWuyq{a&1(8dUnLJAN< zl0iZw3RvSRJA{)04^dD)9wX=-;m0n{42Mi*0|-V_0$^-7Tp?vJnN1Q9vbG$FgaY*s zL1@lZ1Ari%@J~ffoZw+Q6c7}YkT@}e0MY7)z9*jGVg{xl2$`7}pvoBELS<0uiYLzn zJyWMnuV7?}iAjniLYSCS7{&yqh!%*M6vo+7q7ocq3B*Ajo+6x14-ObiIK~&Ggi?k~ zY$kp563kErHZqk`jtz{6RVNS?>Hc;4zY+2G-^Q;37sJj-*H6-OxA6jf&m68qATerj zmpc>K3m?-m59>XteBH3{ea*>-f(z+UYn8h}jDn%<9-PLtv4v(uYQ?E`F$cFSjv_iT z4HD${vk1HvG$?3UB=6RHzRl+(SO-Z1WKB(7kONa4Hb@;;pmcj6^j;&^WONTMoA$Qu zEwoV$V3t^PG-NbM6d7eNu*@k%lTGz?!cU_f(Yq=)UgC+hBbjk1$7FDZW)p?ppga`y1Jp!7FZOnytUg;Rb%U`+SksrEJmdv zG$EoKNfkKlWqa6Z<1%Kx-R+R?FulJbjuMs2xvzMx=V{k6$(9 z5#`bI&E`7U7??iP1m?qs^DkGN^T;_B$5Wf57&CDG=zyq>28 zO(mUWq*jE@kXaTK7#5L z+qgZ>T6Suj?yWMw9d8e*zW~@|2Q{R?5 zf_w)2e3=>zI*=raQF~#0;Z%nne7AoeNY>HFGF*t+e+SS8-|q*+F-Vqtc*NPa-Y8-VvLz~TS^ literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..a328b067b6f4065da417b1419ff866010112a027 GIT binary patch literal 2003 zcmV;^2Q2tPT4*^jL0KkKS$I&y=Kv4DTL1tM00DpK004r4U<^P$KmY(NQ|VegwO@JfHnNFsM0j5FH#ZL z+}pePj*dN(>ey`8RlR!=TygccrglxYGR`p?$5gveW{*?FJ1lYtP8rf#RhOB$c;f|A znca5xa88$2vTMQHc+geOjvmX>=1vgjlE^KVp7Jib`rT35Wp4BQJZ zDvf5dq*VvgX$ElTVY?%526kt39i)Rg_7!Jmf$ShZd)^)uQ?5}DD6pS>+}L`A_wQ&O zU>`CSizJ!5L)3>-YI@DgHC$oi#8TX!G9Mk~mGJkhj{{*&t=P^@BaoO#><1`rSfEhP z1?C3D$|1)kazeK}WoCnwmnVSSVF6>dxG;?kJeBj2pHFN{#sr>jFTxR-RceA_RxlR#67!^u~Qx79!4e zY3jk#0KuNMVG9(Q!J`YSc6|$}NOewv^v(e3+LlZsxLM8yEm@*YtT#tahHQ7ZSzs=& zblZaC3>ob3+Z7EiAiD74g|TFLRHT;<<5etfWEly(tuQ2V>}PAFtBG_R-`O z;NhIFoITz$q%lEr)JY_83|#|P-Z0~fmS(lIU%gW7^2~K%i|b+L;Sg7-Z=YItSYs}w z2iKA_Vr$nfyLSjT*?uN)&kGm2X>EA-m`q^18@b6Me1&d`yw>j9w_MM6a^9X^#1|d~ z1+plWVcJw{rl;2U-&UHOq^+IaWS>|hdpsN26Ul^B$KZfORugDz-5s1Wbd#AAP#PCK zwR3IYWZBC~y7!U=5PL?|hht{!Gk0?}SfXtq-uD9KYl}(Tx00WmPuhGig7(Wja-{5x z<(kQ3weKd66Clgv1bvXh$ETZIL(#i|7kFf0G2l$1Sm=9C}g_nOa;#*|AIqAIhQOxFiSNr-mFy7@g@zkcSvT4Ttciu z2Y@6e>|h1yr)gN()Ynr?D++Bk(&D0cwSzh@rb-jIa=gMN^NYrf=|;}lw`7tEyiIKUcv|g&?x9<7i5c|Xms0auQpW}raN!NF9H%sA zUpDIk+TW$r)g+oxXKM@+kXtQ2>xUq(2=P=|T+g4Pp2IIO@ zP8e47kSB;q=7M#@hp0gfSQi^Y_|6sA%tEx?f%M6kVqB=AWlnrk~@-P(vEJ_C+A z;waxswFpZp+C1C>XR&~=t$<~_vU-BRSr#5b5_2KU;#*2ZQSr*)q*|&uDO_oe(-mi< zG|O8GW&9M;P1}Z0$|sdbj7KT6B+?QXHUy%KVxtkFGHC)zl!y$HY*Q&TmNghDKvZHP ziv)Zmg!uqF-tyco70C$6=oHFAIkXC8_13(tG0p4IgG_~XkvPZ~woJpfVN5d_Teu`D zTWp7-3kE7EF@q3XmZqB;O-7QYMk+MSky#|uNU>R!GbKtGsiw@PV=L( zfrCji2?0hXQLz!B3^tQI2m+gkxD$}IZUJL5&TGbk)+8Vnm;lW0zJv(GpA9HL80Hb*7 zd+Pa5a}Ho#s*6k;y!7v(omRF&mY!c{H4 zJ9|KwDAN;x-bXOItgNdIw>GYn;A;~E8RKRGugADw8tgkXbFP*(#iz~VkFud+16Xm^ zsh4$~>Xg`xtZ7WbO9GbP4vX4Z>_V$SUW*MNLF>L#Hiq$?dAX zVm_=LeNCa*F&^s9RpBiGL~F)0Mdta8Vj(W`UApVJN%t0Tf{IS7s@8VLn7kz@K}!#h zFL4lwd31;fgjvl)3O<=n*22?=!XIN@5Q~LSxgD8koxK)r_~~n^hhSEgjJ30-v*PuM z8+TW8hND+Tfui?&9})sUs!u?X)emnj_>aZaxV#gIwVy)bhhBG7UORsJ3Rh{dZ&n^?Y8ydoONhaO{;VzhGUc8vA z5KgmAWYuZsZO1~yu(6A~CamPucxhwjn0B}-OdCU@0U680`;0wD7zmTooYW!qA236t lxpvbG`I24KhPPPjRm@Gvyeoqr#ztC~2ku1VloL0IQTJkE;D{ zr{9xQI__SAtX#X^%;=2odWhj9 zUE6!PjRc(S)+5HpFH30x1&7k>H%F_&RB=@t(bO)gs8GD{zc_okgaj;(fO>dYAE#=M z!T^1N2&v{DSic)&4&B1nyRJ71@NU;rUbQtX+_ZoPh#Wxi1C0Qc1)IXIq)>0GsC7X? z?hjBeI18((z!S*3(9J_sj63bQYM8{l552@QlsFz#y%^rmy}UzNDGbbjJsIJF`;dIZ zbHKsAz%XaJ9X3Z**4aEsF9*7AnV@VG389Q+%i&GnKq-TWdF?JOVEaw$<#k*_TfXli zwH4{l)46i7g2Dm!uxQY_>kRAKD?JE$FFH3fXxhZ%HqBvItEH0GTwYStM5#ZB3wg%wdv4d4%?_l8};#tw|zEfFy!H;rXO=0kh{|NhYc<^mKk)#B`+kwd82PpXBOmlYs{u} z2X?xM=cu+c**2~)(DJA+?24x_CwL^LJ%$-%bfuh7-(8`)o_j~ZMU;4+&sRKp`)!w7GW!C}U*<}=q_ z2?FVEhDFl~?;3RgeGzvbTkN9uZ>GRyjip3UO9((BNv~o|yHj10b<*`feG)`_!b8#S z1KemrNDo`meB7@qsc0gCI8K!alNjs{We!b}Im?%q7Ii>;K^_L10QBSFP4d#i#R`|K z5$?6^Y@sAdZVPS(%*_zX95zhR1E{MK`?Un|Xh3YDcrQlG4X5+ zO9)6R5+tb1W}3Y!z+Q>n7nktkjybi@&mK~sk|t_qDK#51#*HbY)W%Y5S(MEdG@B;L zQbnnoMj}H?CX}-?B`Ic7lNKf@)XFk6GX$k3ltfiYMK0}aT~}MCg?NTDx|$j#N@(SX zLn-U4gxuC%7)$MIxuen*w)yTZB=?eT84NuH*xh#5&zkv9kRU=rcVtY8N=S+%q@p5{ zl9H0e60Y{OU0>Pt^gZ}~51+&Md{{7Cm?0$NBp2^h9J{<_bAD}3Lzi~-q_Bc;EljdH zGf9|_GzU889(lU<_`YM_J%@wXc25NG_DKk!1c;|QGi5qu)5P2J?sx1yc6SQ*nZ7&e zBcXwF<2xFjyg`h$aC5_}H<&kd-9=N`c9H5?pipdaBvl(B2!&OHaCAj;baIScwx|~& zyLOBVtkWg#3{h#_CE1E1Zx@LcksKDiMHF(1^PbjbtRmM~mkm29C@J#x+sok>J2&qG z(&JD{wK~>w*~tv6?`(ISx!nnB^0g~jn35#IK;RF9@H|mR%(C`6SkUdqUAb?X#;UsK zQ}fnD2Fobvgq=K55FIR|rI=A?Ogt^@vNqVfRn&=>xgF9!U8>|R+yPe^F}Aibc%KrGgpR>XFpmque=tk(h7nql-G&p z?@wD~(K>YajN(^lSiCy)RjU&a75Gd3v g;o=7a+MC7v-X3+%{kPxRzis~)az!{$kd=qLM282B{Qv*} literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info new file mode 100644 index 00000000..89b5afa8 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info @@ -0,0 +1 @@ +2019-01-13 23:55 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info new file mode 100644 index 00000000..beb9b901 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..2eed48e98874f56210457c40c45916cb9269a809 GIT binary patch literal 2810 zcmVOWO@~R^M)btpYQxgHH;R2;SAPF@j@-O38FiyWVN%UnQ7dV9|xN`4!oB8>r?TxanOD z8XAyt;Yhj_EZ}HfRTF#0C8ps@meZx#LA~48Yj<63I(h2PeJ?=N zlGs~m-l}-RQ_VC#T)D9oht`^F$y)9Ra0$t+)_*}X}v^S>9)@wsolpoiMklDvmiS6mb?rXCv-%@x4LGv_O5G!I2i(u|gR zVj7U`BEYN$BF91% z<4j7bd4)wmuX^gm_%Nim~BL-4Z2t05WdBVa^1L!*((txDRKd;f}*+XrXV^T9l)9$(-bMDnf-(Pbvt0|34I9&Z+Wy#k@+d z+Znmy^9WdUkONf30<8v;0TL-1Y6T8ZZFFdqUiCUQPWN&$O2{vde@IAGA_}BPszgen zAwNPNLJMyqPX^gOoEIYCx%B&!6*5V8YlOxwbt%|)x!hI^W@b#s3!?1H7UPlHj^12* z2@~NWJ%oef=Oj#V=QvP0`UDT5R5(RqpUove;j^A4Na%L1T$CzFyV7;iq*Oa-HQvI7 zB+jF1M8;u7Mq!c+c41R>gqfU#hk!@ER{_MHK0X`@{C$w&z-bU6)C}e%V2tgqBIg>g zYq?a&%o7=gOq3FoD@#NzkRYrn2xLHfDxvYg6T&}u^7taZUY%dKG8AbRp~42Gi&5f2 z1bPG$YFeyh_Y*Ssk$}(q$!vkAVRHB<0!9!>_O?L{!jLg(#l-U8~2x$BH`K@=@Ps*$EFXjEc$9j`T>e%Z_ zJ%}5u$ELd%S*Iuzg5kiM2$Y<+I-YrxS{)pV zMxScqQ4;~Z%Ij9NYfWO5sufq~Dks-NZA z1fqopGZHXMNc)Xwj#3Ca6cYJvwM{pB!=@%YD(2zKS&bu{Y2&`HN}*aD5LOA)_%zr- z*}5{zzf+(|S7z&_`tp-Vt`9b}%PSC?C$Vgvc4#UhtqAjd*IHOZqM?zaH1{Ok<>A8` zVGc7!E3#;lNWoLea(=T3zI*Au)~BEy#ddM+XF;`U+JK~P9?)t`IS?l-T?1+nt!wUV za_*uzdF*E0%5TuOg}0+GeVS@O&O>7Xl5@MjDU&*a7j7XO6DoN(upu2!b8I`M(l)wi zwF02R%vwlrSiGje96N3<62OpU;KH5Y+*lM4tD0uwB-qZk4SIAA6#Zdr9FHKCxO2vk zZ3Gf6gJ~2|tUY=H|21IQ16qU&p6FDO zjOFGCowBzDCM%QLv+b&w@jLVhve6I0kx(OjI)RX%5RoLJK?ELt-kIHMWTD2`pL8_mE?bF5;SCDMSbj8`L;*4it3J<;OQ)OW76g>8|Dd#5)nX`h%iVb_?Q=>M1X9#BM?fep{`V~ zy}6k%P)F1birrVPpPLN>=be<}>UMi}8zFciw(Q#?hl#C0pAsJI)@I#ly3a646;ww~ zs(Z>GJeF-CV?_5iwpa;y5Ri{E010fV?7O$2a>V-V_V-k5vXotW+nI{#+1^)`yxMLX zcU?J{Mp_CCsV<5mNaJg`t0tyJi3`jrj502Tp4HI+rDc~hOYXe&)?(d;8O_=5-&COn z1F2>{O$hPztMm)s?# zn_~BsJqyKa4AkwHY}1l*=y{YxzSzjz2dH(6akEz{W7hU#gD-QragMcdm6G(0Qs|LF zkj~iXO00gPiKC`s*oA^YTk4khxu!8@F}pTgTubed3Ap05k%yOiDdjQ9R^HoXwW?Gn za;;#%RpqV4-I%Alc#aQjSjm9!R-YxAfHZn+o^vnk4OF#iX}4{Dl0OL_V16KfNgu-Q MNT&)C3LqraK(s(0F#rGn literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..54e50cfeffaf9222eecd731174f6978de3aec67c GIT binary patch literal 2383 zcmV-V39$A;T4*^jL0KkKSvo1_@Bkf7+W-I%00DpK005)};12veeD$qh(QRpMrANk2 z*jFHc`|Y0I^B;Y;Dc^d>N!Bfa!&Ar^RZL6-lAdIms!W&&sj^6v!~rSk5T2@urUE3U zWYtu}z(l4gnyQ$XeP0M_w6EeMe=(jPt=wvuMFpY!Pt4VuG@2h?6}%1qs;1W;l+>%ydft_zrRgMlejGD0qB_h1cMGC6|^Ib9q(Dt>@N#H`JR2@DpuVSmun{xO_zjYH|##>Gub~D74zTq4&#K4RjPP?J5hQcNA4G_j8Fla4x zQ!HU*5SElg@-{=sW(J~CcwXIzX*=DQUi;VCzWS6lws~_sm|oE^!{Y@wZ$qSDarAb! zpb^tzgiRZGx~>?MSjHrSeNL1;2sH+rkR*CB!4Tnam&tfTJp?1Rdr99jzPL*y7zOuS z-&uo)2f2mUwjMptEQby}f=Xh*e%R^Y(LDfzY8?)`g`<%g7S_(Z3Rn{Yq+^J@)D2dE z!$y~-<^5~uP3CPDw%48mEG9T~)27H*l0`7g0|KNO;B(QO(R6k&GDR*CoeeAufglv6 zi`TDk2}<^uoj}bUM~QZt-0!|S%q34{+Sm)OPAGMNJKklB7AQr5xn&>g-^d zq#$5#NQNVKA$@39ya%9Pv~a%ms?WAvwjpI6rpIM&m-lsW z(m2Z;#znWu=T2*>u!~`4#3B%r;L$Kbg5IWa4#cC1J|79^N&~`7=iJ>IJ(rK!EVDC^ zp+c%QofQ+{nsAzLTYH$;G#dLzbMO)Z!4JLQ{6UB;vdXXxgdj31i6TNlA&@CRLTpV2 zH9)}wV89qKVnZ0kjHJ>UV#Sz(3~XS;V@kkO@jw&vFaYa_cfFxdEuvPS+d_d+^J-P1 z+U}a@Hq>ZTG}^Q(6(T_nK_g8l>N5l}!h^r11qlE^kyRuS859tILV>CP6nBcg7wQo6 z>MY?=D@a$A61U}b;iwwe5f~c7g}|jONth-QQmv&S>WV)EMDhv-nM(DmDJa6%p6c{? zDau+Cl%aMlp@w3zW*9*j4cj9kJOtvH2wXtjpm5@W5rlxXqCz0YfGBhn6V2uz7$?!t zG(s1{j<4nH6R&;(BrMDT&{r-|WJ zga|-sL)}eueO?{i4Jeudg$BlAO(;S|2q+}j!HprINJvO1B_kUFh#oAol3IZXJvfMT zSOMeEI#6n)NdhV1x;kj;r721^Lv+_lu7GhF3|NN%ZK(uwr6^QfFmM(^Jw*WXiaIIY zn*o!sNJ!GC(#VM}DrF&RG$evXwSvPb5ndR?01}Xf0$|xS6u~B7V2oIhj2P2HO(AFD zCNOD=K!Su&L_kQCB7_tZ8)Hd~8f<84NeH4)X{MMA8UQsU0C=GX$}|lYz2wg*C@6^= z7!3%>#xP<`V{8VJW-CTw6mlWNR7fO{Q4%0oh8coQwwnY2pa>5i@%gG7hQNzHh$B#6 z9aO%4Lm7i}GrG`n7$>VZD_q;}eq@CxdgY}mB{lU3f%hH`gz5s6RUaGDUVLIwb$qD0 zsO%dM<)A9Y<%;lzX~YB~i_bzgQ!vr)Lpe+cvLP3W&7=c^z2ZWO5os6Mgj|r}%*E99Wt@Ck!DQpvtBR^9G_h>-<+N-vE7!{?$e^)&71V9wJi}{> zCr+WDT6HxqD1{k=nVKeMl1WKGOEop<%Ac6)N0 zVL^kr??if+sTJQWvL!HT->@i>14G30hLUW7qHR&Nrs=p-v!Hay5izdG!!8>0+_Lh? z>$3N`F04JU^^~QX}eX3fs3y*jg^Z*rES@2 zT7sU;%rrET8*RZ0a?C4A=GxiZEWFh=BjeBpW(r8w#8QK?1!WEj<#=KobCp|H!#X)4 zRla9UBcRTLH}8tmWC= zT?Nw>R_)qh@694|aK7pKW+i1fbG`Dup}o;DQUx9}(*9s*ATdUGGBM z_6;V44@{FVYn&>oySsbc&$wN&o&=91JQuNf1h-|DB1sVxIU)>&-O$rb4wc#$rJnH0 z7J5T|^uMpS!^L%@%5Hlu@!|uwpOK4;AR19a)MuYs_)rY(m{2{tptGbq+?SNBN2+l7 zlYaGr@h-H2cZHqkxI!JlU#yE%-5{r*)MoSqf%Q)>`9k&E?-+qCErx4 z5(&b=2gCw(3ab-V-;PJBk6e;Tb@m1naN%XYSEuz);-9eoLH?=!7ji{7P>?z)=kQsr BPCNhr literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..ed0e14becdb69a5623ce0372ab89eb4964ad69f5 GIT binary patch literal 2509 zcmV;;2{QIVT4*^jL0KkKS?uP*!2lchTL1tM00DpK005)};19jDdv^M1m9r*Y%?;di zqDT@U-l5Ks*NLA!&B2il~nXhfvSE?M5>sW6w@OU6A(&zLQJYmm-14TP)x@o)F#_$Vk5&CdUYm2x1&7v$Myd z({iXmK|t!)iF&r;@vh?YjuJMzanoAIx*1EFe!g~hvTkR63(f9cUcIS860EjwmiqJ% zUt*)$hZ{!hTbt+?7s}J>x7TC>wi+VAiM1yI^dOP(BnLis2*^mZPq;24fcR2y`uCIE z@3Ce!u8!AZg2=EqZeawBcue#nhXDt;b5s%Hk{@(Pf)$m4M^UQy3gR6M1o(Kc6TvTV z-pihQ+rIsqdzZ_c%V&du&y9DFek7CcV(UHR>!8~Axe`ir)(~JpgprYTpq-GDy%*Fm za9@?;L|+uQr{2s|pH6&EXFTCFoPO_}qW$+2T0Zdo4@oPirX8+<(nD9C5)Ap7k-r8o zB94IygVuT{AqCg1%xqu>%K9)6s_%W0A7%(|auW7j_j}pQF08_d-n}))Z(j8$z<7ha z@!|J`>ClPuqtZya0nlSqI|xpPL_J36Rm?FUSp|MqtU%fZgHm#NKIH86Gu)m#sm;+W zs{7;YI%+Ds(LRTy5?52FytFYr9(a%>BNIXS z4lh7(9suio$H%?y`(Ex&cOmfr4jp?Y{voyJ8RNb%-F@9ikYW(xa;L^jetNZRk_H=*V!Qypo}LNr)L!@Rz4gCk&xzY z87?&!fe_|n$R~LiNu2b{(=M8PXGyfrlf&1XWKW~?cN~3$GY$tM%M!^XBsT$mD17{n zz;xYgAjl<7k3@jIP~vFm&2xFUXTsqSBts$(Jq?!+ym!RRC%8w#cnht`A2DKF9L!H4 zKG_N;0U$5Qqr&(kM-PaWK!8ZXEIOwUi0~cjlkB&hS}$TM^;&2TW>1BVSEOH)v^txBzER;5Ph?u)u>sA)>I zC~2vwr8PASx(0#I&;kU4DL+CoD3SuBiUZ+6Y3u>-dYxU+-3MPqC{QRBDg|Jb=ZJYj z!VBP3DOvzk^To79p;m!fAl9XI05@GLP%BW=L;yG_A=6l+z~wM8#OPX7Eh|K!TG7J{ zOG{~5*MMXMr8bllUjbA(vlI?K6+9H)HMCN?ni`{BHFQ9>g&L?1Ew zC^8~i6e@&(xHuRF!~{4*Llb7xXi#M;n1um_C>RX@FkB2#!NJ9m8d8;E0WJ;-;YwH# zP_!siQr3e}bf}@S0N}d~>}uB0Dt7_FipN)47J%GS1Tdzdsv7EQIv_>>A(w5S!epo64PO~Fh@Zq0=dtqN6WRHYQ5 zTIi;mridSlLWM-S8tNKS)`4kgQqt7EAcAm!20VCkfkM!>fo-irO*GYNQ(ZybrJ|af zrh-zYrl?#K5NShD@fBVU8S~;pu8gp5l0Fr5Nc;OKRjTi%ess0L^K)_w_sA4ULx+OR zOT$w0=013Ok(98mZ7{;%O;8rMhNOv<3Y;>-!bv1Cz@zfiNdZ)kt(l_N>BH#(i1_Fv zW*&%=4$qlg!qiTYvq_?o{R85=Sg#&mn&KV|Ihm|riEL*RBbdu%PMT4KV)k@|KWx*p zE*Xrmo?lIR$EgKXJqaH_N+VEyh^G^xTqAUkuFLXwY+Fg2i}QJwGQ`+r^BER~MlE=j zF!OAC6l8)Yn}nsLzZl=dcqhZsTS#RRW;hyQO-|x%EnW4 zPYjwLptMj%dyY0cjKK@2tT`Qj9{P0Lg$?xGS9U|YRJD&wzxRLJK3d7yHbTCFsF7B+}PK<*M();vG!-5vv_!`*vny~imEVD<8e_5)=CFh>g-Q*Kl(Vba&S8~`tY}Yy%&8%}jjgz&g7Ba;z89Vov&L(T zo7q;|Pt=cz*4Q>~X&zGMQFt(rrJ4;TNDx)2mF~d%c~RVwdyJ;TpJ9duRGfmV-c_%6 z-qUIC8kbRUQQSkY_ag4Oj@);mNQy4?nIj*D2s6Ttd4$qy zEL-k4eUVW1(HLoKS%@mc#$3o6DZ*}%?2H-?WF%&hx(?CNppru(yiAX7Ci@I^CuA?0 z1)kwxrD4l=QSQ~~VK!Y`>^&qsrSrTlb{00K$5*FXF0siWb?IA)jhK0-mSrd-nO&EM z1dv*Unqr9iH*#Inl7*ynGi)P>L6VpeNwJI^p^fv$tL!JpGm+)qy~>mjX}xn+3%yUL z*F1FT-fHj_Q_^waG#Wz*Gb>BH>ecBxD{}9|hk;RENK!uYzQxi>C9l9R(Zhw7+GqXm X58rrx0s2GzQ~WOEig2MJ+0BK5ZJoIE literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..1b2a3313656b4efed32051407d05fc5ee06cb569 GIT binary patch literal 2009 zcmV;~2PXJJT4*^jL0KkKS=cFGSpXaOTL1tM00DpK005)};0+v~Q>`mSHEnFy!S=?a z`Wkw6_uFtB3eeMRwV=KdnkE{mn3xG7dTMD@#K1)!s*N=;2qe-aCjC`UPynS<#T1}u z0wpq=B~--2@(0vW72>1(3x7o_p+5vcKGo{~3O}=RO?K8})_01TIg@Rn@*PPCb1}jz zC@OD{I(3!4FuH!NC~GR9Xr@#&x}~I7GKZe`dI6W?(J@7$3N1)DHU+{Hpmbc~hk*DP z0q7&q>)pk=X<6lKtXfo@@O$Gg5=&u*VTfbU>DdFp8_+m~1%PVcuTay7_<9jT;ViT5 zkGq;+W*aef^)fMrbnrrIT_VH~rQ(eWNTPsvvS0woZNo(xoid?RJ__FrxGY4A0Fh6hLn0lWF!QF*`|T07=cA8Y7n_R^+N?Xr$Ih2 zJ!W>THc{7-^7_2y0wIbp&k=1L4hMRj1BW;(5O6H= zMk0hS2@yT^8zuF~Ub8)Y)CZOf~Rbk(ul*Iv{w*V$3xzWDp&qmJh} z9nNveP(JQYn5MvdShnBW^h zK|}c#CR4Q?d0e`ubD`6ryF*QFw5|HM_t&I)_MOSloaMIDTV+7{ApCAp^!CxjwA$)h zs_weA5mOtfrwdN$JDqc-E7+9#e8PS6>)YVyL_o02vJ4cGp#;nzLplitB24TU2tf>x zffa0QmN8`ZDn5BB?dj0c1pD05G9Zl1pJo1*Md)Dd4$6 zdhsR~IANI>W>y)H$(W3(mJy0&;CXo?%kW~D#gdbxqu(mQbD0)plCsMs8H|G%_F9;x z7KxFH2vVjHh?xa+koU=Cy?gG%b*)ynTP6%(ZKm53D=Ze;)vDI))5(pb(-;kyB$7lZ zNl2hbF^ul(t~YAidEHlYbQ;%pDb72cS9Y5%-sRO@+b=?|oc_FdKR{R^{eNG5q3_V8 zh?Y|-4n9**(m}+@10)c{ z!(UKHM=r=+kT4Ee(h3}~IUYp(g!>p?3Uu7V0(4L~swKCW7$S!0o6td6k57`+X@)Ly z%^_By7D{CTxdM{FMs$o+_|7c~TT{Nfo2k8%F=5(OPlCZiE%DY=ZM^txg>>fiE%nQt znP;;wz_hTY=UlSkHzPu_B%&PzWi+39JT_re{O`K_-!;9tYdw#vgNN=l6FG#QsrGlD zFe0HSh`GzkuLyeSOP<=@-OTrUwAnPloDwc(`^TPVb#ASX7+v!vN?S91$-cyH_LgR? z5_~JfvF&YU-LuaXIdMCy7Vdf_`bF)KJGCLejXZLg(n`)_%BKCdl5HEN;Vp3HWO*)YE`7v0mOHANDB*gy zEaO|uMGvRgX5OecKv6Z`^*X-Y?&gIHuc*5xi8Xd!EX$QRr$A>L^WIV(JeEWY5s@A` z9DCl!WU&JwAmtCO_U-0AzJ79MA zZ`q#S-QTI8r!9;^OeANy=Js{&c&wZ@jCjnrTb;JOo#y?{@@y+ zC++i}O}<_P79(a1sL)yB$(hL>Js3rBq&7 zfwq0)=#ZrqSZbf7%qpk=YIs1Z01`BmlPJgpo~lI$r~qPV1i>&#q$#SS2+sFY`1AZ` ztGbB%`_f~PzR%1VopP&cA+yNQ=H{+}W0c)3qm_MKcrghIVI^r!%#*_#FWL}PruyMJ zY(qN>*Tj~ej`8wK*xqt)mTm>3%~c^B?x5kmY*&EW>Poqz z7YTcz(Y$W3@bDz1w(v!(#@DlPuGP{#c;)WbYVXE%zSe;F$?}t^L9^u`+4DZ{yViYQ z0VP}*K_K$?m7p1FRUK4zt#e)7gN*XW0;-;rmt(qMpcNl;b#yl=iAdY&s5`ibS?nF4dJjpn{etWZ8%p4tSu z$Fz; zr?d@FL^-^BiZ6(jB@2uHA(z#9u zE#Eau%z9IpMd`JXE=wp$cI1}g?u&d&HQP4n4E4ql3Xn*HB-s7~&)7+48N!J1+2e3g zz;_PUAPut(+tq4R&D*|`G||-?B>R}%zQ}|o(;*%rQ%TlE&cpKsyAAIwI6J^tWQrVE z1_=cRq*&#ZuePX&-Y(~XJwXmFt21}je0_6b3wm#B3TxDk@TI$iU?Aq5?F_CJci~>1 zm{12jK_e<>g6DGA~Px|(u2^GvAVN>VfjBpL>TSib>Q*ytrlWH; zxh_*O)UXb8!^Wrw+f-p?i|6} z4!z09UI-KXSCdtOleTIn{P1Ri3vQ&2O1)?kf)!rO3f`$cA905E;tyL zQ7Y?>Dp&6YKw(!^}GCS=TPfPT^_qQqi3DX@s5%(6v6q)QqtGYrISi%BJvpvb`( zBom1Oe90#r(!$n_f$?^>L=a0@3~EIf#1ewi%;~d@A{9!+MMeTtq3YW$4Pu#5WYiW* z7E@zmQ#O-GSc)vm6&p5LrcySP$s=T@QkhMfjFgc{VIp!qAv#7R}wt*}I% z%ELz^jN(NbjS@9kRkm*NBQh$P(==4aso$hbyh;K~h$ojeDSZ!@fFAID+HsM29G<-T zy}I{LI-Ym8&2AfF;@7+tZ0hW1Op}|^iw8Hz>U&u&I+eW7D$71lMzg$fgKAmKp<4S* zH{AJlMZ3WWT zOTJq>x6SMBD(#~g%-n^?F79xb4KdErxD1Ya5Ze|u-i_0bRI!TNk0P5xZp++o%nP2h zmur=R)^>+ixV8(V$fPNcVesglMiN1ga|6CqM;>dyM7#}S@ZyN(g%NUG7o3~~D_g4) zt~faO5ne)$_NC1pIC$oc((#oSF^II0y1hJsVhvPgd#Flp=}e~ zh1jNZZkwtgLQ04VG)WJNCr3`Y@{81Vvb!#^dWdzhEnQ3-n0sL$av(S0O2SM<=k}1N3g6Ewo&=jGR ASO5S3 literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..2de34263f691822e032266359e8abacfd1202956 GIT binary patch literal 1829 zcmV+=2io{TT4*^jL0KkKS&>~$H2@Eb+W-I%00DpK004r4U<+wejkIW0tyTA)!$W4P z;y~Lw=hBsFw#9B5Xqk$teyN}uo)9X41gDau)d8RqYE3jLO{maG>Zpw&gakrLnt%_7 z$4_=xW&X3}i<rOJsRJrq%Xe~{ zNNnIV!@US3k?ibQ4Mn^+y5*?G@G2|8=`WcC0vA0+Wm%52Vac~QYb=RVkew^GS*@qS zXJVEruCk|L-%CL__s7GpHN2k4qpcb_)eZxS34=Y3prR!JDu&MOTpgnUuAu>CoXvM8 zZq^}1>l!kF#>NfyJ~iqKrD(*UgTQj0GSEyobA}o3 zc+NcL$QC28X7{_h>8lr2)sg2fER2metw9CvkPJmjeV!)GdJ;1+hV^zOm+55+iS+~( zAnESPr$(U9P4PB(BS9c&zM9tx`DC%=Em}QPy)C+Dtp~8mf!FF&RMR}hc2cTkAz&z(EUNfAG!AK$q zRlshs;D!;(PBQm!EA@#xu|;>=LdpUKgQ!WoozB460QC=SfXPA{jO4r+1+c3}P_JKK zSmR>7qC`u(iip8O)#r9~Xfi)DESB^sy8U@a*KXh}^y*eN+d}(gK`XipW1A zf_#cN)TFZxi_3H8hN_I1x;)uOP0{AWCbM=#DBGi9j2j8&vmSQdSZ-At2trSz05zFd zCaM%>uU89iR7+CnvR!4XHy1oYcEr7t2NFZDR_bQL!IyD&LIYxSfCJ!B`XUZfML3oZ zx7KtC3E15daYLQjS_3R9y3;^mQA>hgPUK3vhUBCchi=9T!hmNT$( zglrj%7C8}%u6cQdAfB48P}^)*JlsmH?nVO3mWVVXN zl+!e1*)*ool8o6|EV888DUy>ivozBf+DNQYmMSGoi85wUl1URPNez;sW`asmkn$iL zRP`SjB_*Y!u;O8XRsy-}yFs?C)y*LSIxgA1ZYZ&nC1kNgQBp)rDU?!8wcfRF%cbXs z9&dMJ)A8F>E`6`M73}
3fjk&PkcayhI zDhUj3t7AKNnsXHC9axMpl45Rw@d1EQSdqhS~?)GuFdAV5s(K-UQ_@88RZL6- zRGY;_r8b}lR3lGSDUity93wT4MVJqJGUhCj8l?b&5=+#4}< z%EvoeFDXmhBUrbAqG}MX?hSj{7R>}_OQ~9Kko$Ovo?g3Ym9%ys-B>U}aQnq>PevTY zjkiXU)>uZ6P(47TW=(HN%*46p-I?C)U}Xe7-&i9?A*AKm66k|S zizuUO+%=}WPf*0Cab`IDI|pF=9r5RTob%(}&wNQXw=n6CnxImvMuRc-V&~G*>BxJ2 znTWVXmcE1u#+vAD7%c~i@H0ktS~WVJbCEMW;2o!(Pbh|E$X z;=8Hg*!Y<9rNv~tyW=vCK0WbrYC#T$2aPJU!1glbDB-`k4bBuVXebS*L&zv-Pk`Xl zLR!&_fM9*KHoK%g_ge$ad*4Hl&Lv~@BS0b+E`Hw?4|lSL0O>5=?_Sm4T-7I*!frU6*WrX+hX~{-ucgZYwX_jnbLKV&Q{F(Ge?@P26@pk#%e!f?8P8D&2aFqk+TZ$Tey; zXgLsAxIGO6te8{M=&K)I$;Y|HI)|VZibT8=o=Gn!SH2j@=!@@(ucxP{h;Q6dr8{SM zMK{~7IKLz7;pkvnjm6zF#|NS17oK*8pP+3(atg)8`7|bvp{Z3s?B^v{$IA)qkGoNJ zHu9+Ml7mi@?_rrvcKqF!?`eidvm|LkQXTO9jVGvilxp;!eZ$`#cxlnZGez>AQC<(p z^m_vm=$Jgm7ce#fu)saTb&%d_i>p|h%k$#4}C9q!m6qI#EMH-x?`{i7G@0RD~Hu89@s6 z$?DGc(t8=Jd$?yaIl_QYF*}%Jv$q%{mG;4|eNTrY6S7_1sT4c-s!*+kRh#0t2k
YEogI*TJqGTNC=0j zh#%iP6_1MJ4{&ff0Q;1!XhnX683o0Bp&C3oA*0qOA|@dx1Cdf3rL^I~0AP`La2^{2 zX3^PjH}~yPU?s|nZ~LI?9cXJE)el(Mf7p{*qj0dY$y!J~}WQ;Az$`BWdm0tu-or4hsg z55u|X-B~K zWliZ^91PR+r+%U50E-d*#IRjqcw=RD z4OqXt@k@GWzHnI`0s|yt1R)Ye8;fnE0NcIw0Z)(=Fok^=y?ShY2~Kb)LJ7nOLLP)F zJVd02QYQf^-xsUIEG;56IFW%4tR5fgwCE8Yb?xYoco3ZsPgMavIEqv{96sCFr2yp$ zT0jaf86nYN4?f1*A%)^6Eh$=sQ;54#!77IlrP~w?j|Dr|$Ea{uZHFas!9#WP)kF`t zBTj(dzJmmv_=yq}kHsoOLP5o3V6m3oaiGbX#h;;w540f&q>?`>tNVyJS36_ks@w2n zGn{}@l$1DBO|~5Dt6Ksw2UeKLQ2fR|mZ|M3OGcjK4l#kD7krrD0q`J(5{8%4r(b%< zg8lDb;PK06yxTUR7yJ5~8gHLNxg%BTr6h9vS zf4{r_eEPh2FcJ-YJl~LQ%mgK&*dZ!9A zXWw=gkaj})_R5(FGiHP~!JEE87Sg_OcI;|H@Q0!7snNeC2!mvEZABof(U zFGqs8cxzEm-0ZJBp4>SnrL|gSV*>AC>32pPjS&hurLRVVl-shz3c{+1XoJ+HeC)tj zj=ssnr1}nAZ(>6owLLb5dyl=Fl=k{7le*L$y|K(1dER#Ob1zqp7P_o*=S3#6GzuJ- zv4u40E&Iq$=L~JHe9i(-vqxpqliYUbMn+s-zR=S0hP>W$>&;=*Q;2%-2u9oVMV;*7jNZcd>2PETNFiPb13um>MB#8_ z2W71ZfrTA#<07>d9tQ-L!7yy8R)n{M##t-GdE@Jshdlh%-monTsKMgAebQKbu4{zQ zbQ5Kh80(L6d&-@jMf2C%piZm+s}49@5)>_s%=Rtd&OAy{+?+=aE?(-vwjXHn7o&BN zkKVZuJp8_%62&%+YjZFIa4S~q7$9YZ6C|V(c5cPD8S$QddmQJs5c}V4?(rprZ+RVO zDCE7qc@SGg*2Z%?!1C-lJB;$#+R8ACZQhMI?cI!Kn|Ike*<M=Aj_%UiCebwpJDX8);%DM^v0rYIo{`X@~0&9;hDJE zA3Sc(n1rn&ReQvTB&ZImR$@dp3rea6d1=*`ADh8cjBLewoY}+1Z`>4&skY0N2D*1V z?sqR})|KV)-;KW7d?S)V9$U6UzUNxvK~-W%!ab{be`dPA-<77FixMkLoUxa;x}SZ# z?>SY5s2BqyrDAh*2^|e}#B)z%2h?Qan>M|WpaThp%*dP={4hN`?ytS;W~^Q56mI*u zp{nW+eZ7%PPuwlZmx{K?wan7H%Q+N9^_WkAUYyq8>41n69K9O#JKooHpI><&XJ(kS z5kYgTH@&Skc8Y4bKwUSdM2YfKW%T(=yt7#D7s+-#e1Pp2E$0?5H;kEhWZtu6UY^@# z=YSaJF4oc-#0E{1fz87J=T|w!?omBAGNPe+d!D g%z%isG~33%YLCjJ>^~7-wMX!~k}1N3hoZ&+u(S%A;Q#;t literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..060dc19e4395e89daf6c3735f538a8ce2fa1697b GIT binary patch literal 2438 zcmV;133>KHT4*^jL0KkKSvo4$>i`{XTL1tM00DpK005)};17KC`+C;TY_83^cQxB< zm&GDjvXp3puJ4`q$JV`UTOsP+;m|txN@yl(s$yUyifxfqOiTn)Y?VzU$$*sfk~IBQ zMAHEhjY;}?s;P;9iAm0sCDXjXo8@^%d#)FJ;s{0N6mmzk*xArm}d8Cz@vInE3E}| zTOd^fvckg>IgU!)l1fXyVa0`kY3smr)L!;HCD52JX?guk{=1^FTP4!Nk zMI<8SS~F1wLHq~Kh>(fD2UnrN4vyEslvavjSAjnvjvs;95Hdgt1r1&RR14fl?)RDT zq_l$cD!1e@Gv}=Cgj`Xk!j3i+JG2()qbkY_2tZ)AE)4+?hC->3DM=zkr1B08mxCe> zpkyLcs%ypK&}o^7vL5%=dEqiLB9Tg9QR(vy>xhOGT<~U%*00x+>}ls$#`CMq2r#2c zC&(C3tu&7S=@jbpp{b4z(j% zGH48=S}KD2bvL^$ zN`pmr6Q_?o4ta-m^L#U<6de>$5*ZFU1JM@(W(P$&!0FKnx~3SvkWU1Gl2EIwr41*X zeY@#bc?&jkv<7o9wM`KXP*UN~3~U?>8eVg)jP4htswAS$oLnr7JD?%agbX~Sq;f{W zg^3C(q!XXGIh==0ylRAvp+Of53eZfRuRg zv)4i?6SBnP|nF z51<4O&1nMzLidkO@1R7%4VJbgrGeI~C}8CVCsdVkpj%oLpw`f#N{VsCYf6VmMlg|t z0NK9&3YP$65F}M30U-f|K@(6wNd!q1LPS7c#6bB`LW|HL*R28fj~`=;f>}sX&}@{c zLcK^4jFA|OIhS7e!vaVF76BRXffOK3bVtGC$esj->N07d`19a>4bXJ)K@`9g z4iQsm^9(5hsX1k*03eJB4M+wmMljHbVgyJ-LJkZFO%f&+6Gez&AvFl577~`i8#l9N z)k6UlI7I`AK?8?AT~iZxjRc60ny#qt)T9E4VmrQ5LB(5bV$=*qnjS+zFhU#P(7{vw zZ+91rR5|NUq(KJIgg}50*$gm7X_$-*78#j^4v>Q$>mmdvUMkHW4c%c8B(x%uNbvFW zA}`EP_#sNZ1u!Y$Ux&ul3U9&?>Z9HG9ysm=>XDBt#(+2^$8Oz*u33z{3GCl0YE^0X)HCz`zJO z2%>;Qkn<6Nh6o;Em|`Lz;vj$+!@jkB79R!=F5~=+z|-I{AD5H^=ufIn`k!n;(d_qk zo61{a=@x&dS=Z*+8%sW$#*6Rn#N@AkC*H^US&xwq^=VvaASeq&w5Wr;icD? zke*Im`+RqI4p{;+fgTFHAf~x9{>xwSOfdwX>A;e0Uj9V|Q{}P(lE{b6j7Y(m?kFZuNZ=V620&y(1PTGUCS4Ra zRfS`CQMB)GT|{%1l1OXHVvDzx=cu<5Gm2>b`z^?NHoO7!EomeaVw*p`ZEkN!l_nNxx12TNs}E>xO%dW zk{yKE=#e2{PVV6>yRiClJ>-&lik=wF-r;PJ17w?9B2; zREQ)Zp$Sl+LJ1^gAuj~TmPuQ;c}J(!zR!Ex-1(kam+ZW}-AmJ%=aO$M4R^b4QqNyJ zyG^{hjT|+8cGssVwcExMbENjo6YE#o@#g+bW0YyTwo|malvPgtpzHvl3LwOgqA~#> zFcL`Zp6A=X_Uy~P-I%PuzQ2395~Uk&&Rl;ywnuyQ`t~0CjxbL0zn$oxD?OyPB-l|A*JXq+Y z2?;8qsoF2HpJb_HJGyDg_iQHN1<_@~fr&xD1 zs;Nt(@u$QoD*ago1i}!|q3^eMcX{1?hACLFa_;Ko`w)I9`w!+1^-u7-k}1N3fzejK ESjxX&{r~^~ literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9bb5f51e8f04d2f3c5f160049fe9831e68595ff1 GIT binary patch literal 2508 zcmV;-2{ZOWT4*^jL0KkKSv+SLzF)$jZ;DoA}m=x0^6B7_hdPFCxrly7nR83R%l~6GP zl_!!ao{^wudq~9W8DoD&#rje6zmgw*T;bfd<6U)K*fV7A%tlv^9zC16g42#(XQlW% z7fv=-^9)WtJ?j!P&)7MUOna$gWHxb2#7SI{&Ly}?6DQ15R+L$bA(2V9;?QRMMPE^o zj2=wP#t+oRU)W}SXIs7AcgN2!qVL^GI2DC34yy$Yr8aBqHtX4 z2j+pIY5-+}LQOS2D##>40hK5`>}W03jrr2h^{e>dIq)8Mo?N3JhJU z@KaO=inHuK0OyY_r*(lX(R^4wH2d6s`1lb#>a6-ZY_A8RDa^#%wz6a9N2^qsi`9Gw z;QS}%X*L`6^m1G)EH-U=Dbga6=y2+u1F<0Qa!>^mI}rRw-2*~tL!W+Utn1syc_rZPt`rSW zW~dSwqrrIr0A*c}G2VShpKq#>4QIyDU}0(q)u+_R3jy?=C}B24P~u~QfavTLN<_2; z4#heLu^j`XebW5OKuQgPS0UD#r;#aG_MEA)U7xl^Zof>J=YfPh4 z11jpW2kRu7mD~g_E(Rtkvu{A98WZ6Tk5Gj?f#1I^d)Q;1GWPU+o34y-?Wf_)Tim%^%s^uMa_3bp7n2d*>PZ6zd zrpb)Kl4CMrTf3`ETWeO+Tdg!@hA)3;D@dYJzv_;mLXZ*DBoaw5P*AIgg6!^;Vefhl z%<0RstFvO;TQ@^oH%9lagSzkz98L%v4k5+i#Nv-Q@lG60GCOwd-rb7ZV{K?^+2Ww} z0rPy7-TQA}cDBi>$W=v&rIS$Z|T&j6{ zb$L2Cr@3Zva!j&huraT??7AK6y*pP)yRD6>)2-2@-OF5>R=bguv%5BTmsMA^QTHVB zhu7a;-*%UGSq3u3>P^R_`Rc|8W zb9OP~9LijZRnrt_9yH-qQ zM(b^DFv~8fKy>|C`OnoeMxUXdzj!b6?@C6FiVA!%loO>n2E-6MMmk#%ft}5%VR3+v+iFE8mqgz-ohm@230K8 za-eYQq{0c^#t0|J-wetv>%EBWJ(D>Njwi3LcRanEwXMs!6ZR!~^PgKx^nUP1y>oE4 zq0ea%A~Bctzmk~7@);xTz=ndFI$Ii}EyxwCx(U>sFE7 zXjX?=QinuF6HS`qW#_Klmc(NS-OFT#icNiC$t7+qRhwi@DJ z(Nc$*5l8euJU6g-itf=Z*TS7+$6!b+fg)0m!#PlMvcfB& zBFRw%(Za)OhbG$UqX-rQ5n&S1e6zqmgF82;jjr}^@j2)g!?-=r7U-5#(5D>0SXk}* zlv`b6RT7mIN;fsl-Im?nciit%iP=joGF+&mzgGU)K_ z88@9(N~P)`t#hb`USP!%3dakQ5&@cfyxTp3xS}BXk`N@O6iY4{y%t(~LdgS96L(Ze z9p0Wn`tv+NO7xUKR;+-Xd7oa`brk_Zy+n~<(IXOV>@CzH$QMhcjU5 z*czMDC#2GN_pfGe2iZ5BJ~^2B)25vr(^mQF&7Sf&GQ?p;A?Ag-ypqE*`@+Y&CmAmh z9p#R8BBq@#s^T<6bJ#j{Bi%M_M(A?SZ*=V)?PD`ayBCidDC2u#4q4X_Rl1?ic_Hm?Fvj-k zpv}YB!Ct18qTjK3d%HS5dwhA@uJWqPJc3KOD~N34n)qzJ%dxB@?|YCC5=`<|H@Gsp zwFT5kICBHb8D$*oJi1`CD|$fr6J0&;c6UN*^w~hvjCZen+Pf(Z774~MSr3YOSbK*-0&3?@D%jKOdS~|DPx8ApV&%t=64)WhU!`+6Q%#vQ{$(a-L z(i8Y_^gxK$#RW4IMQYl-EMm8|aKA}I*kos8ayf~0?`9~H-d%T+_nlngJ%U%1JlB2f zs=RlJfpn$wc&1T%%x4~tE7N=1q1{-9N4cZ{c`q=M^B(OIHb)eaOyB4^YURr;%-&zc WKWcu1_Yd(;{9VZu;X*+1oiZ@vM(joa literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..857fca00a3aeca65c0adce5225a6f8780512fd9c GIT binary patch literal 2036 zcmVusaMVMq8U<~l}*&%*}VV-LxCC)URAq$73M*y|fRh6tCeeZ1u} zpfYBn7h-jBd%oVe22HO>UwZ-R}D|oC3cIK=dFFx znU9q;Q#FsHi;nIg1&Sgl%aJ~wWofR@IyD~$zP+z&gNWgvtV2m^M&Kn~r0)n=EwbhK z!Dw3^yT2Q{#tzy$b zHSnT{;H`$sOYAjiwK-hLIWC`u2?ir3PkG786pDn)5DeW}r;QC4w!7CIb@?&VT%KJx z>N^gv&BcROspZ1VM1GFcDE_@|CtVj2Ova3sLYPdxfG$98lQY}{5^seG()`gBjU`3;Gl zsT4IBEJ&$p$n3)KyEfYNUK?1&V9lp3s`%>?aya4ZyE+3HJm1m zBCT2?B5;f4%a)sM(S^)n*z+m21MO}6BivNsQvXa;J5WHCG6rLa5YQroLO_DB)YTKv zDMEtKq)3cl(!4-N@4Bgqo$JJ4v5W>O(?rl3QVdj(rCKRV77A@5z#3At3J{D~6t$p! z{-Ucgl4e!?CFW9QMK2R9P}oC_@G$q+sl#6%A1BK67L;qki%ei>Xn{x+sZ5$6|&1foJQfFkrkUrf{jfeL}sr5aE)bPWebQ&CH3FiMyxNIHxRL9h){CJM-= zj1vS~5+ejHsb~gpQ3Iv|5DH*dr)p88ghnIqiv~BG}l!ph>-y0t3>8J z1zw6Mdjt(ovoI_&B!W_Yxv>T@=$zB?k<}BA}>{DkUO_a|aS)B(cXSo>4$_;R6jfQc@ZuBB2S2T4F|Gk%DPT zh)9Y<2OdxmH1JD(kL!Mx(As>{LETLz zsO4Wo+4t9;F97*4oi{+n8kAHh&W}F&c0SwWi~`$A0Rk&+1_JPCYT)!nwgm>ljfHN; zLC`!AiVQ^y94r)WKxivo6P65wY6BWDMNAkK7@9FG0kQ&+l@+k2BL#>tgkYjshAg1d zikO?D8`)z|VwJaC!$*E}G9569As}{=GG2T+!eblRYwN6MBrwWqdzvts?(a8E!i_Y{ zK~o_HFl1yJC{$E?%g3a{9%piQlB%k%WcKaya`V>tuO!pAGY=8fd+rdqF{FL=WXkq? zI;C$lUAk23eH%S^^qD35I8nYVmR~uYxhUD`YN~B#m{YF0_gbUr@32~4MBE6)P$d<+ zWia~mbyzTAbwvdx zT0u3o8vU55Hju^`sil~6eE5V!N+!h-RZa2Hzme}m5yZsRw*BxQG{8-W8A|(Xs-8#1 zR$$84t>LYO?RnR+;^Drr6$-3 zyPLX>mN>^Jl@EFt-bQ1mtcX(f#@*VaK_mCjgJn(59}TURlYZEW-oh%r%%yQk+Lwd%85wXNIEi-xhs%xBWz$<7Z&DmVAEK$-GFq} zZkm|ZA)cLF_W^g0Oykg=Asf=SP19(lOc}!%#!Es*=X9fM*JlhxRW!!Y<6dCRUvvtU zO8i*SBO)VWuQW+G*#pJfnwIxPTb~4k6U&f}YA%mta+vQjW3T6!1ZFZ6WMhP7jQXk> ztiU*gce(dlF6mI?Ru{WY@%BGE>Tjv8Y|zt7&H~XIAjPa&f;R3zBf><9-KOf1-i+c_ zBGF@Qpn%b-8r{+Q#@2~bAbo~6|l3ah(kK#w{ SKa75<_+7~q;X*?1OVoTl55D~X literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..311938f6caa221011cef6b06d19a4102d3fd5812 GIT binary patch literal 2041 zcmVbmB}*!g4V#Y|?tB0ufT6{K}4l3}#!$xwz<~g0)s_>n*EXcNQAg^l7aS4{R z_~mSPQWSXjlB&(C5m~c@)Z5Ugh~U|EP}>qi(q5h5Y*Vq*MuSbpRODtwx^~rbgET6p zF8i;v6AB2hjKr`~T_6^Edw}A7bUAfY_Xdn|mv<0PI#dYBTpiv))^5OqY)<44VvDZk z;)v;&!X$?$MT6?S8d0FvFGSEvBxkQEThXC5TrsE@1~@cA8@o*kQllPd`)sZlgEh!(;)hs43? zeK|E@^5vpD?>3|}f@~pInAO40W#?}daMbNo0kO!_yK5B078=c)jmBJinM-|@PB11`&4W4W= z*M==Ldk+n%F86R&XGb~Wr3l?mff)&-JKEOFp*(iQDz}wZ+W* zLSA^Y;t~+*iV^^A0%mTen%~2Qg&T$(J=aNz=S(!x9bWBF-Y3s!o4I7c$0LQ#AW=)o zRyB5_Whjc#2om&Y0=mhRGGp=|FK0tbgw2uC69;4~TAY(%wnK?cCZ$g6??*lCT2;tS zNL(D&Ssj+j5<)cqmt#N}vijSBtwXsIky^sDtq;@ZMy^UnD+lS~mQqiq|2#N+NZGcI2!S#IjgUhRbnr zn{n1%j%v%hxpuzXJx1?mB|9*IasofeLYMZ(O=9=T~)6RE@4D!VfctxKguf(8hy(+Jr4}Hh&A?)ya=OrmBDZ7d8+|3Ix zIJv<SE1cCPb0iAq3oSO{>Y)EA}FMZh$3kU2!=^$DQF0a0*Qu@iHK ziH3lghzN)whFTg~n1Yxkf@FZ0q@;ofmL?%7CRu_BA{HQ$CTU6tAc$sKC<+K72qH>i zVgd+f-~s(A@ovB-@_>|DyIf`1+&ENK)QlX0tiUpP2RVXr*yShaCSn+gBnTjxk|>BG zVhD+eq9#ISk|>#q7$Axuf@!9zlA39mh={3*A)+c`2jx&Vh02Gf{~B@*9L4;|X>WJA+Qw;WM!LGD1w>2qr73XbYImn#B%{u3cmNsj zC*A;E;F$=unJn}U3ove33~x7_oyR+^@{tTrH!~}U?Yg?vb8to3u{fr=HFjZ3b7jTF ziNs~SPHvv`b1kLR(CaQ6z-{(q?pK#$BZ8|j_8pQNZw27q8Jnt7)UVaeWtXM$-ZaT) zjOT1iD`3k++}L}J?c0v`vnt*Tqs~olJ+O))%E>duO?q1s`qn^OsAbv z*(O%nIjAuBOQ)A+T#qVd2i-Q5Lb%r2)-TaFv X>wA>Gi{I8CMS|`~rwS4hX&}iUl6KK1 literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..b0912c9aedbe91bd1d6b7dea0aaa2ae36e412429 GIT binary patch literal 1946 zcmV;L2W9v|T4*^jL0KkKSwp6>IsgzETL1tM00DpK004r4U<=Z!d+c;7m3wISDSWk* zRbK(2Z@wT@JLb1sG}BWVRZLBw)Y@dxQ~)I~l4Vc;l=OmyKmsHXr7;W@HB}#}qfi2M zknPueKKQ!PO9;l~ZpEC3R^+(W;j5ddRbdsMFT`c5p_*59&SN?-^46S#GsJtGE3r0} ziNZ~86l>1TX=7HLP|WAYrz?5F<&?Gv9)oc@yXk^6od}f&bWeKQ)56!K<3_xSTpu0H zE-zid0m?gm&91v;XR&ixvBEGiB2aWdLA(I*s;(r^tMIx_A|8)n2oGX<55lQSB#JcySa_bM?%TDVCvzTN`PW;m z)6H(Ted$5H(^PxcgVO8PGPUBgeUqQI(d;)9d5{Orik0X%W&6w%l&`2;qjH^e8AhA2 z;7rCPO|Bi!f$k7F1dK*#GgUmCfjiC$=t0#}vuG=Mx>U#_nWtZ9z_kmpV`zv220*dFy;MNfkC?XlO3k*#_6v>n&LO@~(Og2SL^{w80?eAnBGGV;s1ny3u$k5 zO3}5H6asCIlx!e3mGa-8FL?a|{CU4btWrPd+B47`w(K_JbF z5U9%r=;6(ow^gMe`3 zEXh^E1I5jeWeEZ-(eh@@Lk&XKN-Jzp6RNA?0O_3Cx0O@dcMK|sSs>bm*sgneZAcGq zIAUm{3uAE-2@@=2FAlQ?&rRfh4HYNVjy{Ap-f97n&*^K_5g}sfRsjVl8=fkC;-LZ@b3FX{P|Z$epmA z)V2Fs+m$ZW`#95bLOV_od4c;($VOMFAh zaBZ$S1Dv)QlPTPSnSrH&D6N`F4T&_%R}f#wM1drWLX=5qsUl5ENT!mSMWlp{O=weE z5@|6ew8(^tEj18@ku=dXh>50A5v?sUL`4!r5?Ump*$Em#(oG^li8VCSNJ*EWo4K*d z^Tn0pRyNc|U5AaKk(kk$10gE8HzwyE)v|NjZa0~dA|{qfB3UgKi%V$}L|RgbC`glP zNohpUOG-$R($djIEIMq$`>AG#r6{G_U0_D3q^fPZtY|EaL8uf(sc?=qwE?>8GMbuk>v)ihu00x>Ss(>NATC7`dBzCcvVRf;zcazI|TOUg{R@IDJ zP9~9;(ywLQ=hlq#9E}qpi*4<~^Kk{m%)T~zyL47=US8`EAvGs^Q)*N>mD$#kM<-hx z_e^0K7M0aCcGVh%GFazzLG~={fthI4jIEuRx^`9FOYM?T^<$i(SGZkuW%gZTDzfnx z9cAn#i%KJn9ePze;dos3YQxgR>*d3eJ1Lm?&Mw24J3DXF$<>J2E;AOCO}c*IyQ)Wf zBRt-!tckAF8&Z2Gp3JbVD7SY!N!G6OwTRcP=Xx`IJ)!VqO0;Z{!L1<6;vL&G93vpzC_jVR6CaY0_-?2||3Rdcb~qy5;F?=IJxv zeiszDt2*Cp?MZ!oB^!C%7eNT&)C2z1s*KryYX*Z=?k literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info new file mode 100644 index 00000000..f10bfee6 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info @@ -0,0 +1 @@ +2019-01-15 23:55 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info new file mode 100644 index 00000000..beb9b901 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..5ca2c32289106229f7b75c18a2da9515250ce2a4 GIT binary patch literal 2849 zcmV++3*PiXT4*^jL0KkKS@TQlJpdh6+W-I%00DpK005)};1P0HFKmh=bZF6{-Q8jI z(SSq%4)RBqsYXBr5Jn0ZYI+7LsfmEqKLnXnQ$Q)EMkXd8lSva5NWc*(rBCAcUxmQ4jZcaT2$X;PghSqae|nV@y@~QRPim^ z?$DlTikxo()!tt0OL1!!coC^{%A&e+#b+?x)@Qs3B!W+kys+OUNpIb=j^GUR>sBbJ>Dfou^4HiMV|EFO7Qh~2tDGOMc0fa8<==_2{2t| z#XChg)-oMJ6G55?h83LIh+(+cJ6;Gm+Inwn`Ltd@>L+a!YMx+k$E~=Iv>0mul&{1J z+ft?b<0)W@h_eL_f!XOK>H-L^3&Wa;Zq26NfY&UKu z9tq@C`q~$g!>otja9F183ajZDl5h*cmcoU#2b34BGX+rS9|7Qno|D8q-?M*e^FAPN zmweFjB78m`<8yH@G7_1pfJ~i^5hJ)cuJi6t(t2bv8rOaUsLA4@!=RgF`dEDWM|MM! zW~|kvr9>%8SL23|F$L_0dK1u@ggqeh&)huv;^5zH?NXX^b)EOT1|S+6)dQGot30I9 zHoSYkPz#!2vO-P06oWfIs556oplf$<^Gh4ho6 z$>V(8__jxEV}Jwg5Y_fjhqaO(voc=iZo}t?-JCu$84WLe4yoZ0UGVC^ZfN7Ow{0pr z->V7MGjp%iV2ip@SlLmIsX~*X^daC19s{R&?oX|^)n%bF-#o)~v=4YLN5$|Znx2Ib zOkHm8TKhHZPD3rCKImObsj1+PN}aDXJ8nScgWGMXqI(%QsDhB!RVJrQtg#sCqz`&< zfnh_y9s9Q4bY(%;k7*;br)Q2!%0t7*j|0Hf5Yr$MzV|rjokD8)(EZEp9y(D(#@hKn zc{(T39Gt%N=$GafNJA+Oqv)^E+?u1>BUxQhhon3Q2`ChYfZE>PIE5?ngIdwxAv`E} zgXody%@Sdr`y%Mib3Svr?$e(!OnIxNbjYXuAizk0$R7vrzcgJiXC)NGa^%ZUaziEz z1yd2u9F!VVtxQKIa8NKx6gafbOqfGCbBw45ilYAWfCb?C9=@3{KT?e-Rg4HQNT-3| z_`WPMAq*8yCNgG#RGvP(27Nf{b89WEj90fS?2`AqvPS z2_UZ^k030Hk0L1|s;}yy)KCg;D*NpR^YO+L3#KyW5Gp!_Qmbe6=Z}KnJT_e-;C5n6 z#fnJ4iv|{j3yuXt_SUyCtE4BQD*gdaK@>k7@ldbOJWsz^FeCfw2j@)#Q%%(l4IMWy zb+2NEn&|55+9BAX#;)pa>6j>Q9CN@`QqqlT9o&L(oakt2v=ebN0np=zp5WCx4974* zAD|*0?tD@2JB{ox^!K?tw_Oc2blOOBCR zdVC7MilQALugdlV^g!$vr8;*g;~3djZBTCwRBBMP6O0~&Un%fk05L*@BVaXG2FVcw z`l^BHf(D7;jw!v*MuK3}u<>Fh3<>RlR4Ho`0Au5^(g8s*(`jHe2Q4XL&`U}XA`mt^ zLaD8%0tkJ@4NXVyX#s%2=y<>|LN?HgQN>CY_(Z@+NJJu$@~{YmEu~5oj3E_`2>6Nc zr-q1#b^!K(@!iZ)GBLCkme7c@G7%PhehU0ZNI^mpMhXyNAq8O%EK!oy>L`a8DZ+Ed z9nBWBq+o<(U_fC47PJzPgh-6FrD!EBTGXHvr3+Gxv}n+(7S@d@AR2-p#>(S|zsINZ zzpnou)_Z;T=7?Q_53OOVZ(h{Awt-@WYcG1sW4LC3{BCui(1JeY1qAz$KN5<`-AN+U z8F)^*wynO-8GuAxqewm1VOky;Jb8#SOBzRJA_*bogXf`)hbx0K8Lr1Fvc(=$5M?adi_u!Pa~y$#Teu(#>B*rdm<< z_-}^~0k@I><76Ex6(tZG`LhfOx(-b{x)Uz3rWi8koXKo++J$uLwbe1nrY@Udd=?i9 znQxodMzb${$(!T*LqQg*79vLJN$bVXcehU5=Y(qP-7|HX8N-#1-c@9!tx1_1>Q6mg zTUxkusLDhHDkFC;39xQgGb)-`8lvgV8mMB=k;hoZV+!%#!_f~G}2&qBjHY=Efg}Ql& z@+U+$pcJO>9>h`8*c=e*?Cr^|;6!JLti0>?#j|~?_ay>xS%*9r>PoxZOwsIXhi22{ zb3GkHyioDS=HzQp1$pa4f=otgo@dnN;#0L!^toy-3o|>XJ9RnC_IN%VbD4|JXnhb| zGVRl=)|CXsr_h!sz_e6L?m2E12t@qa%&Z9sEr&hAi}oUJKE`QmMDLR%q=;P>FHxC^ zvRWKcC^y9tQ!t70i{i$#LHUI`2Q;)wLO7S)VjOflp|$WvpigEPIs!^DphmSCM3cs_ zP$-SlhPHPtMiZF3)*&#gppK8EWk>=VJVUEpsGft^Ar4OK9YH-WE#CWQjwIO4f$VtJ zHK2N7Q51~|ZXab8Ls@0>byZ=VkhrQC{?JYwusujlzbN#~@pF(=W&=+f4i)7I8lmmI zp_LSEJB96I|cCV&TJg1*OM0ZI6U~VlFK)%_llD5r@NoFA8FWG zuTpCSyzhDKXP&u_9cQ~BQHK{=Q%q}AbhiY|G>W3fF>+wLwT;%ZK&ZOq)~qI$ zTBM?emMhFCxoA|GlrSl1VUUhY_6_scqUa{JP!sbUYN#WNY%*M4JI)MRYI8lB>Im$ddZ-ZGqpOO5N&#!0<> z+`8{hZ_}pfEM8XbN7Y*z^0n<|#!1PoPh!WcbGY5SSGW~zJ=YmrGi3*^2sgP($Je>d z>b!lm6;3sdwny6UpBC29t<*qbJ(~Jm*6(O_ zwzFE&(LD>!Y7~=k9S3zvy_>_1dQ5pa`j+}4{gG`7rzwYb7pZ1;F(l-U)kJx0?Mt_g z@Dyl`yH{L9R*1!bbvP=nebQgHITg4U)&dJf;b&TB|+Ultb~ z9cJh!rz5O0p1a$F9nD+HOuw)-uHC(R-sfNJhw?-AAH<*85AeH^DZ+$@nqOJyi1%m& literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..8c82c20fe3f6deff29610021e1c8699e1d986c72 GIT binary patch literal 2362 zcmV-A3B~q8T4*^jL0KkKSsFZ>^8g)J+W-I%00DpK005)};12xyO*$x`U9zAb0!3`L zqyeqKIeT1n7U*wxJt-#*Pez7SNWc%A!^ZaYL$oUb61$3=mzpM8CzKoZeBKb>+#?_FkmcG&-o(rt7}lTppiq z(lbnKsAh(=Y-XD5C%(yr_YFLUWf~H5Y@B@4mDVZEfbVX?&O=f{80ef-cKtm2?ep#S zzTVs!o=6^99&f>+VNjMqmL{)2ICc>G3Sh*XkxfHfT0)mV&gN#HAq%SVfx<|l;x~bLK z_l%U*!J#fVE2BZG7PaW* zDk}skb#xA{AV&yDU~yIs5V*6vU*Y$s{l|l0=0BF)kPwVi5r}p^J!Mhd5$1Fhk$L4#WsWSVbWb zBN8x-08*fe$bvE=k^lf7fQOV&FuXuJ(ZD_*B1QnF8Rrpz%y3*&0~0yLFcK*)aU^pL z3<4mFoJmZ<&T%kgpk#t#97R{iDT*QVD;}OF!!wl#cMREXxG(99F2$GGVjABi*Wed1s<{P>io2K9fW1A>rhI$~rR8Z0=^<2sYsxZz1^PG9b zLmU$@R>kmB$i^W@Fw95Q2#3*VK!Q{7aN?+)Q9!~19ZLX!_X-ODA!cQ#0fj`M9t7L6 zYCIiHH%Uza(BGp?O;pg{^*hn%CsVovFpyIT2|XjxMSgKpqIFKbyR-9W|u_YB~x5?*V!*PMzrhRxSeLm<}izm|})bU@j0% z3K5uM31@;N`C>Uas0tl8Bckq|dkRgM4q#WP=sT{#AjN5}tD~W?VoM#XL1P%&Vr*<0 zV1_ZE8Xa{I4uArBNPc(Ezpfv98O|r)ohjk=TD_FVP*yE&76nyv|Yn_+E@@~Q^Fh<2Tna>|=>g2-Uu$xmTAeby(rlJ@-jYzi^eUgn7f36YS4?y0jLxDTG-P0mb;q9gq!* zLQUGLJvVP{xD4<}iM#E`4oaB;p?(CTp%o&D9>>*^o zLJp?tUHts!YpU+?3X*htj;{AGps0IICvX5C5PjwV^K3kq&qjM2qa96h%9hcTD+yyx z$KAjOP_BB9Upt<@yqkjM z*jK7{i}m`zvFj}?g4iCW%WN#W62#lIvAd-yPnH3k;F}rdj97WNp*KOIam}(J183)b#4--aC9yNW()TLeapkhP=vWv9+{D03ieL8 z8dtk6`?J89Cq4JLhdBqCCrr}W)0c(bmMVE@ZZ%!Ga1cZ|E2CTCz9di@Fp)YuSq*62 z%OaRy0(`+fchZtcW@1S$>EQ&9JKp;Az_%!OyzUX9p#|Z`=Y5;J%T^qHRP$9Qs<$Y2 zM|y^v5-%VX7%5&hw5OSd`Wnb{)W?|b4gn^J+1MHZU}o$-p^3)1*WmaIPj7I;5|pKV z8Ooj}lj-k!Z;r|(kREN}aG48U+#*Ks5#wN7QV@Za2P*X0drnuVX&BICGuHKYbc+7K g!$%GlTXxUyygxnR_6Ok~@}Kc{BvXY60i(&kFkgRA`v3p{ literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..939d6d6aa2ca647aa0a87dbd3062a8efcc6b8cab GIT binary patch literal 2456 zcmV;J31{{~T4*^jL0KkKSv!MbuK*l9+W-I%00DpK005)};1AHQJfx(TZ6v0&^Xb@V z0999g^o9cls^2bjQU=;Fz8aoElB!~0HBZ86HB?P75mVZ#Wek7`G?1B9Gf=?_nuaQ- zCIL#5dZLsK0_8(NVR*z`O&-Pgk%Xay2ul3K$c7+`D1I(DQ6R*NUzrSpfom&`VyUCb zK7S%Jj=hztIcPX$D+b$v`@oRhRIs!;yzTCh^ulY7&Uz%C>x@v>JwM4pm*Kw;1e$|iA4 z-#ia_)jkd@>A=!@Seqv;If>^!k?~MF1o2rA7S{O^bre&{kokUzYeMwkyNQXR_9i9T z?N&kR1x4?~d+ug^-Sd5ZC)10ra+r35@n-Drwp{R|;OxTqFh*oG1VOE6=yn0{AeGo{ zq1sVINq`*kA|Ru2G3vWVYKRAkHd$lGg{oWcC!f1>GM{cjed=@Jco*wz9?-Pm6 zJZ{9}9rl^`w^Y|MPKSB~)`LYf0kLrdXndbUwHPkgCMUyM(3m`a5P<3>>f3{Px_9ii z?>+A1l59Ct_XT?|3-_j6a^JgXYqankSb8)oVLXEa1A=;#{fW3{B=BE76ylv%nl*?h zpRBos5N3*20Qal{WrN8B$sIF-y&q=&$2_t0hfgAjVPHxyK1zl(J3#g*f}e@OISi6s zX(>X35s=96gz!VqoepZ}$3DyUlbf!eyu0pSNhIetu^&@O$EEeGFVMX?MLizZDl9Op z#R^_YQA-YwR5uMtS~np{W`|a(DRS1porLfc3^|(lTK4_Z&$FISQ(t`{J@H8xhgAEC z;rTLN*+DOp$Ffq~g6v6k-%{6_Qwo|XSo;v$8z$$jFeDjjr4Lx*~mXA&^_u=G3PwWDCFc?BUh5aNQ9 zVtPkYOhqq|D0~OX!u21YbHi6wDaGpdr;P8s8|2OGqipsG!n`LQyYZSWxSm-&r%ABu`uEB>4}E$h zi4g$;?6@2lFa$)h6C|>*Qb%kSw$0+9ryM*Sib3a*tMl8(PT2NUbz7N z>c2HC!wkaKS~FEz+c2$+?N#J&B{yrgM_pZVohe?aN6)-Rr>f`Ik>v95d2-xdapiF? zI1VIb&WWU8fGMSBKq#nXgxkJKy`Sa!mwrwj7wnV{{@KQ=i5duMg$l#bduAS-GcD%eQ zJw+dIPXj(O1~wNs%Hw2A9fV575?yz}d41K?a%YuiZoP`HoPpAG^f=BsA%bEEDiVT_ zhh67mw~qAd^H%1uyL5K)_CVow!5PX@(Z1#U%y)D5_*3ov6#MtDC&DOENhg&vj$f9j zZQmb!?FWG(Uj^RuIJ|cPag}wUm>|40LEiV6T&k#obWfk61rcIn&=pDu%@l*IpR!yT zrA~Ts)!Vqs}*XPs!jQKkx}Hf2p3TbgBN zu*|KQ>@iGXYf3AHduXMdO52{MYfdI5*PPJEiIqbwxp5`L5P5M44n84<`eq<;e2GPK zQx;Yp2GWu<^yXk;=^$V@xhC@qm=TF!^5UYxk)J#ar-g|jh;YOk`4#t1k&rksBLIMx z%rcV+kjUa3Wu#Lmg8oPm@iT)BByz>smfWe$smpotJ2qs`Un#Ln zk59X?sc!ftqsG#;d&#)xESY-tY;?O%10{9sW9!S_G@oa)cJGG!%yr{)%vwWev@s!> z1IVfv;XzuUaSYZC5me+tZl^M}PD~k@Nk&A_(Gu?rrx00)C6=lxjP)xER2}a@s3;6Y z&VY?j{ZG_@4kbe#eDH2xd$NJ99_~XRyuFjR+Y9vE#S*)+P1?ojLsC_1=w2b6RlN1v z8P(U)QQRY)<&f!JeAas#bJ5+j-gzt;?O0;*3|HF`_4XUS*W$-Cy_mbmMjsd06vwp9 z8yz?l+sjW;razM`5|TLqr`xU})h%|gvmMg3d7;9SjdG2Xn5Z5clAgkic+T;K6XJZ2Lad?@RTg3#Ls&YLCI~iz z^`w&ab#~_p-u}_f{iIbFT<(nv7;s0-(ww?+K@f<^F6Kk@STUy&L?FsYq*`<0jk$M9 za$!hWdrHPfmdtnMS2<@`bsYOnY<5`zz_)ZVN;lcBGD&=oClMHhrin~^c{u6u(aam< zy*9naeEsawY?K_QxCWRkB#K{7R?-6q6ZVke`s&KAS(s3)!Gkwxr+HTNZtQeZ3~Lbc zjBv9zOhwhP%uQfu5i?CLG)rlg$}Cx{DO(k%Xyx&O!8&WZvt;hN`vVF%aKj|1e|_Qk W?+>s)ct6U2i@744C`cW_u~&ea@WOil literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..8a4447fe9fa3dfbd332ff5d57a26706badfc6246 GIT binary patch literal 2031 zcmVO7MjgpajM+B_i&zi+xX&jv+_g=nlcK3Fi`h!*Nl3zh>P!Z*PW_La+^y7*N>z z>F+%A&w6vq)O@4rUZ>Et*_5W)^q%*6ra0%M#W;0XpHtCqJ(VXOk=d_Eu+rD+Do&I} z*U5aI^2t80%0BwM_p9D7!oJtg2}FTO4Tq^DJ=MPS9zB4}Wa3~`?)yUA*P-lo8cC!Z zM3$PMbYTrhWSEM$KAnlAn3$15dP3~73-74M+Vs=o;|q2Yazm8o7g_X&c@Bqq26Q{v zMOVzY6e&nftRFS!& zO;t?~RRzRge3=Ic4~0CC#ho|=O(0t@zjE-{Z)WIMph?;F``?I%Pj|A{6Q@d@3D{~1 z7=+a!gyarLSr4GZ5+E@k3<&{*nhB(j5hU^~pO{g5x{1cC+!A9iCh=1}POs&u@_d;- z7jgo!9}7-^aRreAljZ_4F^P$BN~Kb49V5Z;(^%T9uKT|U;MM?1<4fK9=Fl{hE*df( zaM+Nb9Vd2QFoBE%MPb4dilY$nd?5!V5-caOLu8sld{dap7uDY{TKCuAYWF0JVK+LJ zQ;v@*INdYi{T=mDd(hH};-N7kB&8djs8SIG4iI#aKoo-nf?{Ws@a*oi;R9m`7qGm6 z!=g!X95P1?8<-VQQwGYBG7LpgUWv)tlS(QKnG%S=5?F50f+nY2wuWD*h_JO21A=ZR zkG$j4l=3UbeQmX(Qgo>!^k=DhkK^MhVVNwSX}UIRbgkQ4Q*C7@tz?$k-Ky5w)fzRV zn<7K=mkB~R^wo ztLa47%twpmeSM9lvSNId&5cJm8yh!8cTLe-t8}Y&H)@$NaMq1olSCxqAsEY&GcqO7 zd5ny+114uvTVmaAZ1&kRH%7IiMwtQ}Bn;hICS!6jg>l@jaT})EF6+4BovbS^Y-$^= zH7&}(&24J5*lH&vK*bTrHwuv4Qb#nC3vD)KT34;vm9^&T$!hg}r*XGX@o?*tc^*fn zImbA7yrZ4dx#{6My1Tr+Q}IYc6%e#cWXQp=o8IGa+=}kHoHFE&a$eh(*SYE{YgEan?@!2|larqqw&SO=uzb!t zFC*t4eDKBk_1E6~{k-e(?|gkfKCgY6xcXF4nZlV+tlkG%!|qCQn}<5S`a~7@A`l}Z zL~cNq2yjt>6TC+a815{?Lo>@Dn2d{umEel@U2uovd|ZR5hGdal=;zy+XL#`HWEH}I z*Vk9llphg|^eT z49pr)y~6_H*sjd2ZIzWSvc^W+HH(r{N=hk8$7=+{p;#ITa102L0uc#`LDhQOXzuU3 z-uH(5j@BscP$>)S+q>~k*zRX)D7tZnUavlgvdxRt?+HEG+D!ANcs$8#PFae)cD?PL zHQe(bZ0gC{jLqK^a<#SWCF{)NW6gc#*S5Lt^hPph7F>-VC8D1;i4!*A`seas33$bk>MA?EoW6D|%kUkKb=vreR;50MO6-Hh=b z*o`9mF0Oc0aipS!&tulbGAJ;5`sg)E(rxgw&QY6RIo7%F+m6042``` zR2B?D2#b+KeyqEhk*wD|)eW%NWmJ%)niBVGX4W*HEw}H8k%7F2!I){`o&^gqxd6lj zQ>m~wB`S;*7L3+qR<77sGVhS}rT1{eTU#Z2=RU^e&TU@}!N@|f4?&#yEw&5@AT57a zwL1IfiYH$({X#-7Q$0=k^CmN9Jap%lH$^#UU%C&e4&^xH6>55r$?6u`6&z=S2=y}x zOu(^0>I86TQpu3QA_gMH7%H)fEb0O-tGm0qyWR6Yf$LviUcK*gulhs#A^Z>OPxOcW NF64@Ep&&H}qw==>*;@br literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..cc17736454fb755a9bac0aa4f7630eee8952ecb1 GIT binary patch literal 2116 zcmV-K2)p+}T4*^jL0KkKS$>BJv;YtgTL1tM00DpK004r4U<|T~-M1FgV^XDi=c7iJ zvbKBN|I@TrV%o#VLb$%s!E@ddXNDUhp9410VEkH zRA?*H8?Tk$KCBy#Wqm4O`Pw0qO^Ta^0RMR009L>E>(PrL%k_l6NvNET{=5db-%!~=2nknb++U}(O4afs<&dCPB& z;`&{#Hs4O$c8GBT5E1u@@X|e%DR7UURBi$e6e$|s09hGh!GVDBOvMfCQcd?_;1b7t zMy#eDpB36d`!y=sGN^*Fxv?Hp?n_1SR)M*g53;tkk-#7$+!HkW%{Ffg@?~&#v(Le% zZ$f)p_db^^W-)!FO){^{V;o6D=#7Ce-0R{N*f}lXH)xSO$%l7WFD=&C>h@7&ORiUV z^pRW$c44Sw-)GJCu#(6#5JQi?)t4xsg^bd;=dW@dcyE$+IVW}v_qT=S@Y*QKP?Nc| zkBEsH(~q?f-yd@#Kvh&Wcv&2XnRk*%W}|P#_a+e7kk1gjIEfE=O|)7u9UO0$%hfbJ z(M2?OQh1m_-7zF)aTDn4?TaTIuM8Yh!gPwZO%zpwIedm?>A=f~QX)FNOysnQW*u5X z<+ErM7~VVQosSgl%zhGF;wv*F;4i!^+aEgE(0Jh!&%{p%BD9U(nN7+BjG3aAF>VKk zg4@jDo1`c}=Rt+fLLJ!dSptF`o;N0e2zh~B9f*17*s=(gG;||0u3R;ra*GylP`n-( zGg1XTdhc+72^^QH;bBL?aLj^T8%MP?{16x4YU)Am?7N`ui7ZJ%?x||Wo_`b4Q(X*& zuiB6xt2oYw)5kXBGPj@(Yd{h`#>X}$JR^%1MkZ>B;`T&6lqfv2f!a&n8|m^Y8B9=H z@$F}v^2s?a{wNxBdU!ubqIsg^Nf83*1iqw@F%%!74kv1uJSgKsPB@(HHOtqz$4gAA}XI z{4P1toheOi$&+NLStr!gHw;z0_NXPuSzH*$dzj6_QIYnmg^uNz8g#=(SUG#zUMsx> zBgB9YN)Qcc>BPL#tJixagd~R0$XP^NI+JHRGL5UQqGG%0Dl(~Q zk!Iz^vW}NSMUmKVh!DLgBxi#>!wTA3>=-nKELdjjT%}6wm=Ok=>k7%}VI4dUdRId_ z4|g*wg9DwKH8Tf{LLOU|XvBtS0qkQL2co0K*BH(tTCrgy*P@`~%L)iCu0UK2pcoes z5DRqb(wPgDh$9=O8c?y2Fw(BM`dxWmnUa!00xt@%4D4p% z#$%&OuBB~{5u+$_;9S1z!(VI2^J2CWOfpBd<>k|r_GV=xA|fc#Qbl8E(<*3*jYf?H zl-Np4#zLB5rXgahFF!%iENiSs;snqR2f1mEb^E z0a;TnpmasOR<6m<9XqlpWytkjn5mkA$TJFFrb#ko5QNN`5iE(MO%^mnl#rVuKqPm7 z9`u8s7%+dn4L@~dGrFq2@1Jzn)VD&&DcpBP>gtzE5i>D!(JKq(772cJB}zEPRU;NG z6)q-h;8)J~RbZ0CBf8-2SP?@N<_A47`wq^bLWY_Gs9rqvA~OcNafa4!ZeK#|MiUlL zRHi>yG}o^ytshz@4dRV2t=7X#?3j#?%m&d8>dEg>@*J2 zHWj*J!Gp)knpA3Rh0i7t1f=L?-r||giF?}}8Cs}5T%naLUrCEo+0M+ zd67C9aq9N0_8Zg~%kEA~P-bte*>_}N{k)Pu|q8W4>jc(GUAsQCs6uPM21 zFo^hWM~~09m=9L_omtc4H3XJIwfCa+u~I)hcVLRp*^cC_!yXV}_mAbNPKHpiT zJ;!HVyAwBw5^!8D%znI=lA$bu>xs+T7pg)ombKWu)kA^v=~C3B@|k@h?@6`dmrqb( z1z#BE;YjYKPEHUHB=Pg)P*UaC;@+j%I0BUNxketJES0 u26Iz12wUhesve3MYhs}Mp_sYa_|J*lb?{g3=>y=e;ddlcg$W1faGOBj9`u?3 literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..ed4b30c28e63da993eab04b6e18d622d5690351f GIT binary patch literal 2044 zcmVNUi;lDB<+Z=e5EP9 zwcV-kdv^<2_VWre4OsX}0&J>)2B)COs;T;>fJ&YasUsi~o}ekJfCN;)ihu&9s)mvJ zqact2`X}0c>z_PnW_^40m%6dZua@3!arV+AWovwAI%{hLc#bxXb!P^%4dx!De(a*C znu-gm8Qs-g%QT)SEmd7FN?FDUs=G1YN!6X*oi@#JG2D5XRufn|OLd2`UnJ{Qc9p9S zJT2&@6P?9eDSo-ruBFslTYawYb5oH{zi^?WY;Q#iZ>F02Q&`N{H{HiU!tzl>_V0q5 z?_k@O47Z9Y;T^U4j*Y0JO%5#K0`%>dWNKSOUZ9{alVvFb_%xO zkp!Ax&e5aLiL|?v#prAz{OtCyXEg-MJ>$oB_FM z1v5``9_AZ@SDls>ur8Zka!+Jyu@Z!H$+ujV4^7ds0UK#~Ym}ILIO}$AJW+V}9c+sE zLUs8@w@5oKL23b4*dJW)0t+smce=89*J;9YM)#7;$vqfyOwT~(r9+mtuOzjTxg4&j zmuVa1`zJ3CMAsUQjzf|~E68%o7AQw76SE94%&#iw_o1&X?#dLL=It0RZfVY&LPm1) zt<(>9Y1fUj!nJnK9K;pdJ?q39zh!FHF1L6ixj#Y)cy>NBJZbj^6n2E~tsATM;}S_v zu1m>sTC|BbxxYl_?u5CWj5roKGjrXuz!K*13Vl_-FaamNBSJorM22XpRS6D96bj-d@KE&#FrTgp zPCSsso$D`??xod}+DMB#slL?uD=wtn z>k>nfP2J%%PU@m{QG9U*`%vse9}`ILnP`u$$1ui}Z#Zz)py9nbkKL@=*CgZ_rsFo4 z6+a}8bH(FxNzxol$uXDMR1pFK2y#IdBvkDvR8i`3C@(InS<~4=Pj$uUa}pCWU^XXT2my-6iLi&x^%(>li3z{$&WGW$4)TRlP$t}`N+W=iAD}J zNsN+pH6*=5RP)^}d&`pW_j90v4@eON))f<+RZ`rwp7j+V)RDqf9yV#p?w4Y%RZgm+yqc^vUarb&_th5i)t6lzv}5+`O1m?j zVmrj+%NJ={s&|b!wq8s+pG*w4aLRr;+SJ}$({lPnK*^4-_Nflpw^}$6pjBSZO{Lcy zslF&!Bt2W0c3zY1o~7Kq%p1Q>*43f#F_6LZOFm8$N8~tp+}i0RMwlt5<{M|{%pO)g z6$`2a6+nw9M1Y2^j-D@LzIn`}p;MO5rIK|j<&fhjZ_ui?+UuIkUgqFwk8%gQQeyTd zWiM%^or=-Bk#;nyZ@LcWh&e}Kr=b~wnq5klmETi1su13HAaH2r;!O2R!%m>P%;2mQ zs7vCgl~=JMCAlVRY6@zRCC!R!uRA=EZz zd92AVw|5bFHTob^yoUB8Ub2-|YQ;GaZdl1PE@_5U!P2E>U5{Uh_M}KvRh3D5KD~6X z$tKMqG?f}vNuzZuEi8*5V$K~W(s&FEC!yp?G*cYifC1C2UUv307CvUHWHzWi2ZHe% zgl_G!im7&NR$Z}#Gu}whV@c?hR1b+s(7>VM8u=h-VM}GGhUVCzG>KDzi>*iP$H(mptC1p@A(>T&3F z=m(^fo=?1e4x=8}3~bx+>CZeo2I1T6#Vjjj!z52I(xX|FC_KE#aWskuli@&?S54?& zfpuE9<&|$_be@uFMHU&i&6Ni40^@y`_d3 z8Dfkl9EcIyG*>y30um9{PFgv>Lfk2^_+f^mwBXFf#b zqAGEfWu;+an}l3OfO@Y{!Pbp$g&gfl=S~PSA$qL>3V9*b zC4M2xYIPm77Q)GNj!;5czZ=V`-UM<1f(SO0uQ{YF=*v%IIj=$* zD20LO2Mv>JP-9X-){O)tQ>-XM(3?S3oL!+yVZq?h7=;R` ztW8g(;6u_%cn+**JKdff*6%y3-A49Z+oTXYodX|ARpJB0N_5JoSGhzK=z9y z7{n?@1R^$+DpI2uM^xQ))ikh)Y7{LB+P2WEeg2U|%v2o2QIS*$00e&#PKp3{cS3|i z@%!?A7=)Naf%>5?QMUsqo92dz0+fX?%C!H*v~ws03qmCvGJ;z|QwJI}hKvkiGHS)d z=pw#^2dE+*jM67GIlu|%=yl*6LG)~};t@C{lq@eo1=&S`EwX|aphE4mv9?9!kQSOw zP(`890lT$ZEh%gZ83ntRgLYV1X{{PU%dlDs3K~(TikA|e4o*kR7mnl%&MCvHs(h%g z@3X;A5sm{r^^6k(0Q+B{K@4$7ACBPecyj}(Sj4_QEiHsn_`V@dK2^d0EeB*ixmez7@_j3*k-n)6vI*Bf|aSI{Rhj@Dr zTtkL;$S^VehlE9*3!USy4*)MoUXpih(l))L+B=wsPa2g@_by*n!LbQM&>& z)5gb8eeMPLsU=@5M{Z|OD3~~%9vlHN4xPmD5UzI(L?M}$xuu>q6lywK2ULd*#YQ4djoUe1<8@V*@{gN~2a~ZZ z6C>Osbvt)shcH0?n9Ns09&722ykL12#U2{78pyfReMf+^T^hbxPFa}WjXEEW z6OrZu4k*xeOdJ^m9z$p&AjZS5v|xyv2K?iLtvM8b5?QDqebI(Qd3_+eyMP`vjdvDim_}a~91cz&}%NBK-4{TIB$8b4MwM*BI7HEYW!ZVd( zc5gw~O4cZQ#^!S1Ugf&@C35UH=Q9wKymQ&^N%oQL=BBN0Cbq)Y=~ncr+`4MXH*Gk& z_p;^9Ox>1RsTf$sB~>m4Ok(BCsGV$A64NFXQBX@-P)3K!o-!41L|JJrT3KaM$KfzS zcSP|G4~>6giF-)*&u_a$D;=sLEAIqjG+g}a(f zIRntk_byUhFUj9tT?&A9QsgK}+O4Op|Z#hMG=nm8vr zVb-daMKOkrd(&8H9WK3Wi=#cYhDW`;*-CGRrF0yzK(@L)m%FV;*fsXZXUON>11if; zEXt@QxTQzl-WK9bv*g6VEF8YqqIAIbyRpGH%X4*5vn!rF?c)-jf`W i%dfC7>T+^q%+E4>Q`(=w?hnO3#oUoj6eJ5Sb(26dkX=Fm literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9b71a7abc871f5d38ae0d55b934ea8660111affa GIT binary patch literal 2453 zcmV;G32OF2T4*^jL0KkKS?l?4M*tleTL1tM00DpK005)};1688eeZ4U?%P{7*|yoX z*|*M(F;xl&UChU>UhTFyKJBP;dzB*byfr-<8C6V71d?Wgs$yU?#3t2L#K1^IGO3~r zB9%|}Q&OnYKoKD{nyDBcJ_BsB2M)%1Je%*K{MW@vj+VhUHlDU>)uQh1)?-XE16UAv zVOCyp%e$oUCsumrVrVVEBr2#%bmnDkcrmKSpiS;e!Y-+XB*Dwdj6SLwGK|N8Llc(i zQrhS$V@8*-YelHMUQ5tSfnN)16p%SkgOG%h9wb$h<W8wfg@p%0M7h~yp@0R(RI zkPOHt(JAV@+UkuS0CqYlL5NNBJAxpuZfmRUf>47ADx-s@7UxmMRVCc>koON2+L9}44D9Ja z2ogdaN>6~o!%%s2Z6VZ+z~~+h^(BCywM1rW=@qNpnKi8RWt5T$aMeX6ykXVIQLCOX z-p(q1nl^~Sn_B~|$IJ>Ok;I~&$Onh(Q@{2ZsU&iM3KOd_e-C8Y00iK)D8p z+gp})n}Optcd+zCq?Eh|Y_br_uBlu;_S~A4RwTi;*@!8pzGX%V0g*5uXsQhZ;anU; zp%b2}stCY^1R)`UF(Sblk-%t!ZEf+phVQt?cbgg{w5xiok8-P?t~rNr8#Fu?^ccws zUU{B_D9%vYJ)mJle0ISo69B-*1|Fn=2oj2sWJwu<8mENhgTgrkA`QD;Z!;6WcXJ#m zibyJjhI_s3&kpdwG-q@jUW!Yk9u=F&eqoxwrSS`vE1Z_gV zfM^?G5VZf)Ta}T&UYfv?!%x0|16N9Oe%eA1Ev30r@}!$EZd=#eGEa@!*b!Gy*YC#`8ud zuLR!E0o3We76(_lF^UF;u8HOHW+%Im4mtM$iNgDi0pp%t4upzsg6XD?siNw4sJ%J| zK;-3f6*%%QycH^e)dH2M=&GL}tM1ALq%r_}_pUe&d~=5yDUL0KkxUFprkW-LgQd36 zFfb_yLIHt`EFl*I5ZkVUgMw0;YPbo2ZMMGxhXNpcVz0^{9mf$__C<_WWh+V{`2Gb4 z%uP3UM}epiHRl^c77Lnk%p4L1*HD9kMi6y?V1x@siBS>cQ4fp&In_>2K4L@}#i2o} z6oev0U|Lq8MJouf0s>2!UzfmnPasGzO-Ik676~OON`ovpL=)@=h!Xd@pccXq4J9c6 z*`*-Q&}GV60|pN4y8#xhTVqKLt8Fm@9kPWc#Lr#@Q2PN66{k}{KMgd{6zl{vp{33yp*hY?gXh=mEcNLH2yl)y8%hAsoZwXm@ZEeh2kZ37X{v0rpj8W@qo@ja6+?|iW)Je(JkP-pKNK52%70aHl}swWGQ>$m4TfW84mT<*QgiJK(20~haq>v(}-q{E@L>YXd&1sr)rh>}3qlqr4fVrchN@Pen zID^A{_=bHDbFyw#Zexapn{ z+F-#N7DmXdg9zzcC@4|KsStvdu7UwX1Hr+7h{&l1t~gkaWJwIj1Qh}mr0r*~ZtW`z z;dU2ixI{&w4F`sbK5M4jvk*_CQO71_v4L-OIqJde=FR7Oe!KM9+SY|^XiT+t=;1>7KLGX3cAs%bj%}@d28rwNzXt zYpF2zTyt-y&oj$QH1X#I_Bosfea-1Pp>FRW@Ty)SpKRg0IF6|po>s6TJj}?iO;zMm zcg8ulg@h0j6}N6K_{B}nddn!++OcFb&jgI$Y-v0MVB!SGRQdFhKQ7 zU^i4K`wt>Uk*9d~j)N#y9!Rj|;IY1Sy=!fu-Bir(<%;8wU$D--+szPz=Hp|%h=fLM zl=s0JY}TB;#d2)mCBS{>7YQ8>BEXpNv{yi~FDompCC*`JG_%~?SUtsVFAhj-3}0(R zpdvURWw_5=4#l+A<+5%nSha|}Lhj(gJPd>r=U&1L0(LCSZN-0JVWWo&ExU92r}0nN Te<1%<{|mVyoG3_j{I{ckd4gPM literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..fefafb556780c64bbf10850f8c272003bf8d5bfe GIT binary patch literal 2502 zcmV;%2|4ycT4*^jL0KkKS*lhfV*nfU+W-I%00DpK005)};1B3`-Im&DqU_z9vvzGh zrS=+WP?X*ESH&Q}!ZM{*Ss_;qPaxA(6wn5#_#rB$CIL#L^-U?L01{~;n`)|Y}$`9u5eGs~ba$05jAdiQzRbI3=3XjL**MP9W|gd&pgILyUwKPY`G3)NdPR7L?L>+zA&3KQk-22e1!3Zc0u4TjtiINCM>{;%E3F1o}$2jrXRO=Ws`A|jpTr09Zz)i15Ln!Ho9yLMHH$q zvUUeT9};Yy_HlYvCJ=OxG(PO+uzTJT<0N(;5Jwy(5&Dm5u6@N9t)FnH(xIZj@Su*q z6sE#lNQ6HJAjApq9w4PIpt^R3)rsVDI|-|~-kp45+$1kEmwWI@H+_@65oh7I=5+dd z*<)hVzP9pkF$Wi87_(rpJ!|YyADVMQ1p~Z;5UBLxN`PZ2=}=%6sT3`K)2lu0Qz>|O#=CxoU#Up?8{=|)udckFp) zfF2xuJ#wbS5DO{3&`kX|o&^&76^&?8A`&CM(}yntuLgW{b8hB60^juJG(&WacbqD45l_PvTg zPp5tHBrE{CHE@zb(JZpS&L#TIB;T3#TQ1thxp8EUf(MV0zJYeb_ z4t2C96NYy}IoEU{=66X>Nom9Co)IvL2Lw?wQk8b`;B7dL1tl)SM9Q*WVaO&3hz54X z5HqG#hS}*W_8tSF{YZlfN|7a%aC-gx2Y3^YMq$N#odv%{(&%M(a1s2?*SWO3SI1Mh0m(3cWOJGvO%==-Vj2* zLU5a-Fc3+WS87F@??T)MIjrFll1Q)aREyDIF@BT{Y*z`omI?n$yzyZnb$E@n5JBg z^2>8BUCd^BA);W-80^HP;WIC68DLvrQv#k%7R& z`o-&`vfQ|ey2UUu7jCtndJsVbpE{*YiHI$A+WC99(&pMcl&ZR7Efo{%v!1Le$$iDl z?!r;%a*aEoqaNaA?7&YYMLuG4jlx0z=YNNOq#TE*v zV%sYfGJ%T2BIHXH7=sGLePfvn6viwoRw+v*QTZsawow=qR2Ynl79X$P>+ha#Yi|9p zQBHLSTH#wUt8j>p#U`!8l0Jhv+AzgU9n))ajMhxoO6-@u$$Rcd9$E%r>tEx0Q+Z7T+ulaa~$0<#dgHF|vJk4(Gew zxNoIo&}eo{2VCwqE{%;>LUfRZZu@HWMHQ_w`I@WVJ=cc_wr$UACHfOGc|jPSdr0VJ zT@JK#Z+entiPq5Lj;e-c6zmg}(_Oxl_JX;fFgj0Lfp*?9qW9MrzSj>te4!c;aNXyQ zsaMq(H)fbCw)$PhTWlAF*cHGP?#4{ypmoO_z87#TB(#LK&*ms!nRR^4k zaLyGf6v#5+VZVuv^-eD@QuO4qXW?Glb>$k+bZ zA>g<#ik~Uvd!5MdzTv^%eV=5dJ{KJCIvM8g4n%WF=}LTuIzV+p1874){5%ts6vh%5 zA?m)O9RcWr9kesL1`D@cNOOyy5t+d00>HL7A$PAd3{2*(V|-x;5b4(HIEKLh8VAo8 zlvHCbFRSQ>Md}>iA~7nc%<8t~<#`IK2Y}(~5D3w`;HXV+yYA~uHa;T}`F=hj*?Oo4-9FpBc{cTXJzw!>2Du z!{0n1_Pie_)X}elVr#k|4)NzRp!Ck5ce z1K|b)NWvg!Xb!<~A1l+Dneps2Xza##WngS33LFXPC5soN-M~nI2YW|4I^pI@_^$=% zJsApOL-PnCXa$*hjqzwmpb5cjGTOOUCf$InPk1`>1|^RY`&r^`I;1nnT9k* z+7(aI(t%dTJBKcJa_TwV*F(FxW^65`N?j*E5JizeVfczj2(F1gaHBtdtMl{r{m;a6 zoVj#&#Z5AH6`Gvxl)w=Ubga5p18)1J z#qTdJT*;=7m&WG5D&v$IAR)m;4%wsw9&HQ)ysZZ2G#ch)HM2pRxzah^&KEnm$#u@_ zr8(Nv`l^r9;*Trsef>2U)_%CS>(S%WyQ(?H)H5Ecd3)1*7VkI0n#~PKn$d;!gnpkS zdZ*vDeN9rVRYgu7SrBRSX#K#vEqe=l*rDzpt zR;Cu3#bs%xFufi;rn1JC)>>JwL~#S}#XU}Q!uPzd9z9QdPhS!1>sZZA8m(rvS*+Bt znWrKcL|rR}I1~!jt47kbZD`OlHKFf5d{0u(XUb_UnluSCX+XB!sGwm^xzzptALr-p zzl7_n;5x(%SkgQ!Uem!Rm35l2q1e_SKy^B4)FDUR_Gb<{X0=&Up5g2|bBritJ`RVq zlNcTu8&wIoaBHd_Ub}+>A#Z~ritO@eT+nf#c_9hAqOUTET|o8_PcA#72?8ru%jEQd z+CbKFu zf<);AgRaZ5=$c6fne0|F>C6f2tj|)y4oSV%=e3X}wAZ!=6gD7&Ia{b8GXy{c71gV- zK~nFf)~&Yl#~AvnKRN1PB2i zdXO|j9uRRs1Qy89K_LMS=)1|>#=X{-f$ftd%=PSxrs-o&hBc6T zQG%jX=WSyQ%3+aNK~;jf{TnZK{lJ+JRtmY5m4M>0j(fT}DzND8Z+sTSwOp`4z=j?7 z3rXr=kz8mxm3QX6@IzEbJ_D?7;;FPd#}Ooqpu7qknQU z_v)Ft8+>hIy275Q>?~KN4#k85@@8SRb9LfI@gUR1lL>2couZUpZ!B2#)%knTEUb95 z0{3WHWG6fb4p{0Youghy^}lpOl!s`q+)83-#*EJ4MkAfn(5Z6F(;756rcyzkZkaod z0wxfQ!Ud26f)Nm8Gpy4r(=-WRYJ1y+;T@-ES7LD7z|1?Y!5IZiPh={Br=%3Jx{l&F zW1SZl%1*vvV);a4b%@o|99VhvH!y)Wj12|&_<;5Fkc7m>qwj$D?H?SXkKckmXrm9f w7tdXws=XIh7hwt9+;U?315KSeS#73&YLDul=zn1U%75bSNT&)C1Sih7z{um`mjD0& literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9c832827b70eb5eda5c332f19610f93b281ffcc2 GIT binary patch literal 1900 zcmV-y2b1_hT4*^jL0KkKS$&h*AOH`I+W-I%00DpK004r4U<=Yx^vaZ~RVnwS00HUn z5;u>fBB@YAQ`19DRZJ(K)bN2+03}alRWx8s2|ZOn00K=)Z8Z~30Fy+LsiqJs&hLn3 zxz$eUABtC-^E(#P88tGR;J2%g#bnsha-N=wH(iSdK9jWy zms+S{DfFyo2zk>H%S2)?Q`b(j17;JV9=83tgMb6x5-GJ}*MNsJuZg(NE#6&+7Rnyt zJO-`dFcUTSG8EQ5?IXdNG@E8H3~2zg0O6(zEH|#Ty8x0DK%?S7CWr%0p?D|0L z^>E=pP`RU2<#Nw8<=cWb3$0k2FOTEAg~Hq7*ux=cuLKN^tn;xN4KYg+vFp0s~`Ly43<%+qGq3;>DRmN_T6$!x=}Q2(Sj8bC#MP-($L|<%i4Si^$j7(=0PO%DGod%3nd0}LSgOA_Y~EXYIFy- zawRtft+&~8210TjJ;C>Rx4zI2)8Pp&V!{L3_bOSLa3(~3yXqw3)QGcVilp?6)<N3)Yh(~g7+KR~0Qb#E z>s8%XVMttyCGKq^D$Cj}Wy++_tWn`(sl#r*N4zEdd*1O6(Kv+|BRS+6fpuS;s2g@`n0 z+J~BXRAtsl5~mTe!~%K}6X61TggxpmOZ3`q(?ms;8c~g_;O5X*cJljcbdG)KL&7DaXrhEiK5 z!DBL6I?mjM(=uS4e<*Xo=A54pfIZaYq43k1b81nC(^+uR%-Afpr$n&hsf4LmGt@Ss zvJ?2lg2YJ13xOn@vJktPN~|i3(L1To{0+liq&o#3WhU5110`*@v8Lvk$y|a7oA`(* zsEE;uEk?vAkTpBd3LZyl_n!`qGZn<3$ zO^J1v8w@iLtniGG$)=G>DH=4P69{HZnJC5z3P_;HX);L+(2T@Hj70?kz=ZDinWxVE zUOsW>HPu%X)hK11WGZ1+S>1bK%j(y9WO`yYZ*k$LEGsEU>YGs0ZvjT`afHDYt3tw5 zx^ca|P8MEI1+BDEUIqe*1@FD@4+mWd6roBHPH$T+iG6}+tv*AzX{EI{b!9(G;h1=Y zEZDha8*1e7MHe!x7kK5$28+V5Ud%1hhbvsLk8c9z>SUMDYIYvk!DgxwHx{8)GMvm0 zQ`#==Tj!L>V?jmiJJ&5ts#$iEYY`b|1y>&1r%>A(BSp09u9b>21S*i(9E1dkZw7~B0XX_?;-D(hVoW!g4Boq?+Q=ek mrZ>6OhSp{p>W;IWd_|Krd*Ju)=>y>R@Vk;J!i0nDp3wj|iiPw5 literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..b8120c0b408294804604928fe644d616adb74c6f GIT binary patch literal 1793 zcmV+c2mbg%T4*^jL0KkKS-YZvd;kxQTL1tM00DpK004r4U<*(yum-9?ec@FA6ToeU zy#Q3IMiQD+W=g04YIs1YOqdchl@mY#C#s}0(lP>unqUAzN)(W2Dut8~)4cUvIrAUO z!G>i>wiqUw|8~yat6Y04IvngV3Ds`GU7><0-U{o^*i_|?=ZT_Mf(Ifqx=Q4gox|14 z8ItolG}ztU4CZa@=tiB>T9ge4H(2V;JwnD~K;lfa4OoJ2Vi8y(t9G^3MM@1B%BJ1A z=gzw-GY~SfcA5i5cX)s>bdVv+5X#Z4pel{$yB+S>A1_KRbJF>o+p48+rbXf5gamTN z1^~u_K4Ev`jmqsyTdg}SyS+_$Tgum2RcNS4d+)eZG>}&+O0^|Np=3Te(kR{%ZM~UV zbx2*#yj4wACjk4_EZ+Hc7oSIOY%k@PwtIceFM}%#2s9lq>gSmKr)knu*kC|}Ll`)1 zW>s}v8p~o1@f;r6-ro`Y+*JoYd9x|cuMb#s5U;BZU^#a@q8h@%5iyuK}CxzV?B2HRf3Gn#Ww*d~I2(nk%n9_Io9mQ?lqA zNnLH$BU#q5Lh1{#LOz~X>7ZA&_m^&&lV4$;A?{R$6S~pdxtpFZ4K9Ep&$#fq%utOW z30(3Dl`Zb3A1oYpLWWJH+%j*HfnoP~^_%hzK~`A5Xwsn}Amkw>LdnTdVpIv()l=Q+ zg|?RoU(#*QygOFyn{=x6hQ6*Z4@sA9lg(3yTE{s$W7EcO$tpOyuAa>Cl{&zQ{phPp z?JbKoY2@&_%JZPebwa)jdAhgnQ$2rk7ZN0qd zuw?ejs2$G>KKBFAuQ@E9<;eL6#6I&sB?0!n>2h9K$GmUNw=5Jp2LT$Eb3}JoMO#mL3P8`olc{T zg{W%HVk%5v7B{ZeCySOKe zVlLDfnc3Zkr=t^~VvU1#`tp1yr>DFCfg#Qi&dPMBb~9TG z^u5#OF>*Zt$3Za|6Jis#jzeRxJ?JR>O1;3tdSiPliGy*ut5~8kM*4EcoM(rKz(&Al z%P(f#&KNyK0?(Eh_aTSeZpU$>%RM`+M0J^>V?)sNafMIVdF=jQ=80%Z(PC7PrHDx&lRfWvcw*#8tQ_8ux1V6FgQiuw zBo(MZ69{Oa$!X#4nu1foE z^Qas!_~o)H?CYHN+M08Kf$saMP>^UQjS$_CO;cL6YX_On&vQCas&~E91!VSTW$4&= zHMTL;B3Eoqbbl)AFhr*nd?)LI^!noJodi4&yS}?tF0W5*Z&J@?EK-(lS9;xtD#gP> zv{nM2SYsX+FQniSu*I!q+;fSVE@(mMG?7BmiSf^ae@0D3-Le*VeX>#&P z4U?PYDwj;YT+PyoMmR(vtO*cCuLcg6XdfFqsrO?T4TGd_j~8u(GZ<$GBwhkgKB8-C j>Os4kj81@=+`n1kep~nLzdsfIZ}E2|Q-ui&x+oXGqG4>R literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..79c31540fdacc2a4b4da66b1b35572beebe0e697 GIT binary patch literal 116 zcmV-)0E_=ZT4*^jL0KkKS@H-gP5=_KTL1tM00Dl8004r4FaWqiO*I+~JwgBg1u8~O zOpP~~BT^+gf;56WVhHgBZV1E?!U*N05#0(|K^cNF+!2gWrHR}T)Dg@P#1V)hrdH*n W(r%P77vcjE@pmLsg$WNJg0$e-SSMBh literal 0 HcmV?d00001 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info new file mode 100644 index 00000000..d4d158ac --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info @@ -0,0 +1 @@ +2019-01-17 23:55 diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info new file mode 100644 index 00000000..beb9b901 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info @@ -0,0 +1 @@ +00:05 diff --git a/lightsim2grid/tests/case_14_storage_iidm/config.py b/lightsim2grid/tests/case_14_storage_iidm/config.py new file mode 100644 index 00000000..afefe03d --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/config.py @@ -0,0 +1,40 @@ +from grid2op.Action import PowerlineChangeDispatchAndStorageAction +from grid2op.Reward import L2RPNReward +from grid2op.Rules import DefaultRules +from grid2op.Chronics import Multifolder +from grid2op.Chronics import GridStateFromFileWithForecasts +from grid2op.Backend import PandaPowerBackend + +config = { + "backend": PandaPowerBackend, + "action_class": PowerlineChangeDispatchAndStorageAction, + "observation_class": None, + "reward_class": L2RPNReward, + "gamerules_class": DefaultRules, + "chronics_class": Multifolder, + "grid_value_class": GridStateFromFileWithForecasts, + "volagecontroler_class": None, + "thermal_limits": [ + 541.0, + 450.0, + 375.0, + 636.0, + 175.0, + 285.0, + 335.0, + 657.0, + 496.0, + 827.0, + 442.0, + 641.0, + 840.0, + 156.0, + 664.0, + 235.0, + 119.0, + 179.0, + 1986.0, + 1572.0, + ], + "names_chronics_to_grid": None, +} diff --git a/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py b/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py new file mode 100644 index 00000000..47e14243 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py @@ -0,0 +1,293 @@ +# Copyright (c) 2023, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +import pandapower as pp +import pypowsybl as pypo +import pandas as pd +import numpy as np +import pypowsybl.network as pypo_n +import grid2op + +from lightsim2grid.gridmodel import init as init_from_pp +from lightsim2grid.gridmodel.from_pypowsybl import init as init_from_pypo + + + +def get_voltage_level_id(el, nb_dig_bus): + return f'vl_{f"{el}".zfill(nb_dig_bus)}' + + +def get_bus_id(el, nb_dig_bus): + return f'bus_{f"{el}".zfill(nb_dig_bus)}' + +env = grid2op.make("educ_case14_storage", test=True) +pp_grid = env.backend._grid +pypo_grid = pypo_n.create_empty() # network_id='educ_case14_storage' + +# add substation NO BECAUSE pypowsybl call "site" substations +for el in range(env.n_sub): + pypo_grid.create_substations(id=f"sub_{el}", name=f"sub_{el}") + +# pypowsybl._pypowsybl.PyPowsyblError: Could not create transformer 3_6_0: both voltage ids must be on the same substation +corresp_sub = {6 : 3, 8 : 3, 5: 4, 7 : 3} + +nb_dig_bus = len(str(env.n_sub)) +# then add voltage levels +for el in range(env.n_sub): + nominal_v = pp_grid.bus.iloc[el]["vn_kv"] + pypo_grid.create_voltage_levels(id=get_voltage_level_id(el, nb_dig_bus), + substation_id=f"sub_{el}" if not el in corresp_sub else f"sub_{corresp_sub[el]}", + name=f"vl_{el}", + topology_kind="BUS_BREAKER", + nominal_v=nominal_v, + low_voltage_limit= 0.90 * nominal_v, + high_voltage_limit= 1.1 * nominal_v) + +# add the buses (not modified grid) +for el in range(env.n_sub): + pypo_grid.create_buses(id=get_bus_id(el, nb_dig_bus), + voltage_level_id=get_voltage_level_id(el, nb_dig_bus)) + +# add loads +nb_dig = len(str(env.n_load)) +for el in range(env.n_load): + el_pp = pp_grid.load.iloc[el] + pypo_grid.create_loads(id=f'load_{f"{el}".zfill(nb_dig)}', + name=f"load_{el_pp['bus']}_{el}", + voltage_level_id=get_voltage_level_id(el_pp["bus"], nb_dig_bus), + bus_id=get_bus_id(el_pp["bus"], nb_dig_bus), + p0=el_pp["p_mw"], + q0=el_pp["q_mvar"], + ) + +# add generators +nb_dig = len(str(env.n_gen)) +for el in range(env.n_gen): + el_pp = pp_grid.gen.iloc[el] + tg_v_pu = el_pp["vm_pu"] + tg_v_kv = tg_v_pu * pp_grid.bus.loc[el_pp['bus']]["vn_kv"] + pypo_grid.create_generators(id=f'gen_{f"{el}".zfill(nb_dig)}', + voltage_level_id=get_voltage_level_id(el_pp['bus'], nb_dig_bus), + bus_id=get_bus_id(el_pp["bus"], nb_dig_bus), + max_p=9999999., + min_p=0., + target_p=el_pp["p_mw"], + target_v=tg_v_kv, + voltage_regulator_on=True, + ) + +# add storage units +nb_dig = len(str(env.n_storage)) +for el in range(env.n_storage): + el_pp = pp_grid.storage.iloc[el] + pypo_grid.create_batteries(id=f'storage_{f"{el}".zfill(nb_dig)}', + name=f"storage_{el_pp['bus']}_{el}", + voltage_level_id=get_voltage_level_id(el_pp['bus'], nb_dig_bus), + bus_id=get_bus_id(el_pp["bus"], nb_dig_bus), + min_p=-9999999., + max_p=9999999., + target_p=el_pp["p_mw"], + target_q=0., + ) + +# add shunts +nb_dig = len(str(env.n_shunt)) +shunt_df = pd.DataFrame({"id": [], + "voltage_level_id": [], + "bus_id" : [], + "name": [], + "model_type": [], + "section_count": [] + }) +model_df = pd.DataFrame({'id': [], + "g_per_section": [], + "b_per_section": [], + "max_section_count": []}) +for el in range(env.n_shunt): + el_pp = pp_grid.shunt.iloc[el] + shunt_kv = el_pp["vn_kv"] + id_ = f'shunt_{f"{el}".zfill(nb_dig)}' + sh_dict = {"id": id_, + "voltage_level_id": get_voltage_level_id(el_pp['bus'], nb_dig_bus), + "bus_id": get_bus_id(el_pp["bus"], nb_dig_bus), + "name": f"shunt_{el_pp['bus']}_{el}", + "model_type": "LINEAR", + "section_count": 1 + } + mod_dict = {"id": id_, + "g_per_section": -el_pp["p_mw"] / shunt_kv**2, + "b_per_section": -el_pp["q_mvar"] / shunt_kv**2, + "max_section_count": 1} + shunt_df = pd.concat((shunt_df, pd.DataFrame({k: [v] for k, v in sh_dict.items()}))) + model_df = pd.concat((model_df, pd.DataFrame({k: [v] for k, v in mod_dict.items()}))) +shunt_df["id"] = shunt_df["id"].astype(str) +shunt_df.set_index("id", inplace=True) +shunt_df["section_count"] = shunt_df["section_count"].astype(int) +model_df["id"] = model_df["id"].astype(str) +model_df.set_index("id", inplace=True) +model_df["max_section_count"] = model_df["max_section_count"].astype(int) +pypo_grid.create_shunt_compensators(shunt_df, model_df) + +# powerlines +f_hz = 50. +nb_dig = len(str(pp_grid.line.shape[0])) +for el in range(pp_grid.line.shape[0]): + el_pp = pp_grid.line.iloc[el] + id_ = f'line_{f"{el}".zfill(nb_dig)}' + nm_ = f"{el_pp['from_bus']}_{el_pp['to_bus']}_{el}" + length_ = el_pp["length_km"] + line_b = el_pp["c_nf_per_km"] * length_ * 2.0 * f_hz * np.pi * 1e-9 + pypo_grid.create_lines(id=id_, + name=nm_, + voltage_level1_id=get_voltage_level_id(el_pp['from_bus'], nb_dig_bus), + bus1_id=get_bus_id(el_pp["from_bus"], nb_dig_bus), + voltage_level2_id=get_voltage_level_id(el_pp['to_bus'], nb_dig_bus), + bus2_id=get_bus_id(el_pp["to_bus"], nb_dig_bus), + r=el_pp["r_ohm_per_km"] * length_, + x=el_pp["x_ohm_per_km"] * length_, + g1=0.5 * el_pp["g_us_per_km"] * length_ * 1e-6, # TODO + g2=0.5 * el_pp["g_us_per_km"] * length_ * 1e-6, # TODO + b1=0.5 * line_b, # TODO + b2=0.5 * line_b, # TODO + ) + +# transformers +nb_dig = len(str(pp_grid.trafo.shape[0])) +from pandapower.build_branch import _calc_tap_from_dataframe, _wye_delta, _calc_r_x_y_from_dataframe +net = {"_options": {"calculate_voltage_angles": True, "mode": "pf", "trafo_model": "t"}} +ppc = {} +sequence = 1 +vn_trafo_hv, vn_trafo_lv, shift = _calc_tap_from_dataframe(net, pp_grid.trafo) +vn_lv = pp_grid.trafo["vn_lv_kv"].values / pp_grid.bus.loc[pp_grid.trafo["lv_bus"]]["vn_kv"].values +r, x, y = _calc_r_x_y_from_dataframe(pp_grid, pp_grid.trafo, vn_trafo_lv, vn_lv, ppc, sequence=sequence) +sn_mva = pp_grid.sn_mva +for el in range(pp_grid.trafo.shape[0]): + el_pp = pp_grid.trafo.iloc[el] + id_ = f'trafo_{f"{el}".zfill(nb_dig)}' + nm_ = f"{el_pp['hv_bus']}_{el_pp['lv_bus']}_{el}" + assert el_pp["tap_side"] == "hv" or el_pp["tap_side"] is None or not el_pp["tap_side"], f'{el_pp["tap_side"]} is not hv or None' + # TODO I suppose here that tap is hv side !!! + # otherwise reverse the tap here + vn_kv_2 = pp_grid.bus.loc[el_pp["lv_bus"]]["vn_kv"] + vn_kv_1 = pp_grid.bus.loc[el_pp["hv_bus"]]["vn_kv"] + if np.isfinite(el_pp["tap_step_percent"]): + vn_kv_1 *= (1. + el_pp["tap_pos"] * el_pp["tap_step_percent"] / 100.) + + # trafo r and trafo x + vn_lv_kv = el_pp["vn_lv_kv"] + # parallel = 1 + # sn_mva = 1. + # sn_trafo_mva = 1. + # vk_percent = el_pp["vk_percent"] + # vkr_percent = el_pp["vkr_percent"] + # vn_lv = el_pp["vn_lv_kv"] + # sn = 1. + # tap_lv = np.square(vn_trafo_lv[el] / vn_lv) * sn_mva + # parallel = 1 + + # # r and x + # z_sc = vk_percent / 100. / sn_trafo_mva * tap_lv + # trafo_r = vkr_percent / 100. / sn_trafo_mva * tap_lv + # trafo_x = np.sign(z_sc) * np.sqrt((z_sc ** 2 - trafo_r ** 2).astype(float)) + + # # y which is needed for b and g + # baseR = np.square(vn_lv) / sn_mva + # pfe = el_pp["pfe_kw"] * 1e-3 + # vnl_squared = vn_lv_kv ** 2 + # b_real = pfe / vnl_squared * baseR + # i0 = el_pp["i0_percent"] + # b_img = (i0 / 100. * sn) ** 2 - pfe ** 2 + # if b_img < 0: + # b_img = 0. + # b_img = np.sqrt(b_img) * baseR / vnl_squared + # y = - b_real * 1j - b_img * np.sign(i0) + + # # then convert star to pi + # trafo_r, trafo_x, y =_wye_delta(np.array([trafo_r]), np.array([trafo_x]), np.array([y])) + trafo_to_pu = 1. / sn_mva + trafo_r = r[el] + trafo_x = x[el] + + pypo_grid.create_2_windings_transformers(id=id_, + name=nm_, + voltage_level1_id=get_voltage_level_id(el_pp['hv_bus'], nb_dig_bus), + bus1_id=get_bus_id(el_pp["hv_bus"], nb_dig_bus), + voltage_level2_id=get_voltage_level_id(el_pp['lv_bus'], nb_dig_bus), + bus2_id=get_bus_id(el_pp["lv_bus"], nb_dig_bus), + r=trafo_r * trafo_to_pu, + x=trafo_x * trafo_to_pu, + g=y[el].real / trafo_to_pu, + b=y[el].imag / trafo_to_pu, + rated_u1=vn_kv_1, + rated_u2=vn_kv_2 + ) + + +# check that grid are equals +ls_grid_pp = init_from_pp(pp_grid) +ls_grid_pypo = init_from_pypo(pypo_grid, + gen_slack_id=np.where(pp_grid.gen["slack"])[0], + sn_mva=1.) + +# check the elements are consistent +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_buses(), ls_grid_pypo.get_buses())): + assert el_pp == el_pypo, f"error for {i}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_loads(), ls_grid_pypo.get_loads())): + assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}" + assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}" + assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_generators(), ls_grid_pypo.get_generators())): + assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}" + assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}" + assert el_pp.target_vm_pu == el_pypo.target_vm_pu, f"error for {i}" + assert el_pp.is_slack == el_pypo.is_slack, f"error for {i}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_storages(), ls_grid_pypo.get_storages())): + assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}" + assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}" + assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_shunts(), ls_grid_pypo.get_shunts())): + assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}" + assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}" + assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_lines(), ls_grid_pypo.get_lines())): + assert np.allclose(el_pp.bus_or_id, el_pypo.bus_or_id), f"error for {i}" + assert np.allclose(el_pp.bus_ex_id, el_pypo.bus_ex_id), f"error for {i}" + assert np.allclose(el_pp.r_pu, el_pypo.r_pu), f"error for {i}: {el_pp.r_pu} vs {el_pypo.r_pu}" + assert np.allclose(el_pp.x_pu, el_pypo.x_pu), f"error for {i}: {el_pp.x_pu} vs {el_pypo.x_pu}" + assert np.allclose(el_pp.h_pu, el_pypo.h_pu), f"error for {i}: {el_pp.h_pu} vs {el_pypo.h_pu}" + assert np.allclose(el_pp.h_or_pu, el_pypo.h_or_pu), f"error for {i}: {el_pp.h_or_pu} vs {el_pypo.h_or_pu}" + assert np.allclose(el_pp.h_ex_pu, el_pypo.h_ex_pu), f"error for {i}: {el_pp.h_ex_pu} vs {el_pypo.h_ex_pu}" + +for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_trafos(), ls_grid_pypo.get_trafos())): + assert np.allclose(el_pp.bus_lv_id, el_pypo.bus_lv_id), f"error for {i}" + assert np.allclose(el_pp.bus_hv_id, el_pypo.bus_hv_id), f"error for {i}" + assert np.allclose(el_pp.ratio, el_pypo.ratio), f"error for {i}: {el_pp.ratio} vs {el_pypo.ratio}" + assert np.allclose(el_pp.shift_rad, el_pypo.shift_rad), f"error for {i}: {el_pp.shift_rad} vs {el_pypo.shift_rad}" + assert np.allclose(el_pp.r_pu, el_pypo.r_pu), f"error for {i}: {el_pp.r_pu} vs {el_pypo.r_pu}" + assert np.allclose(el_pp.x_pu, el_pypo.x_pu), f"error for {i}: {el_pp.x_pu} vs {el_pypo.x_pu}" + assert np.allclose(el_pp.h_pu, el_pypo.h_pu), f"error for {i}: {el_pp.h_pu} vs {el_pypo.h_pu}" + +# check powerflow is the same +V_init_pp = np.zeros(len(ls_grid_pp.get_buses()), dtype=complex) * 1.06 +V_init_pypo = np.zeros(len(ls_grid_pypo.get_buses()), dtype=complex) * 1.06 +V_pp = ls_grid_pp.ac_pf(1. * V_init_pp, 10, 1e-8) +V_pypo = ls_grid_pypo.ac_pf(1. * V_init_pypo, 10, 1e-8) +assert np.allclose(V_pp[:env.n_sub], V_pypo[:env.n_sub]) +V_pp = ls_grid_pp.dc_pf(1. * V_init_pp, 10, 1e-8) +V_pypo = ls_grid_pypo.dc_pf(1. * V_init_pypo, 10, 1e-8) +assert np.allclose(V_pp[:env.n_sub], V_pypo[:env.n_sub]) + +# TODO dc_lines, static generators + +# and now save the grid +pypo_grid.dump("grid.xiidm") diff --git a/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json b/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json new file mode 100644 index 00000000..da831744 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json @@ -0,0 +1,58 @@ +{ + "0": { + "NO_OVERFLOW_DISCONNECTION": true, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 9999, + "NB_TIMESTEP_COOLDOWN_SUB": 0, + "NB_TIMESTEP_COOLDOWN_LINE": 0, + "HARD_OVERFLOW_THRESHOLD": 9999, + "NB_TIMESTEP_RECONNECTION": 0, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "1": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 6, + "NB_TIMESTEP_COOLDOWN_SUB": 0, + "NB_TIMESTEP_COOLDOWN_LINE": 0, + "HARD_OVERFLOW_THRESHOLD": 3.0, + "NB_TIMESTEP_RECONNECTION": 1, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "2": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 3, + "NB_TIMESTEP_COOLDOWN_SUB": 1, + "NB_TIMESTEP_COOLDOWN_LINE": 1, + "HARD_OVERFLOW_THRESHOLD": 2.5, + "NB_TIMESTEP_RECONNECTION": 6, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + }, + "competition": { + "NO_OVERFLOW_DISCONNECTION": false, + "NB_TIMESTEP_OVERFLOW_ALLOWED": 3, + "NB_TIMESTEP_COOLDOWN_SUB": 3, + "NB_TIMESTEP_COOLDOWN_LINE": 3, + "HARD_OVERFLOW_THRESHOLD": 2.0, + "NB_TIMESTEP_RECONNECTION": 12, + "IGNORE_MIN_UP_DOWN_TIME": true, + "ALLOW_DISPATCH_GEN_SWITCH_OFF": true, + "ENV_DC": false, + "FORECAST_DC": false, + "MAX_SUB_CHANGED": 1, + "MAX_LINE_STATUS_CHANGED": 1 + } +} diff --git a/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm b/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm new file mode 100644 index 00000000..d2eedb6a --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json b/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json new file mode 100644 index 00000000..e1534647 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json @@ -0,0 +1,58 @@ +{ + "sub_0": [ + -280.0, + -81.0 + ], + "sub_1": [ + -100.0, + -270.0 + ], + "sub_2": [ + 366.0, + -270.0 + ], + "sub_3": [ + 366.0, + -54.0 + ], + "sub_4": [ + -64.0, + -54.0 + ], + "sub_5": [ + -64.0, + 54.0 + ], + "sub_6": [ + 450.0, + 0.0 + ], + "sub_7": [ + 550.0, + 0.0 + ], + "sub_8": [ + 326.0, + 54.0 + ], + "sub_9": [ + 222.0, + 108.0 + ], + "sub_10": [ + 79.0, + 162.0 + ], + "sub_11": [ + -170.0, + 270.0 + ], + "sub_12": [ + -64.0, + 270.0 + ], + "sub_13": [ + 222.0, + 216.0 + ] +} diff --git a/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv b/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv new file mode 100644 index 00000000..0c1159a0 --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv @@ -0,0 +1,7 @@ +Pmax,Pmin,name,type,bus,max_ramp_up,max_ramp_down,min_up_time,min_down_time,marginal_cost,shut_down_cost,start_cost,x,y,V +140,0.0,gen_1_0,nuclear,1,5,5,96,96,40,10,20,180,10,142.1 +120,0.0,gen_2_1,thermal,2,10,10,4,4,70,1,2,646,10,142.1 +70,0.0,gen_5_2,wind,5,0,0,0,0,0,0,0,216,334,22.0 +70,0.0,gen_5_3,solar,5,0,0,0,0,0,0,0,216,334,22.0 +40,0.0,gen_7_4,solar,7,0,0,0,0,0,0,0,718,280,13.2 +100,0.0,gen_0_5,hydro,0,15,15,4,4,70,1,2,0,199,142.1 diff --git a/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv b/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv new file mode 100644 index 00000000..0bb5168f --- /dev/null +++ b/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv @@ -0,0 +1,3 @@ +Emax,Emin,name,type,max_p_prod,max_p_absorb,marginal_cost,power_loss,charging_efficiency,discharging_efficiency +15,0,storage_5_0,battery,5,5,20,0.1,0.95,1 +7,0,storage_7_1,battery,10,10,20,0.1,1,0.9 diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py index 9ee745df..5e41f080 100644 --- a/lightsim2grid/tests/test_backend_pypowsybl.py +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -12,7 +12,17 @@ from lightsim2grid import LightSimBackend import grid2op +try: + from grid2op._create_test_suite import create_test_suite + CAN_DO_TEST_SUITE = True +except ImportError as exc_: + CAN_DO_TEST_SUITE = False + +def _aux_get_loader_kwargs(): + return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 6} + + class BackendTester(unittest.TestCase): """issue is still not replicated and these tests pass""" def setUp(self) -> None: @@ -27,12 +37,9 @@ def _aux_prep_backend(self, backend): backend.load_redispacthing_data(self.path) backend.assert_grid_correct() - def _aux_get_loader_kwargs(self): - return {"use_buses_for_sub": True, "double_bus_per_sub": True} - def test_load_grid(self): backend = LightSimBackend(loader_method="pypowsybl", - loader_kwargs=self._aux_get_loader_kwargs()) + loader_kwargs=_aux_get_loader_kwargs()) self._aux_prep_backend(backend) cls = type(backend) assert cls.n_line == 20, f"wrong number of line {cls.n_line} vs 20" @@ -49,7 +56,7 @@ def test_load_grid(self): assert backend.nb_bus_total == 28 def test_runpf(self): - backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=self._aux_get_loader_kwargs()) + backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=_aux_get_loader_kwargs()) self._aux_prep_backend(backend) # AC powerflow conv, exc_ = backend.runpf() @@ -57,7 +64,46 @@ def test_runpf(self): # DC powerflow conv, exc_ = backend.runpf(is_dc=True) assert conv - + +if CAN_DO_TEST_SUITE: + dir_path = os.path.dirname(os.path.realpath(__file__)) + path_case_14_storage_iidm = os.path.join(dir_path, "case_14_storage_iidm") + from grid2op.tests.aaa_test_backend_interface import AAATestBackendAPI + + class TestBackendAPI_PyPoBk(AAATestBackendAPI, unittest.TestCase): + def get_path(self): + return path_case_14_storage_iidm + + def get_casefile(self): + return "grid.xiidm" + + def make_backend(self, detailed_infos_for_cascading_failures=False): + return LightSimBackend(loader_method="pypowsybl", + loader_kwargs=_aux_get_loader_kwargs(), + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + + # # add test of grid2op for the backend based on pypowsybl + # def this_make_backend(self, detailed_infos_for_cascading_failures=False): + # return LightSimBackend( + # loader_method="pypowsybl", + # loader_kwargs=_aux_get_loader_kwargs(), + # detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures + # ) + # add_name_cls = "test_LightSimBackend_pypowsybl" + + # def get_path_test_api(self): + # return path + + # def get_casefile(self): + # return "grid.xiidm" + + # res = create_test_suite(make_backend_fun=this_make_backend, + # add_name_cls=add_name_cls, + # add_to_module=__name__, + # extended_test=False, # for now keep `extended_test=False` until all problems are solved + # get_paths={"AAATestBackendAPI": get_path_test_api}, + # get_casefiles={"AAATestBackendAPI": get_casefile} + # ) # TODO env tester if __name__ == "__main__": unittest.main() diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index ff38cc10..36611ee0 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -112,8 +112,9 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix return false; } - if(!Va_dc_without_slack.array().allFinite()){ + if(!Va_dc_without_slack.array().allFinite() || (Va_dc_without_slack.lpNorm() >= 1e6)){ // for convergence, all values should be finite + // and it's not realistic if some Va are too high err_ = ErrorType::SolverSolve; V = CplxVect(); V_ = CplxVect(); diff --git a/src/DataConverter.cpp b/src/DataConverter.cpp index 3390d2fe..49c5f52c 100644 --- a/src/DataConverter.cpp +++ b/src/DataConverter.cpp @@ -129,7 +129,7 @@ std::tuple Date: Wed, 18 Oct 2023 17:00:16 +0200 Subject: [PATCH 05/66] fixing a bug in shunt introduced by refactoring with pypowsybl --- lightsim2grid/lightSimBackend.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 5e99f0c2..01aad338 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -465,7 +465,7 @@ def _load_grid_pypowsybl(self, path=None, filename=None): self.__nb_powerline = len(self._grid.get_lines()) self.__nb_bus_before = len(self._grid.get_buses()) - self.shunts_data_available = True + type(self).shunts_data_available = True # init this self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) @@ -593,18 +593,16 @@ def _load_grid_pandapower(self, path=None, filename=None): tmp.reshape(-1, 1)), axis=-1) self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)] - - self._compute_pos_big_topo() - 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 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._compute_pos_big_topo() 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 self._aux_finish_setup_after_reading() @@ -655,7 +653,8 @@ def _aux_finish_setup_after_reading(self): self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int).reshape(-1) self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int).reshape(-1) - if self.shunts_data_available: + + if type(self).shunts_data_available: self.shunt_topo_vect = np.ones(cls.n_shunt, dtype=dt_int) # shunts self.sh_p = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1) @@ -760,7 +759,7 @@ def _count_object_per_bus(self): 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: + if type(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) @@ -800,7 +799,7 @@ def apply_action(self, backendAction): backendAction.storage_power.values) # handle shunts - if self.shunts_data_available: + if type(self).shunts_data_available: shunt_p, shunt_q, shunt_bus = backendAction.shunt_p, backendAction.shunt_q, backendAction.shunt_bus # shunt topology # (need to be done before to avoid error like "impossible to set reactive value of a disconnected shunt") @@ -868,7 +867,7 @@ def runpf(self, is_dc=False): self.comp_time += self._grid.get_dc_computation_time() else: self.comp_time += self._grid.get_computation_time() - + self.V[:] = V (self.p_or[:self.__nb_powerline], self.q_or[:self.__nb_powerline], @@ -915,7 +914,7 @@ def runpf(self, is_dc=False): raise DivergingPowerFlow(f"At least one generator is disconnected (check gen {gen_disco})") # TODO storage case of divergence ! - if self.shunts_data_available: + if type(self).shunts_data_available: self._set_shunt_info() self._fill_theta() @@ -963,7 +962,7 @@ def _fill_nans(self): self.load_theta[:] = np.NaN self.gen_theta[:] = np.NaN - if self.shunts_data_available: + if type(self).shunts_data_available: self.sh_p[:] = np.NaN self.sh_q[:] = np.NaN self.sh_v[:] = np.NaN @@ -1060,7 +1059,7 @@ def copy(self): ] + type(self)._li_attr_disp for attr_nm in cls_attr: - if hasattr(self, attr_nm): + if hasattr(self, attr_nm) and not hasattr(type(self), attr_nm): # this test is needed for backward compatibility with other grid2op version setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm))) ############### @@ -1124,7 +1123,7 @@ def shunt_info(self): def _set_shunt_info(self): self.sh_p[:], self.sh_q[:], self.sh_v[:] = 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) + shunt_bus = np.array([self._grid.get_bus_shunt(i) for i in range(type(self).n_shunt)], dtype=dt_int) res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int) # by default all shunts are on bus one res_bus[shunt_bus >= self.__nb_bus_before] = 2 # except the one that are connected to bus 2 res_bus[shunt_bus == -1] = -1 # or the one that are disconnected From 7a91699aabb8d972cd563a8a4714bf7748b90272 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Wed, 18 Oct 2023 17:20:30 +0200 Subject: [PATCH 06/66] fixing a bug in shunt introduced by refactoring with pypowsybl --- lightsim2grid/lightSimBackend.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 01aad338..0ad925da 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -35,6 +35,9 @@ class LightSimBackend(Backend): This is a specialization of the grid2op Backend class to use the lightsim2grid solver, coded in c++, aiming at speeding up the computations. """ + + shunts_data_available = True + def __init__(self, detailed_infos_for_cascading_failures: bool=False, can_be_copied: bool=True, From e86bca8500458e4c9ae52383280df24ce3496abf Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 19 Oct 2023 14:41:38 +0200 Subject: [PATCH 07/66] fixing some more tests --- lightsim2grid/gridmodel/from_pypowsybl.py | 111 +++++++++++---- lightsim2grid/gridmodel/initGridModel.py | 2 +- lightsim2grid/lightSimBackend.py | 3 +- lightsim2grid/tests/test_LightSimBackend.py | 29 +++- lightsim2grid/tests/test_basic_backend_api.py | 3 +- .../tests/test_init_from_pypowsybl.py | 134 +++++++++--------- src/GridModel.cpp | 6 +- src/GridModel.h | 2 +- src/main.cpp | 2 +- 9 files changed, 187 insertions(+), 105 deletions(-) diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index ee23402e..2a7f55ef 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -6,14 +6,19 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. +import copy import numpy as np import pypowsybl as pypo +# import pypowsybl.loadflow as lf + from lightsim2grid_cpp import GridModel def init(net : pypo.network, gen_slack_id: int = None, + slack_bus_id: int = None, sn_mva = 100., + sort_index=True, f_hz = 50.): model = GridModel() # for substation @@ -21,48 +26,74 @@ def init(net : pypo.network, # network.get_substations() # network.get_busbar_sections() + if gen_slack_id is not None and slack_bus_id is not None: + raise RuntimeError("Impossible to intialize a grid with both gen_slack_id and slack_bus_id") + # assign unique id to the buses - bus_df = net.get_buses().sort_index().copy() + bus_df_orig = net.get_buses() + if sort_index: + bus_df = bus_df_orig.sort_index() + else: + bus_df = bus_df_orig bus_df["bus_id"] = np.arange(bus_df.shape[0]) + bus_df_orig["bus_id"] = bus_df.loc[bus_df_orig.index]["bus_id"] + model._ls_to_orig = 1 * bus_df_orig["bus_id"].values + voltage_levels = net.get_voltage_levels() model.set_sn_mva(sn_mva) model.set_init_vm_pu(1.06) - model.init_bus(net.get_voltage_levels().loc[bus_df["voltage_level_id"].values]["nominal_v"].values, + model.init_bus(voltage_levels.loc[bus_df["voltage_level_id"].values]["nominal_v"].values, 0, 0 # unused ) # do the generators - df_gen = net.get_generators().sort_index() + if sort_index: + df_gen = net.get_generators().sort_index() + else: + 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["min_q"].values.astype(np.float32) - # to handle encoding in 32 bits and overflow when "splitting" the Q values among generators + max_q = df_gen["max_q"].values.astype(np.float32) min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min / 2. + 1. max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max / 2. - 1. model.init_generators(df_gen["target_p"].values, - df_gen["target_v"].values / net.get_voltage_levels().loc[df_gen["voltage_level_id"].values]["nominal_v"].values, + df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values, min_q, max_q, 1 * bus_df.loc[df_gen["bus_id"].values]["bus_id"].values ) # TODO dist slack - if gen_slack_id is None: - model.add_gen_slackbus(0, 1.) - else: + if gen_slack_id is not None: model.add_gen_slackbus(gen_slack_id, 1.) - + elif slack_bus_id is not None: + gen_bus = np.array([el.bus_id for el in model.get_generators()]) + gen_is_conn_slack = gen_bus == model._ls_to_orig[slack_bus_id] + nb_conn = gen_is_conn_slack.sum() + if nb_conn == 0: + raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack") + for gen_id, is_slack in enumerate(gen_is_conn_slack): + if is_slack: + model.add_gen_slackbus(gen_id, 1. / nb_conn) + else: + model.add_gen_slackbus(0, 1.) + # for loads - df_load = net.get_loads().sort_index() + if sort_index: + df_load = net.get_loads().sort_index() + else: + df_load = net.get_loads() model.init_loads(df_load["p0"].values, df_load["q0"].values, 1 * bus_df.loc[df_load["bus_id"].values]["bus_id"].values ) # for lines - df_line = net.get_lines().sort_index() - # TODO add g1 / b1 and g2 / b2 in lightsim2grid - # line_h = (1j*df_line["g1"].values + df_line["b1"].values + 1j*df_line["g2"].values + df_line["b2"].values) + if sort_index: + df_line = net.get_lines().sort_index() + else: + df_line = net.get_lines() # per unit - branch_from_kv = net.get_voltage_levels().loc[df_line["voltage_level1_id"].values]["nominal_v"].values - branch_to_kv = net.get_voltage_levels().loc[df_line["voltage_level2_id"].values]["nominal_v"].values + branch_from_kv = voltage_levels.loc[df_line["voltage_level1_id"].values]["nominal_v"].values + branch_to_kv = voltage_levels.loc[df_line["voltage_level2_id"].values]["nominal_v"].values # only valid for lines with same voltages at both side... # branch_from_pu = branch_from_kv * branch_from_kv / sn_mva @@ -94,7 +125,10 @@ def init(net : pypo.network, ) # for trafo - df_trafo = net.get_2_windings_transformers().sort_index() + if sort_index: + df_trafo = net.get_2_windings_transformers().sort_index() + else: + df_trafo = net.get_2_windings_transformers() # TODO net.get_ratio_tap_changers() # TODO net.get_phase_tap_changers() shift_ = np.zeros(df_trafo.shape[0]) @@ -102,8 +136,8 @@ def init(net : pypo.network, is_tap_hv_side = np.ones(df_trafo.shape[0], dtype=bool) # TODO # per unit - trafo_from_kv = net.get_voltage_levels().loc[df_trafo["voltage_level1_id"].values]["nominal_v"].values - trafo_to_kv = net.get_voltage_levels().loc[df_trafo["voltage_level2_id"].values]["nominal_v"].values + trafo_from_kv = voltage_levels.loc[df_trafo["voltage_level1_id"].values]["nominal_v"].values + trafo_to_kv = voltage_levels.loc[df_trafo["voltage_level2_id"].values]["nominal_v"].values trafo_to_pu = trafo_to_kv * trafo_to_kv / sn_mva # tap tap_step_pct = (df_trafo["rated_u1"] / trafo_from_kv - 1.) * 100. @@ -121,16 +155,34 @@ def init(net : pypo.network, 1 * bus_df.loc[df_trafo["bus2_id"].values]["bus_id"].values) # for shunt - df_shunt = net.get_shunt_compensators().sort_index() - shunt_kv = net.get_voltage_levels().loc[df_shunt["voltage_level_id"].values]["nominal_v"].values + if sort_index: + df_shunt = net.get_shunt_compensators().sort_index() + else: + df_shunt = net.get_shunt_compensators() + + is_on = copy.deepcopy(df_shunt["connected"]) + if (~is_on).any(): + df_shunt["connected"] = True + net.update_shunt_compensators(df_shunt[["connected"]]) + if sort_index: + df_shunt = net.get_shunt_compensators().sort_index() + else: + df_shunt = net.get_shunt_compensators() + df_shunt["connected"] = is_on + net.update_shunt_compensators(df_shunt[["connected"]]) + + shunt_kv = voltage_levels.loc[df_shunt["voltage_level_id"].values]["nominal_v"].values model.init_shunt(-df_shunt["g"].values * shunt_kv**2, -df_shunt["b"].values * shunt_kv**2, 1 * bus_df.loc[df_shunt["bus_id"].values]["bus_id"].values ) - + for shunt_id, conn in enumerate(is_on): + if not conn: + model.deactivate_shunt(shunt_id) + # for hvdc (TODO not tested yet) df_dc = net.get_hvdc_lines().sort_index() - df_sations = net.get_vsc_converter_stations() + df_sations = net.get_vsc_converter_stations().sort_index() bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values loss_percent = np.zeros(df_dc.shape[0]) # TODO @@ -140,8 +192,8 @@ def init(net : pypo.network, df_dc["target_p"].values, loss_percent, loss_mw, - net.get_voltage_levels().loc[df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values]["nominal_v"].values, - net.get_voltage_levels().loc[df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values]["nominal_v"].values, + voltage_levels.loc[df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values]["nominal_v"].values, + voltage_levels.loc[df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values]["nominal_v"].values, df_sations.loc[df_dc["converter_station1_id"].values]["min_q"].values, df_sations.loc[df_dc["converter_station1_id"].values]["max_q"].values, df_sations.loc[df_dc["converter_station2_id"].values]["min_q"].values, @@ -149,15 +201,20 @@ def init(net : pypo.network, ) # storage units (TODO not tested yet) - df_batt = net.get_batteries().sort_index() + if sort_index: + df_batt = net.get_batteries().sort_index() + else: + df_batt = net.get_batteries() model.init_storages(df_batt["target_p"].values, df_batt["target_q"].values, 1 * bus_df.loc[df_batt["bus_id"].values]["bus_id"].values ) # TODO - # sgen + # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO # TODO checks # no 3windings trafo and other exotic stuff + if net.get_phase_tap_changers().shape[0] > 0: + raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") return model diff --git a/lightsim2grid/gridmodel/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py index 8a6840c2..32def861 100644 --- a/lightsim2grid/gridmodel/initGridModel.py +++ b/lightsim2grid/gridmodel/initGridModel.py @@ -81,7 +81,7 @@ def init(pp_net): tmp_bus_ind = np.argsort(pp_net.bus.index) if np.any(np.sort(pp_net.bus.index) != np.arange(pp_net.bus.shape[0])): - model._ls_to_pp = 1 * pp_net.bus.index.values.astype(int) + model._ls_to_orig = 1 * pp_net.bus.index.values.astype(int) pp_to_ls = {pp_bus: ls_bus for pp_bus, ls_bus in zip(pp_net.bus.index, tmp_bus_ind)} else: pp_to_ls = None diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 0ad925da..8d586bba 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -79,6 +79,8 @@ def __init__(self, self._loader_method = loader_method self._loader_kwargs = loader_kwargs + self.shunts_data_available = True # needs to be self and not type(self) here + self.nb_bus_total = None self.initdc = True # does not really hurt computation time self.__nb_powerline = None @@ -468,7 +470,6 @@ def _load_grid_pypowsybl(self, path=None, filename=None): self.__nb_powerline = len(self._grid.get_lines()) self.__nb_bus_before = len(self._grid.get_buses()) - type(self).shunts_data_available = True # init this self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) diff --git a/lightsim2grid/tests/test_LightSimBackend.py b/lightsim2grid/tests/test_LightSimBackend.py index 2cae0ba0..f1a8a627 100644 --- a/lightsim2grid/tests/test_LightSimBackend.py +++ b/lightsim2grid/tests/test_LightSimBackend.py @@ -19,7 +19,33 @@ from grid2op.Space import GridObjects # lazy import __has_storage = hasattr(GridObjects, "n_storage") -from grid2op.tests.helper_path_test import HelperTests +try: + # new way of doing, does not need to inherit from HelperTests but from unittest.TestCase + from grid2op._create_test_suite import create_test_suite + from grid2op.tests.helper_path_test import HelperTests as DEPRECATEDHelper + + class _Garbage: + def setUp(self): + pass + + class _SuperGarbage(DEPRECATEDHelper, _Garbage): + pass + + _garbage = _SuperGarbage() + _garbage.setUp() + + class HelperTests(unittest.TestCase): + def setUp(self) -> None: + self.tol_one = _garbage.tol_one + self.tolvect = _garbage.tolvect + return super().setUp() + + def tearDown(self) -> None: + return super().tearDown() + +except ImportError as exc_: + # old way of doing, need to inherit from that + from grid2op.tests.helper_path_test import HelperTests from grid2op.tests.BaseBackendTest import BaseTestNames, BaseTestLoadingCase, BaseTestLoadingBackendFunc from grid2op.tests.BaseBackendTest import BaseTestTopoAction, BaseTestEnvPerformsCorrectCascadingFailures from grid2op.tests.BaseBackendTest import BaseTestChangeBusAffectRightBus, BaseTestShuntAction @@ -37,6 +63,7 @@ from lightsim2grid.solver import SolverType from grid2op.Runner import Runner + class TestNames(HelperTests, BaseTestNames): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): diff --git a/lightsim2grid/tests/test_basic_backend_api.py b/lightsim2grid/tests/test_basic_backend_api.py index 318edc00..c404bad7 100644 --- a/lightsim2grid/tests/test_basic_backend_api.py +++ b/lightsim2grid/tests/test_basic_backend_api.py @@ -29,8 +29,7 @@ def this_make_backend(self, detailed_infos_for_cascading_failures=False): extended_test=False, # for now keep `extended_test=False` until all problems are solved ) else: - import warnings - warnings.warn("Have you installed grid2op in dev / editable mode ? We cannot make the `create_test_suite` :-(") + print("Have you installed grid2op in dev / editable mode ? We cannot make the `create_test_suite` :-(") # and run it with `python -m unittest gridcal_backend_tests.py` if __name__ == "__main__": diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 97528f56..76e6c1d4 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -22,11 +22,9 @@ class AuxInitFromPyPowSyBl: def get_pypo_grid(self): return pp.network.create_ieee14() - - def get_gen_slack_id(self): - return 0 def get_slackbus_id(self): + # id in pandapower return 0 def get_equiv_pdp_grid(self): @@ -57,7 +55,7 @@ def setUp(self) -> None: self.ref_samecase = None # init lightsim2grid model - self.gridmodel = init(self.network_ref, gen_slack_id=self.get_gen_slack_id()) + self.gridmodel = init(self.network_ref, slack_bus_id=self.get_slackbus_id()) # use some data self.nb_bus_total = self.network_ref.get_buses().shape[0] @@ -86,17 +84,22 @@ def test_compare_pp(self): v_ls = self.gridmodel.dc_pf(1.0 * self.V_init_dc, 2, self.tol) v_ls_ref = self.ref_samecase.dc_pf(1.0 * self.V_init_dc, 2, self.tol) slack_id = self.get_slackbus_id() - # (array([ 30, 31, 212, 218, 265]), array([265, 31, 212, 218, 30])) + reorder = self.gridmodel._ls_to_orig.reshape(1, -1) # for case 118 - # bus_or = [64] - # bus_ex = [67] - # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)] + # reorder_flat = reorder.reshape(-1) + # bus_or = [64] # pandapower + # bus_ex = [67] # pandapower + # lines = [el.id for el in self.gridmodel.get_lines() if (reorder_flat[el.bus_or_id] in bus_or and reorder_flat[el.bus_ex_id] in bus_ex) or (reorder_flat[el.bus_or_id] in bus_ex and reorder_flat[el.bus_ex_id] in bus_or)] + # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in reorder_flat[bus_or] and el.bus_ex_id in reorder_flat[bus_ex]) or (el.bus_or_id in reorder_flat[bus_ex] and el.bus_ex_id in reorder_flat[bus_or])] + # tmp_or = [reorder_flat[el.bus_or_id] for el in self.gridmodel.get_lines()] + # tmp_ex = [reorder_flat[el.bus_ex_id] for el in self.gridmodel.get_lines()] + # lines = [el.id for el in self.gridmodel.get_trafos() if (reorder_flat[el.bus_hv_id] in bus_or and reorder_flat[el.bus_lv_id] in bus_ex) or (reorder_flat[el.bus_hv_id] in bus_ex and reorder_flat[el.bus_lv_id] in bus_or)] + # tmp_or = [reorder_flat[el.bus_hv_id] for el in self.gridmodel.get_trafos()] + # tmp_ex = [reorder_flat[el.bus_lv_id] for el in self.gridmodel.get_trafos()] # lines_ref = [el.id for el in self.ref_samecase.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)] # if not lines_ref: # lines_ref = [el.id for el in self.ref_samecase.get_trafos() if (el.bus_hv_id in bus_or and el.bus_lv_id in bus_ex) or (el.bus_hv_id in bus_ex and el.bus_lv_id in bus_or)] - # import pdb - # pdb.set_trace() # # self.pp_samecase["_ppc"]["internal"]["Bbus"] # self.pp_samecase["_ppc"]["internal"]["Ybus"][64,67] # self.pp_samecase["_ppc"]["internal"]["bus"] @@ -114,61 +117,63 @@ 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"] - - assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for dc" - tmp_ = self.gridmodel.get_dcYbus() - self.ref_samecase.get_dcYbus() - assert np.abs(tmp_).max() <= self.tol_eq, "error for dcYbus" + max_ = np.abs(v_ls[reorder] - v_ls_ref).max() + # assert max_ <= self.tol_eq, f"error for vresults for dc: {max_:.2e}" + tmp_ = self.gridmodel.get_dcYbus()[reorder.T, reorder].todense() - self.ref_samecase.get_dcYbus().todense() + max_ = np.abs(tmp_).max() + mat_ls = self.gridmodel.get_dcYbus()[reorder.T, reorder].todense() + mat_pp = self.ref_samecase.get_dcYbus().todense() + assert max_ <= self.tol_eq, f"error for dcYbus: {max_:.2e}" # check Sbus without slack + Sbus_ordered = self.gridmodel.get_Sbus()[reorder].reshape(-1) if slack_id > 0: - assert np.abs(self.gridmodel.get_Sbus()[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() <= self.tol_eq, "error for dc Sbus" + max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() + assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}" if slack_id != self.gridmodel.get_Sbus().shape[0] - 1: - assert np.abs(self.gridmodel.get_Sbus()[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() <= self.tol_eq, "error for dc Sbus" + max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() + assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}" # same in AC v_ls = self.gridmodel.ac_pf(self.V_init_ac, 2, self.tol) v_ls_ref = self.ref_samecase.ac_pf(self.V_init_ac, 2, self.tol) - assert np.abs(self.gridmodel.get_Ybus() - self.ref_samecase.get_Ybus()).max() <= self.tol_eq, "error for Ybus" + max_ = np.abs(self.gridmodel.get_Ybus()[reorder.T, reorder] - self.ref_samecase.get_Ybus()).max() + assert max_ <= self.tol_eq, f"error for Ybus: {max_:.2e}" # check Sbus without slack + Sbus_ordered = self.gridmodel.get_Sbus()[reorder].reshape(-1) if slack_id > 0: - assert np.abs(self.gridmodel.get_Sbus()[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() <= self.tol_eq, "error for dc Sbus" + max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() + assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}" if slack_id != self.gridmodel.get_Sbus().shape[0] - 1: - assert np.abs(self.gridmodel.get_Sbus()[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() <= self.tol_eq, "error for dc Sbus" + max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() + assert max_ <= self.tol_eq, f"error for dc Sbus : {max_:.2e}" def test_dc_pf(self): """test I get the same results as pandapower in dc""" v_ls = self.gridmodel.dc_pf(self.V_init_dc, 2, self.tol) + reorder = self.gridmodel._ls_to_orig.reshape(1, -1) if self.compare_pp(): v_ls_ref = self.ref_samecase.dc_pf(self.V_init_dc, 2, self.tol) - assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for dc" + assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for dc: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}" lf.run_dc(self.network_ref) if self.compare_pp(): pdp.rundcpp(self.pp_samecase) - # for case 300 - # np.where(np.abs(v_ang_ls - v_ang_pypo) > 3) # => 173, 177 - # bus_or = [173] - # bus_ex = [177] - # # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)] - # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or or el.bus_or_id in bus_ex or el.bus_ex_id in bus_or or el.bus_ex_id in bus_ex)] - # trafos = [el.id for el in self.gridmodel.get_trafos() if (el.bus_hv_id in bus_or or el.bus_hv_id in bus_ex or el.bus_lv_id in bus_or or el.bus_lv_id in bus_ex)] - # shunts = [el.id for el in self.gridmodel.get_shunts() if el.bus_id in bus_or or el.bus_id in bus_ex] - # trafo 77 => 204 2040 in cdf file - # trafo 85 => - # v_mag not really relevant in dc so i study only va v_ang_pypo = self.network_ref.get_buses()["v_angle"].values v_ang_ls = np.rad2deg(np.angle(v_ls)) + # np.where(np.abs(v_ang_ls[reorder] - v_ang_pypo) >= 9.) if self.compare_pp(): v_ang_pp = self.pp_samecase.res_bus["va_degree"].values - assert np.abs(v_ang_ls - v_ang_pp).max() <= self.tol_eq, "error for va results for dc" - assert np.abs(v_ang_ls - v_ang_pypo).max() <= self.tol_eq + assert np.abs(v_ang_ls[reorder] - v_ang_pp).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_ang_ls[reorder] - v_ang_pp).max():.2e}" + assert np.abs(v_ang_ls[reorder] - v_ang_pypo).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_ang_ls[reorder] - v_ang_pypo).max():.2e}" def test_ac_pf(self): # run the powerflows v_ls = self.gridmodel.ac_pf(1.0 * self.V_init_ac, 10, self.tol) + reorder = self.gridmodel._ls_to_orig.reshape(1, -1) if self.compare_pp(): v_ls_ref = self.ref_samecase.ac_pf(1.0 * self.V_init_ac, 10, self.tol) - assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for ac" + assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for ac: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}" param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, transformer_voltage_control_on=False, @@ -180,40 +185,43 @@ def test_ac_pf(self): "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} ) res_pypow = lf.run_ac(self.network_ref, parameters=param) + bus_ref_kv = self.network_ref.get_voltage_levels().loc[self.network_ref.get_buses()["voltage_level_id"].values]["nominal_v"].values + v_mag_pypo = self.network_ref.get_buses()["v_mag"].values / bus_ref_kv + v_ang_pypo = self.network_ref.get_buses()["v_angle"].values if self.compare_pp(): pdp.runpp(self.pp_samecase, init="flat") - + + # check that pypow solution is "feasible" as seen by lightsim2grid + if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED: + v_pypow = v_mag_pypo * np.exp(1j * np.deg2rad(v_ang_pypo)) + pypow_to_ls = np.argsort(reorder).reshape(-1) + v_pypow_ls = self.gridmodel.check_solution(v_pypow[pypow_to_ls], False) + assert np.abs(v_pypow_ls).max() <= 10. * self.tol_eq, f"error when checking results of pypowsybl in lightsim2grid: {np.abs(v_pypow_ls).max():.2e}" + # check voltage angles - v_ang_pypo = self.network_ref.get_buses()["v_angle"].values v_ang_ls = np.rad2deg(np.angle(v_ls)) if self.compare_pp(): v_ang_pp = self.pp_samecase.res_bus["va_degree"].values - self.pp_samecase.ext_grid["va_degree"].values - assert np.abs(v_ang_ls - v_ang_pp).max() <= self.tol_eq, "error for va results for ac" + assert np.abs(v_ang_ls[reorder] - v_ang_pp).max() <= self.tol_eq, f"error for va results for ac: {np.abs(v_ang_ls[reorder] - v_ang_pp).max():.2e}" if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED: - assert np.abs(v_ang_ls - v_ang_pypo).max() <= self.tol_eq + assert np.abs(v_ang_ls[reorder] - v_ang_pypo).max() <= self.tol_eq, f"error for va results for ac: {np.abs(v_ang_ls[reorder] - v_ang_pypo).max():.2e}" # check voltage magnitudes - bus_ref_kv = self.network_ref.get_voltage_levels().loc[self.network_ref.get_buses()["voltage_level_id"].values]["nominal_v"].values - v_mag_pypo = self.network_ref.get_buses()["v_mag"].values / bus_ref_kv v_mag_ls = np.abs(v_ls) if self.compare_pp(): v_mag_pp = self.pp_samecase.res_bus["vm_pu"].values - assert np.abs(v_mag_ls - v_mag_pp).max() <= self.tol_eq, "error for va results for dc" + assert np.abs(v_mag_ls[reorder] - v_mag_pp).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_mag_ls[reorder] - v_mag_pp).max():.2e}" if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED: - assert np.abs(v_mag_ls - v_mag_pypo).max() <= self.tol_eq + assert np.abs(v_mag_ls[reorder] - v_mag_pypo).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_mag_ls[reorder] - v_mag_pypo).max():.2e}" - # check that pypow solution is "feasible" as seen by lightsim2grid - if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED: - v_pypow = v_mag_pypo * np.exp(1j * np.deg2rad(v_ang_pypo)) - v_pypow_ls = self.gridmodel.check_solution(v_pypow, False) - assert np.abs(v_pypow_ls).max() <= 10. * self.tol_eq - + class TestCase14FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase): pass class TestCase30FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase): + """compare from the ieee 30""" # unittest.TestCase does not work because of https://github.com/powsybl/pypowsybl/issues/644 def get_pypo_grid(self): return pp.network.create_ieee30() @@ -223,18 +231,10 @@ def get_equiv_pdp_grid(self): class TestCase57FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase): + """compare from the ieee 57""" # does not appear to be the same grid ! def get_pypo_grid(self): res = pp.network.create_ieee57() - # df = res.get_2_windings_transformers()[["rated_u1"]] - # df["rated_u1"] = 1.0 - # res.update_2_windings_transformers(df) - - # df = res.get_lines()[["b1", "b2", "r"]] - # df["b1"] = 0. - # df["b2"] = 0. - # df["r"] = 0. - # res.update_lines(df) return res def get_equiv_pdp_grid(self): @@ -248,15 +248,13 @@ def get_tol_eq(self): class TestCase118FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase): - # unittest.TestCase does not work because of https://github.com/powsybl/pypowsybl/issues/644 + """compare from the ieee 118: does not work because of https://github.com/e2nIEE/pandapower/issues/2131""" + # does not work because of https://github.com/e2nIEE/pandapower/issues/2131 def get_pypo_grid(self): return pp.network.create_ieee118() def get_equiv_pdp_grid(self): return pn.case118() - - def get_gen_slack_id(self): - return 29 def get_slackbus_id(self): return 68 @@ -270,10 +268,14 @@ def compare_pp(self): class TestCase300FromPypo(AuxInitFromPyPowSyBl): - # does not work, probably grid not ordered the same - # need further investigation + """compare from the ieee 300""" + # does not work because of phase tap changer def get_pypo_grid(self): - return pp.network.create_ieee300() + res = pp.network.create_ieee300() + df = res.get_shunt_compensators()[["connected"]] # "b", "g", + df["connected"] = False + res.update_shunt_compensators(df) + return res def get_equiv_pdp_grid(self): return pn.case300() @@ -284,10 +286,6 @@ def get_tol_eq(self): def compare_pp(self): return False - - def get_gen_slack_id(self): - # does not work with PP, probably bus not ordered the same - return 55 def get_slackbus_id(self): # does not work with PP, probably bus not ordered the same diff --git a/src/GridModel.cpp b/src/GridModel.cpp index c10476b1..7ac04207 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -14,7 +14,7 @@ GridModel::GridModel(const GridModel & other) { reset(true, true, true); - _ls_to_pp = other._ls_to_pp; + _ls_to_orig = other._ls_to_orig; init_vm_pu_ = other.init_vm_pu_; sn_mva_ = other.sn_mva_; @@ -80,7 +80,7 @@ GridModel::GridModel(const GridModel & other) GridModel::StateRes GridModel::get_state() const { std::vector bus_vn_kv(bus_vn_kv_.begin(), bus_vn_kv_.end()); - std::vector ls_to_pp(_ls_to_pp.begin(), _ls_to_pp.end()); + std::vector ls_to_pp(_ls_to_orig.begin(), _ls_to_orig.end()); int version_major = VERSION_MAJOR; int version_medium = VERSION_MEDIUM; int version_minor = VERSION_MINOR; @@ -161,7 +161,7 @@ void GridModel::set_state(GridModel::StateRes & my_state) // assign it to this instance - _ls_to_pp =IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size()); + _ls_to_orig =IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size()); // buses // 1. bus_vn_kv_ bus_vn_kv_ = RealVect::Map(&bus_vn_kv[0], bus_vn_kv.size()); diff --git a/src/GridModel.h b/src/GridModel.h index c2fcf374..d03e1df6 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -45,7 +45,7 @@ class GridModel : public DataGeneric { public: // can be modified python side - IntVect _ls_to_pp; // for converter from bus in lightsim2grid index to bus in pandapower index + IntVect _ls_to_orig; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) public: typedef std::tuple< diff --git a/src/main.cpp b/src/main.cpp index 65048590..da169f9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -586,7 +586,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "GridModel", DocGridModel::GridModel.c_str()) .def(py::init<>()) .def("copy", &GridModel::copy) - .def_readwrite("_ls_to_pp", &GridModel::_ls_to_pp, "for converter from bus in lightsim2grid index to bus in pandapower index") + .def_readwrite("_ls_to_orig", &GridModel::_ls_to_orig, "for converter from bus in lightsim2grid index to bus index in original file format (*eg* pandapower of pypowsybl)") // pickle .def(py::pickle( From eb4410852e18a5e4bb3a49339159f2d25fbc9f5c Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 19 Oct 2023 15:20:48 +0200 Subject: [PATCH 08/66] fixing few remaining tests --- .gitignore | 1 + lightsim2grid/lightSimBackend.py | 23 ++++++++++++++++++++++- lightsim2grid/tests/test_multi_slack.py | 4 ++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 671f272f..4558f40f 100644 --- a/.gitignore +++ b/.gitignore @@ -274,3 +274,4 @@ req_test.txt test.txt test_output.txt test_pypower_fdpf.py +lightsim2grid/tests/_grid2op_for_test/ diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 8d586bba..2d502d92 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -418,7 +418,28 @@ def _load_grid_pypowsybl(self, path=None, filename=None): if self._loader_kwargs is not None: loader_kwargs = self._loader_kwargs - full_path = self.make_complete_path(path, filename) + try: + full_path = self.make_complete_path(path, filename) + except AttributeError as exc_: + warnings.warn("Please upgrade your grid2op version") + import os + from grid2op.Exceptions import Grid2OpException + def make_complete_path(path, filename): + if path is None and filename is None: + raise Grid2OpException( + "You must provide at least one of path or file to load a powergrid." + ) + if path is None: + full_path = filename + elif filename is None: + full_path = path + else: + full_path = os.path.join(path, filename) + if not os.path.exists(full_path): + raise Grid2OpException('There is no powergrid at "{}"'.format(full_path)) + return full_path + full_path = make_complete_path(path, filename) + grid_tmp = pypow_net.load(full_path) gen_slack_id = None if "gen_slack_id" in loader_kwargs: diff --git a/lightsim2grid/tests/test_multi_slack.py b/lightsim2grid/tests/test_multi_slack.py index 6a98c656..387d6bf1 100644 --- a/lightsim2grid/tests/test_multi_slack.py +++ b/lightsim2grid/tests/test_multi_slack.py @@ -77,7 +77,7 @@ def test_Ybus(self): # Ybus seen by pandapower pp.runpp(self.net, **self.get_pp_options()) Ybus_pp_tmp = self.net["_ppc"]["internal"]["Ybus"] - id_bus_pp = self.net._pd2ppc_lookups["bus"][self.gridmodel._ls_to_pp] + id_bus_pp = self.net._pd2ppc_lookups["bus"][self.gridmodel._ls_to_orig] assert (id_bus_pp == np.arange(id_bus_pp.size)).all() # assert bus are sorted correctly and in the same order # Ybus_pp = Ybus_pp_tmp[id_bus_pp, id_bus_pp] # does not work in don't know why... anyway Ybus_pp = Ybus_pp_tmp @@ -91,7 +91,7 @@ def test_Ybus(self): def test_Sbus(self): # Ybus seen by pandapower pp.runpp(self.net, **self.get_pp_options()) - bus_lookup = self.net._pd2ppc_lookups["bus"] #[self.gridmodel._ls_to_pp] + bus_lookup = self.net._pd2ppc_lookups["bus"] #[self.gridmodel._ls_to_orig] Sbus_pp = self.net["_ppc"]["internal"]["Sbus"] *_, V0 = self._aux_get_init_pp_data() # Ybus from lightsim2grid From b3c9ab28b1226f051d060d4f7e95c7aff3e57a66 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 19 Oct 2023 17:18:37 +0200 Subject: [PATCH 09/66] adressing issue #66, now reproduced --- lightsim2grid/lightSimBackend.py | 8 ++++++-- lightsim2grid/tests/test_dist_slack_backend.py | 2 +- lightsim2grid/tests/test_issue_66.py | 18 +++++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 2d502d92..6cb4945d 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -820,8 +820,12 @@ def apply_action(self, backendAction): raise BackendError(f"{exc_}") if self.__has_storage: - self._grid.update_storages_p(backendAction.storage_power.changed, - backendAction.storage_power.values) + try: + self._grid.update_storages_p(backendAction.storage_power.changed, + backendAction.storage_power.values) + except RuntimeError as exc_: + # modification of power of disconnected storage has no effect in lightsim2grid + pass # handle shunts if type(self).shunts_data_available: diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py index 5025a5c0..966c0ea3 100644 --- a/lightsim2grid/tests/test_dist_slack_backend.py +++ b/lightsim2grid/tests/test_dist_slack_backend.py @@ -98,7 +98,7 @@ def test_after_runner(self): runner_ds = Runner(**self.env_ds.get_params_for_runner()) res_ss = runner_ss.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) res_ds = runner_ds.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) - assert res_ss[0][3] == res_ds[0][3] # same number of steps survived + assert res_ss[0][3] == res_ds[0][3], f"{res_ss[0][3]} vs {res_ds[0][3]}" # same number of steps survived assert res_ss[0][2] != res_ds[0][2] # not the same reward ep_ss = res_ss[0][-1] ep_ds = res_ds[0][-1] diff --git a/lightsim2grid/tests/test_issue_66.py b/lightsim2grid/tests/test_issue_66.py index aa418733..38046139 100644 --- a/lightsim2grid/tests/test_issue_66.py +++ b/lightsim2grid/tests/test_issue_66.py @@ -9,6 +9,7 @@ import unittest import warnings from lightsim2grid import LightSimBackend +from grid2op.Action import PlayableAction import grid2op class Issue66Tester(unittest.TestCase): @@ -16,7 +17,8 @@ class Issue66Tester(unittest.TestCase): def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") - self.env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend()) + self.env = grid2op.make("educ_case14_storage", test=True, backend=LightSimBackend(), + action_class=PlayableAction) return super().setUp() def tearDown(self) -> None: @@ -67,6 +69,20 @@ def test_change_bus_gen(self): obs, reward, done, info = self.env.step(act) assert done + def test_disco_storage(self): + """test i can disconnect a storage unit""" + obs = self.env.reset() + act = self.env.action_space({"set_bus": {"storages_id": [(0, -1)]}}) + obs, reward, done, info = self.env.step(act) + assert not done + # should not raise any RuntimeError + + act = self.env.action_space({"storage_p": [(0, -1)]}) + obs, reward, done, info = self.env.step(act) + assert not done + # should not raise any RuntimeError + + if __name__ == "__main__": unittest.main() \ No newline at end of file From 655cb0616aa95403ab78564360d526ad67949341 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 20 Oct 2023 09:12:02 +0200 Subject: [PATCH 10/66] fixing the fix to issue 66 --- lightsim2grid/tests/test_issue_66.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lightsim2grid/tests/test_issue_66.py b/lightsim2grid/tests/test_issue_66.py index 38046139..be2cc08e 100644 --- a/lightsim2grid/tests/test_issue_66.py +++ b/lightsim2grid/tests/test_issue_66.py @@ -10,6 +10,7 @@ import warnings from lightsim2grid import LightSimBackend from grid2op.Action import PlayableAction +from grid2op.Rules import AlwaysLegal import grid2op class Issue66Tester(unittest.TestCase): @@ -18,7 +19,13 @@ def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("educ_case14_storage", test=True, backend=LightSimBackend(), - action_class=PlayableAction) + action_class=PlayableAction, + gamerules_class=AlwaysLegal) + param = self.env.parameters + param.NB_TIMESTEP_COOLDOWN_LINE = 0 + param.NB_TIMESTEP_COOLDOWN_SUB = 0 + self.env.change_parameters(param) + self.env.change_forecast_parameters(param) return super().setUp() def tearDown(self) -> None: @@ -47,6 +54,7 @@ def test_change_bus_load(self): act = self.env.action_space({"set_bus": {"loads_id": [(9, 2)], "lines_or_id": [(14, 2)]}}) obs, reward, done, info = self.env.step(act) + assert len(info['exception']) == 0 assert not done # should not raise any RuntimeError @@ -61,10 +69,11 @@ def test_change_bus_gen(self): act = self.env.action_space({"set_bus": {"generators_id": [(0, 2)], "lines_ex_id": [(0, 2)]}}) obs, reward, done, info = self.env.step(act) + assert len(info['exception']) == 0 assert not done # should not raise any RuntimeError - # isolate the load + # isolate the generator act = self.env.action_space({"set_bus": {"lines_ex_id": [(0, 1)]}}) obs, reward, done, info = self.env.step(act) assert done @@ -74,11 +83,13 @@ def test_disco_storage(self): obs = self.env.reset() act = self.env.action_space({"set_bus": {"storages_id": [(0, -1)]}}) obs, reward, done, info = self.env.step(act) + assert len(info['exception']) == 0 assert not done # should not raise any RuntimeError - act = self.env.action_space({"storage_p": [(0, -1)]}) + act = self.env.action_space({"set_storage": [(0, -1)]}) obs, reward, done, info = self.env.step(act) + assert len(info['exception']) == 0 assert not done # should not raise any RuntimeError From dddd4c4a8d7eb030da56ba334fb58c7fd114eeed Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 20 Oct 2023 17:37:11 +0200 Subject: [PATCH 11/66] adding experimental support for environment with other type of file --- lightsim2grid/gridmodel/from_pypowsybl.py | 5 ++- lightsim2grid/lightSimBackend.py | 31 +++++++++---- lightsim2grid/tests/test_backend_pypowsybl.py | 43 ++++++++++++------- setup.py | 3 +- src/SparseLUSolver.h | 2 +- 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 2a7f55ef..db1fd3d2 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -50,11 +50,12 @@ def init(net : pypo.network, df_gen = net.get_generators().sort_index() else: 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 / 2. + 1. - max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max / 2. - 1. + min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 0.5 + 1. + max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 0.5 - 1. model.init_generators(df_gen["target_p"].values, df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values, min_q, diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 6cb4945d..c1a2f612 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -79,6 +79,13 @@ def __init__(self, self._loader_method = loader_method self._loader_kwargs = loader_kwargs + if loader_method == "pandapower": + self.supported_grid_format = ("json", ) # new in 1.9.6 + elif loader_method == "pypowsybl": + self.supported_grid_format = ("xiidm", ) # new in 1.9.6 + else: + raise BackendError(f"Uknown loader_metho : '{loader_method}'") + self.shunts_data_available = True # needs to be self and not type(self) here self.nb_bus_total = None @@ -444,7 +451,7 @@ def make_complete_path(path, filename): gen_slack_id = None if "gen_slack_id" in loader_kwargs: gen_slack_id = int(loader_kwargs["gen_slack_id"]) - self._grid = init_pypow(grid_tmp, gen_slack_id=None) # TODO gen_slack_id ! + self._grid = init_pypow(grid_tmp, gen_slack_id=None) # TODO gen_slack_id, make things crash ! self._aux_setup_right_after_grid_init() # mandatory for the backend @@ -458,10 +465,10 @@ def make_complete_path(path, filename): from_sub = True if "use_buses_for_sub" in loader_kwargs and loader_kwargs["use_buses_for_sub"]: - df = grid_tmp.get_buses() - from_sub = False + df = grid_tmp.get_buses() + from_sub = False self.n_sub = df.shape[0] - self.name_sub = ["sub_{}".format(i) for i, _ in df.iterrows()] + self.name_sub = ["sub_{}".format(i) for i, _ in enumerate(df.iterrows())] if not from_sub: self.load_to_subid = np.array([el.bus_id for el in self._grid.get_loads()], dtype=dt_int) @@ -510,7 +517,14 @@ def make_complete_path(path, filename): self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)] self._aux_finish_setup_after_reading() self.prod_pu_to_kv = 1.0 * self._grid.get_buses()[[el.bus_id for el in self._grid.get_generators()]] - self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float) + self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float) + + # TODO + max_not_too_max = (np.finfo(dt_float).max * 0.5 - 1.) + self.thermal_limit_a = max_not_too_max * np.ones(self.n_line, dtype=dt_float) + bus_vn_kv = np.array(self._grid.get_buses()) + shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()]) + self._sh_vnkv = bus_vn_kv[shunt_bus_id] def _aux_setup_right_after_grid_init(self): self._handle_turnedoff_pv() @@ -738,7 +752,7 @@ def assert_grid_correct_after_powerflow(self): try: # feature added in grid2op 1.4 or 1.5 _init_action_to_set = self.get_action_to_set() - except TypeError: + except TypeError as exc_: _init_action_to_set = self._get_action_to_set_deprecated() self._init_action_to_set += _init_action_to_set @@ -817,12 +831,12 @@ def apply_action(self, backendAction): backendAction.load_q.values) except RuntimeError as exc_: # see https://github.com/BDonnot/lightsim2grid/issues/66 (even though it's not a "bug" and has not been replicated) - raise BackendError(f"{exc_}") + raise BackendError(f"{exc_}") from exc_ if self.__has_storage: try: self._grid.update_storages_p(backendAction.storage_power.changed, - backendAction.storage_power.values) + backendAction.storage_power.values) except RuntimeError as exc_: # modification of power of disconnected storage has no effect in lightsim2grid pass @@ -872,7 +886,6 @@ def runpf(self, is_dc=False): if (self.V is None) or (self.V.shape[0] == 0): # create the vector V as it is not created 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() # if I init with dc values, it should depends on previous state diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py index 5e41f080..90ea5bcc 100644 --- a/lightsim2grid/tests/test_backend_pypowsybl.py +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -19,8 +19,11 @@ CAN_DO_TEST_SUITE = False -def _aux_get_loader_kwargs(): +def _aux_get_loader_kwargs_storage(): return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 6} + +def _aux_get_loader_kwargs(): + return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 5} class BackendTester(unittest.TestCase): @@ -79,31 +82,39 @@ def get_casefile(self): def make_backend(self, detailed_infos_for_cascading_failures=False): return LightSimBackend(loader_method="pypowsybl", - loader_kwargs=_aux_get_loader_kwargs(), + loader_kwargs=_aux_get_loader_kwargs_storage(), detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) # # add test of grid2op for the backend based on pypowsybl # def this_make_backend(self, detailed_infos_for_cascading_failures=False): # return LightSimBackend( # loader_method="pypowsybl", - # loader_kwargs=_aux_get_loader_kwargs(), + # loader_kwargs=_aux_get_loader_kwargs_storage(), # detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures # ) # add_name_cls = "test_LightSimBackend_pypowsybl" - # def get_path_test_api(self): - # return path - - # def get_casefile(self): - # return "grid.xiidm" - - # res = create_test_suite(make_backend_fun=this_make_backend, - # add_name_cls=add_name_cls, - # add_to_module=__name__, - # extended_test=False, # for now keep `extended_test=False` until all problems are solved - # get_paths={"AAATestBackendAPI": get_path_test_api}, - # get_casefiles={"AAATestBackendAPI": get_casefile} - # ) +if CAN_DO_TEST_SUITE: + # requires grid2Op 1.9.6 at least + class EnvTester(unittest.TestCase): + def setUp(self) -> None: + dir_path = os.path.dirname(os.path.realpath(__file__)) + path_case_14_storage_iidm = os.path.join(dir_path, "case_14_storage_iidm") + self.env = grid2op.make(path_case_14_storage_iidm, + backend=LightSimBackend(loader_method="pypowsybl", + loader_kwargs=_aux_get_loader_kwargs_storage(), + ) + ) + super().setUp() + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def test_can_make(self): + self.env.reset() + 1 + 1 + # TODO env tester if __name__ == "__main__": unittest.main() diff --git a/setup.py b/setup.py index b28c51d3..50c1c9b1 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext -__version__ = "0.7.5" +__version__ = "0.7.6.dev0" KLU_SOLVER_AVAILABLE = False # Try to link against SuiteSparse (if available) @@ -386,6 +386,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Intended Audience :: Developers", "Intended Audience :: Education", diff --git a/src/SparseLUSolver.h b/src/SparseLUSolver.h index 4be807a8..34849a5a 100644 --- a/src/SparseLUSolver.h +++ b/src/SparseLUSolver.h @@ -18,7 +18,7 @@ #include "Eigen/SparseLU" /** -class to handle the solver using newton-raphson method, using a "SparseLU" algorithm from Eigein +class to handle the solver using newton-raphson method, using a "SparseLU" algorithm from Eigen and sparse matrices. As long as the admittance matrix of the sytem does not change, you can reuse the same solver. From 82f9cdea2f3542e6a79f77ce8b63eb02d1c115c7 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 23 Oct 2023 14:32:32 +0200 Subject: [PATCH 12/66] improve the importer from iidm --- CHANGELOG.rst | 1 + lightsim2grid/gridmodel/from_pypowsybl.py | 4 +- lightsim2grid/gridmodel/initGridModel.py | 7 +-- lightsim2grid/lightSimBackend.py | 10 +++- lightsim2grid/tests/test_backend_pypowsybl.py | 60 +++++++++++++++++-- .../tests/test_init_from_pypowsybl.py | 6 +- src/GridModel.cpp | 44 ++++++++++++-- src/GridModel.h | 13 +++- src/main.cpp | 3 +- 9 files changed, 122 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dfd2a15e..7ccb37f2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,7 @@ Change Log - [FIXED] now voltage is properly set to 0. when shunts are disconnected - [FIXED] now voltage is properly set to 0. when storage units are disconnected - [FIXED] a bug where non connected grid were not spotted in DC +- [FIXED] a bug when trying to set the slack for a non existing genererator - [IMPROVED] now making the new grid2op `create_test_suite` [0.7.5] 2023-10-05 diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index db1fd3d2..c8ad3d35 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -37,13 +37,13 @@ def init(net : pypo.network, bus_df = bus_df_orig bus_df["bus_id"] = np.arange(bus_df.shape[0]) bus_df_orig["bus_id"] = bus_df.loc[bus_df_orig.index]["bus_id"] - model._ls_to_orig = 1 * bus_df_orig["bus_id"].values voltage_levels = net.get_voltage_levels() model.set_sn_mva(sn_mva) model.set_init_vm_pu(1.06) model.init_bus(voltage_levels.loc[bus_df["voltage_level_id"].values]["nominal_v"].values, 0, 0 # unused ) + model._orig_to_ls = 1 * bus_df_orig["bus_id"].values # do the generators if sort_index: @@ -67,7 +67,7 @@ def init(net : pypo.network, model.add_gen_slackbus(gen_slack_id, 1.) elif slack_bus_id is not None: gen_bus = np.array([el.bus_id for el in model.get_generators()]) - gen_is_conn_slack = gen_bus == model._ls_to_orig[slack_bus_id] + gen_is_conn_slack = gen_bus == model._orig_to_ls[slack_bus_id] nb_conn = gen_is_conn_slack.sum() if nb_conn == 0: raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack") diff --git a/lightsim2grid/gridmodel/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py index 32def861..78293cdc 100644 --- a/lightsim2grid/gridmodel/initGridModel.py +++ b/lightsim2grid/gridmodel/initGridModel.py @@ -80,15 +80,14 @@ def init(pp_net): model.set_sn_mva(pp_net.sn_mva) tmp_bus_ind = np.argsort(pp_net.bus.index) + model.init_bus(pp_net.bus.iloc[tmp_bus_ind]["vn_kv"].values, + pp_net.line.shape[0], + pp_net.trafo.shape[0]) if np.any(np.sort(pp_net.bus.index) != np.arange(pp_net.bus.shape[0])): model._ls_to_orig = 1 * pp_net.bus.index.values.astype(int) pp_to_ls = {pp_bus: ls_bus for pp_bus, ls_bus in zip(pp_net.bus.index, tmp_bus_ind)} else: pp_to_ls = None - model.init_bus(pp_net.bus.iloc[tmp_bus_ind]["vn_kv"].values, - pp_net.line.shape[0], - pp_net.trafo.shape[0]) - # deactivate in lightsim the deactivated bus in pandapower for bus_id in range(pp_net.bus.shape[0]): if not pp_net.bus["in_service"].values[bus_id]: diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index c1a2f612..461b13db 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -451,7 +451,7 @@ def make_complete_path(path, filename): gen_slack_id = None if "gen_slack_id" in loader_kwargs: gen_slack_id = int(loader_kwargs["gen_slack_id"]) - self._grid = init_pypow(grid_tmp, gen_slack_id=None) # TODO gen_slack_id, make things crash ! + self._grid = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True) self._aux_setup_right_after_grid_init() # mandatory for the backend @@ -507,10 +507,15 @@ def make_complete_path(path, filename): # as it is done with grid2Op simulated environment if "double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]: bus_init = self._grid.get_buses() + orig_to_ls = np.array(self._grid._orig_to_ls) bus_doubled = np.concatenate((bus_init, bus_init)) self._grid.init_bus(bus_doubled, 0, 0) for i in range(self.__nb_bus_before): self._grid.deactivate_bus(i + self.__nb_bus_before) + new_orig_to_ls = np.concatenate((orig_to_ls, + np.zeros(orig_to_ls.shape[0], dtype=orig_to_ls.dtype) + self.__nb_bus_before) + ) + self._grid._orig_to_ls = new_orig_to_ls self.nb_bus_total = len(self._grid.get_buses()) # and now things needed by the backend (legacy) @@ -1052,7 +1057,7 @@ def copy(self): "_big_topo_to_obj", "max_it", "tol", "dim_topo", "_idx_hack_storage", "_timer_preproc", "_timer_postproc", "_timer_solver", - "_my_kwargs", + "_my_kwargs", "supported_grid_format", "_turned_off_pv", "_dist_slack_non_renew", "_loader_method", "_loader_kwargs" ] @@ -1106,7 +1111,6 @@ def copy(self): setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm))) ############### - # handle the most complicated res._grid = mygrid.copy() res.__me_at_init = __me_at_init.copy() # this is const diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py index 90ea5bcc..4cc8528d 100644 --- a/lightsim2grid/tests/test_backend_pypowsybl.py +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -9,8 +9,12 @@ import unittest import warnings import os +import numpy as np from lightsim2grid import LightSimBackend +from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow import grid2op +from grid2op.Runner import Runner +import pypowsybl.network as pypow_net try: from grid2op._create_test_suite import create_test_suite @@ -20,10 +24,10 @@ def _aux_get_loader_kwargs_storage(): - return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 6} + return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 5} def _aux_get_loader_kwargs(): - return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 5} + return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 0} class BackendTester(unittest.TestCase): @@ -34,7 +38,7 @@ def setUp(self) -> None: self.file_name = "grid.xiidm" def _aux_prep_backend(self, backend): - backend.set_env_name("case_14_iidm") + backend.set_env_name("case_14_iidm_BackendTester") backend.load_grid(self.path, self.file_name) backend.load_storage_data(self.path) backend.load_redispacthing_data(self.path) @@ -68,6 +72,35 @@ def test_runpf(self): conv, exc_ = backend.runpf(is_dc=True) assert conv +class BackendTester2(unittest.TestCase): + """issue is still not replicated and these tests pass""" + def _aux_prep_backend(self, backend): + backend.set_env_name("case_14_storage_iidm_BackendTester2") + backend.load_grid(self.path, self.file_name) + backend.load_storage_data(self.path) + backend.load_redispacthing_data(self.path) + backend.assert_grid_correct() + + def setUp(self) -> None: + dir_path = os.path.dirname(os.path.realpath(__file__)) + self.path = os.path.join(dir_path, "case_14_storage_iidm") + self.file_name = "grid.xiidm" + + def test_init(self): + grid_tmp = pypow_net.load(os.path.join(self.path, self.file_name)) + grid = init_pypow(grid_tmp, gen_slack_id=5, sort_index=True) + grid.ac_pf(np.ones(14, dtype=np.complex128), 10, 1e-6) + + def test_runpf(self): + backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=_aux_get_loader_kwargs()) + self._aux_prep_backend(backend) + # AC powerflow + conv, exc_ = backend.runpf() + assert conv + # DC powerflow + conv, exc_ = backend.runpf(is_dc=True) + assert conv + if CAN_DO_TEST_SUITE: dir_path = os.path.dirname(os.path.realpath(__file__)) path_case_14_storage_iidm = os.path.join(dir_path, "case_14_storage_iidm") @@ -103,7 +136,8 @@ def setUp(self) -> None: self.env = grid2op.make(path_case_14_storage_iidm, backend=LightSimBackend(loader_method="pypowsybl", loader_kwargs=_aux_get_loader_kwargs_storage(), - ) + ), + _add_to_name=type(self).__name__ ) super().setUp() @@ -115,6 +149,24 @@ def test_can_make(self): self.env.reset() 1 + 1 + def test_copy(self): + obs = self.env.reset() + env_cpy = self.env.copy() + obs_cpy = env_cpy.reset() + assert self.env.backend.supported_grid_format == ("xiidm", ) + assert env_cpy.backend.supported_grid_format == ("xiidm", ) + + def test_runner(self): + obs = self.env.reset() + env_cpy = self.env.copy() + runner = Runner(**self.env.get_params_for_runner()) + runner_cpy = Runner(**env_cpy.get_params_for_runner()) + res = runner.run(nb_episode=1, max_iter=10) + res_cpy = runner_cpy.run(nb_episode=1, max_iter=10) + for el, el_cpy in zip(res[0], res_cpy[0]): + assert el == el_cpy, f"{el} vs {el_cpy}" + + # TODO env tester if __name__ == "__main__": unittest.main() diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 76e6c1d4..db64eace 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -84,7 +84,7 @@ def test_compare_pp(self): v_ls = self.gridmodel.dc_pf(1.0 * self.V_init_dc, 2, self.tol) v_ls_ref = self.ref_samecase.dc_pf(1.0 * self.V_init_dc, 2, self.tol) slack_id = self.get_slackbus_id() - reorder = self.gridmodel._ls_to_orig.reshape(1, -1) + reorder = self.gridmodel._orig_to_ls.reshape(1, -1) # for case 118 # reorder_flat = reorder.reshape(-1) @@ -150,7 +150,7 @@ def test_compare_pp(self): def test_dc_pf(self): """test I get the same results as pandapower in dc""" v_ls = self.gridmodel.dc_pf(self.V_init_dc, 2, self.tol) - reorder = self.gridmodel._ls_to_orig.reshape(1, -1) + reorder = self.gridmodel._orig_to_ls.reshape(1, -1) if self.compare_pp(): v_ls_ref = self.ref_samecase.dc_pf(self.V_init_dc, 2, self.tol) assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for dc: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}" @@ -170,7 +170,7 @@ def test_dc_pf(self): def test_ac_pf(self): # run the powerflows v_ls = self.gridmodel.ac_pf(1.0 * self.V_init_ac, 10, self.tol) - reorder = self.gridmodel._ls_to_orig.reshape(1, -1) + reorder = self.gridmodel._orig_to_ls.reshape(1, -1) if self.compare_pp(): v_ls_ref = self.ref_samecase.ac_pf(1.0 * self.V_init_ac, 10, self.tol) assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for ac: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}" diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 7ac04207..31b01353 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -14,7 +14,7 @@ GridModel::GridModel(const GridModel & other) { reset(true, true, true); - _ls_to_orig = other._ls_to_orig; + set_ls_to_orig(other._ls_to_orig); // set also orig_to_ls init_vm_pu_ = other.init_vm_pu_; sn_mva_ = other.sn_mva_; @@ -80,7 +80,7 @@ GridModel::GridModel(const GridModel & other) GridModel::StateRes GridModel::get_state() const { std::vector bus_vn_kv(bus_vn_kv_.begin(), bus_vn_kv_.end()); - std::vector ls_to_pp(_ls_to_orig.begin(), _ls_to_orig.end()); + std::vector ls_to_orig(_ls_to_orig.begin(), _ls_to_orig.end()); int version_major = VERSION_MAJOR; int version_medium = VERSION_MEDIUM; int version_minor = VERSION_MINOR; @@ -96,7 +96,7 @@ GridModel::StateRes GridModel::get_state() const GridModel::StateRes res(version_major, version_medium, version_minor, - ls_to_pp, + ls_to_orig, init_vm_pu_, sn_mva_, bus_vn_kv, @@ -161,7 +161,8 @@ void GridModel::set_state(GridModel::StateRes & my_state) // assign it to this instance - _ls_to_orig =IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size()); + set_ls_to_orig(IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size())); // set also _orig_to_ls + // buses // 1. bus_vn_kv_ bus_vn_kv_ = RealVect::Map(&bus_vn_kv[0], bus_vn_kv.size()); @@ -187,6 +188,35 @@ void GridModel::set_state(GridModel::StateRes & my_state) dc_lines_.set_state(state_dc_lines); }; +void GridModel::set_ls_to_orig(const IntVect & ls_to_orig){ + if(ls_to_orig.size() != bus_vn_kv_.size()) + throw std::runtime_error("Impossible to set the converter ls_to_orig: the provided vector has not the same size as the number of bus on the grid."); + _ls_to_orig = ls_to_orig; + const auto size = ls_to_orig.lpNorm(); + _orig_to_ls = IntVect::Zero(size); + _orig_to_ls.array() -= 1; + for(auto i = 0; i < size; ++i){ + _orig_to_ls[i] = _ls_to_orig[i]; + } +} + +void GridModel::set_orig_to_ls(const IntVect & orig_to_ls){ + _orig_to_ls = orig_to_ls; + Eigen::Index nb_bus_ls = 0; + for(const auto el : orig_to_ls){ + if (el != -1) nb_bus_ls += 1; + } + _ls_to_orig = IntVect::Zero(nb_bus_ls); + Eigen::Index ls2or_ind = 0; + for(auto or2ls_ind = 0; or2ls_ind < nb_bus_ls; ++or2ls_ind){ + const auto my_ind = _orig_to_ls[or2ls_ind]; + if(my_ind >= 0){ + _ls_to_orig[ls2or_ind] = my_ind; + ls2or_ind++; + } + } +} + //init void GridModel::init_bus(const RealVect & bus_vn_kv, int nb_line, int nb_trafo){ /** @@ -198,6 +228,8 @@ void GridModel::init_bus(const RealVect & bus_vn_kv, int nb_line, int nb_trafo){ bus_vn_kv_ = bus_vn_kv; // base_kv bus_status_ = std::vector(nb_bus, true); // by default everything is connected + _orig_to_ls = IntVect(); + _ls_to_orig = IntVect(); } void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) @@ -696,7 +728,7 @@ void GridModel::add_gen_slackbus(int gen_id, real_type weight){ exc_ << gen_id; throw std::runtime_error(exc_.str()); } - if(gen_id > generators_.nb()) + if(gen_id >= generators_.nb()) { std::ostringstream exc_; exc_ << "GridModel::add_gen_slackbus: There are only " << generators_.nb() << " generators on the grid. "; @@ -720,7 +752,7 @@ void GridModel::remove_gen_slackbus(int gen_id){ exc_ << gen_id; throw std::runtime_error(exc_.str()); } - if(gen_id > generators_.nb()) + if(gen_id >= generators_.nb()) { // TODO DEBUG MODE: only check when in debug mode std::ostringstream exc_; diff --git a/src/GridModel.h b/src/GridModel.h index d03e1df6..751a9608 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -44,9 +44,6 @@ //TODO implement a BFS check to make sure the Ymatrix is "connected" [one single component] class GridModel : public DataGeneric { - public: // can be modified python side - IntVect _ls_to_orig; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) - public: typedef std::tuple< int, // version major @@ -84,6 +81,12 @@ class GridModel : public DataGeneric GridModel res(*this); return res; } + + void set_ls_to_orig(const IntVect & ls_to_orig); // set both _ls_to_orig and _orig_to_ls + void set_orig_to_ls(const IntVect & orig_to_ls); // set both _orig_to_ls and _ls_to_orig + const IntVect & get_ls_to_orig(void) const {return _ls_to_orig;} + const IntVect & get_orig_to_ls(void) const {return _orig_to_ls;} + Eigen::Index total_bus() const {return bus_vn_kv_.size();} const std::vector & id_me_to_ac_solver() const {return id_me_to_ac_solver_;} const std::vector & id_ac_solver_to_me() const {return id_ac_solver_to_me_;} @@ -642,6 +645,10 @@ class GridModel : public DataGeneric void check_solution_q_values_onegen(CplxVect & res, const DataGen::GenInfo& gen, bool check_q_limits) const; protected: + // memory for the import + IntVect _ls_to_orig; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) + IntVect _orig_to_ls; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) + // member of the grid // static const int _deactivated_bus_id; diff --git a/src/main.cpp b/src/main.cpp index da169f9e..c5130a44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -586,7 +586,8 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "GridModel", DocGridModel::GridModel.c_str()) .def(py::init<>()) .def("copy", &GridModel::copy) - .def_readwrite("_ls_to_orig", &GridModel::_ls_to_orig, "for converter from bus in lightsim2grid index to bus index in original file format (*eg* pandapower of pypowsybl)") + .def_property("_ls_to_orig", &GridModel::get_ls_to_orig, &GridModel::set_ls_to_orig, "remember the conversion from bus index in lightsim2grid to bus index in original file format (*eg* pandapower of pypowsybl).") + .def_property("_orig_to_ls", &GridModel::get_orig_to_ls, &GridModel::set_orig_to_ls, "remember the conversion from bus index in original file format (*eg* pandapower of pypowsybl) to bus index in lightsim2grid.") // pickle .def(py::pickle( From 92612390091b7bbbde33c1e77a8d106b4e5fadee Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 23 Oct 2023 16:56:50 +0200 Subject: [PATCH 13/66] first poc for speed improvment: not recomputing what does not need to [skip ci] --- src/BaseSolver.cpp | 6 +- src/BaseSolver.h | 2 +- src/DataDCLine.h | 38 ++++++------- src/DataGen.cpp | 8 +-- src/DataGen.h | 13 ++--- src/DataGeneric.cpp | 6 +- src/DataGeneric.h | 6 +- src/DataLine.h | 8 +-- src/DataLoad.h | 10 ++-- src/DataSGen.h | 10 ++-- src/DataShunt.h | 10 ++-- src/DataTrafo.h | 8 +-- src/GridModel.cpp | 67 ++++++++++------------ src/GridModel.h | 134 +++++++++++++++++++++++++++----------------- src/Utils.h | 65 +++++++++++++++++++++ src/main.cpp | 21 ++++++- 16 files changed, 256 insertions(+), 156 deletions(-) diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 05c837ed..22decb1e 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -8,15 +8,15 @@ #include "BaseSolver.h" -void BaseSolver::reset(){ +void BaseSolver::reset(const SolverControl & solver_control){ // reset timers reset_timer(); //reset the attribute n_ = -1; Vm_ = RealVect(); // voltage magnitude - Va_= RealVect(); // voltage angle - V_= RealVect(); // voltage angle + Va_ = RealVect(); // voltage angle + V_ = RealVect(); // voltage angle // TODO solver control: see if I could reuse some of these nr_iter_ = 0; // number of iteration performs by the algorithm err_ = ErrorType::NotInitError; //error message: } diff --git a/src/BaseSolver.h b/src/BaseSolver.h index 54d87107..f88cf4d7 100644 --- a/src/BaseSolver.h +++ b/src/BaseSolver.h @@ -99,7 +99,7 @@ class BaseSolver : public BaseConstants ) = 0 ; virtual - void reset(); + void reset(const SolverControl & solver_control); protected: virtual void reset_timer(){ diff --git a/src/DataDCLine.h b/src/DataDCLine.h index 185be259..46c7af30 100644 --- a/src/DataDCLine.h +++ b/src/DataDCLine.h @@ -156,20 +156,20 @@ class DataDCLine : public DataGeneric ); // accessor / modifiers - void deactivate(int dcline_id, bool & need_reset) { - _deactivate(dcline_id, status_, need_reset); - from_gen_.deactivate(dcline_id, need_reset); - to_gen_.deactivate(dcline_id, need_reset); + void deactivate(int dcline_id, SolverControl & solver_control) { + _deactivate(dcline_id, status_); + from_gen_.deactivate(dcline_id, solver_control); + to_gen_.deactivate(dcline_id, solver_control); } - void reactivate(int dcline_id, bool & need_reset) { - _reactivate(dcline_id, status_, need_reset); - from_gen_.reactivate(dcline_id, need_reset); - to_gen_.reactivate(dcline_id, need_reset); + void reactivate(int dcline_id, SolverControl & solver_control) { + _reactivate(dcline_id, status_); + from_gen_.reactivate(dcline_id, solver_control); + to_gen_.reactivate(dcline_id, solver_control); } - void change_bus_or(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) { - from_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} - void change_bus_ex(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) { - to_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} + void change_bus_or(int dcline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + from_gen_.change_bus(dcline_id, new_bus_id, solver_control, nb_bus);} + void change_bus_ex(int dcline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + to_gen_.change_bus(dcline_id, new_bus_id, solver_control, nb_bus);} int get_bus_or(int dcline_id) {return from_gen_.get_bus(dcline_id);} int get_bus_ex(int dcline_id) {return to_gen_.get_bus(dcline_id);} real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);} @@ -185,16 +185,16 @@ class DataDCLine : public DataGeneric ; return new_p_ext; } - void change_p(int dcline_id, real_type new_p, bool & need_reset){ - from_gen_.change_p(dcline_id, -1.0 * new_p, need_reset); + void change_p(int dcline_id, real_type new_p, SolverControl & sovler_control){ + from_gen_.change_p(dcline_id, -1.0 * new_p, sovler_control); - to_gen_.change_p(dcline_id, -1.0 * get_to_mw(dcline_id, new_p), need_reset); + to_gen_.change_p(dcline_id, -1.0 * get_to_mw(dcline_id, new_p), sovler_control); } - void change_v_or(int dcline_id, real_type new_v_pu, bool & need_reset){ - from_gen_.change_v(dcline_id, new_v_pu, need_reset); + void change_v_or(int dcline_id, real_type new_v_pu, SolverControl & sovler_control){ + from_gen_.change_v(dcline_id, new_v_pu, sovler_control); } - void change_v_ex(int dcline_id, real_type new_v_pu, bool & need_reset){ - to_gen_.change_v(dcline_id, new_v_pu, need_reset); + void change_v_ex(int dcline_id, real_type new_v_pu, SolverControl & sovler_control){ + to_gen_.change_v(dcline_id, new_v_pu, sovler_control); } // solver stuff diff --git a/src/DataGen.cpp b/src/DataGen.cpp index 238a6586..50a5937b 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -377,7 +377,7 @@ void DataGen::set_q(const RealVect & reactive_mismatch, void DataGen::update_slack_weights(Eigen::Ref > could_be_slack, - bool & need_reset) + bool & need_reset_solver) { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) @@ -386,16 +386,16 @@ void DataGen::update_slack_weights(Eigen::Ref 0.){ // gen is properly connected - if(!gen_slackbus_[gen_id]) need_reset = true; // it was not in the slack before, so I need to reset the solver + if(!gen_slackbus_[gen_id]) need_reset_solver = true; // it was not in the slack before, so I need to reset the solver add_slackbus(gen_id, p_mw_(gen_id)); }else{ // gen is now "turned off" - if(gen_slackbus_[gen_id]) need_reset = true; // it was in the slack before, so I need to reset the solver + if(gen_slackbus_[gen_id]) need_reset_solver = true; // it was in the slack before, so I need to reset the solver remove_slackbus(gen_id); } }else{ - if(gen_slackbus_[gen_id]) need_reset = true; // it was in the slack before, I need to reset the solver + if(gen_slackbus_[gen_id]) need_reset_solver = true; // it was in the slack before, I need to reset the solver remove_slackbus(gen_id); } } diff --git a/src/DataGen.h b/src/DataGen.h index 237f118e..c6a9cca7 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -171,17 +171,16 @@ class DataGen: public DataGeneric void turnedoff_no_pv(){turnedoff_gen_pv_=false;} // turned off generators are not pv void turnedoff_pv(){turnedoff_gen_pv_=true;} // turned off generators are pv bool get_turnedoff_gen_pv() const {return turnedoff_gen_pv_;} - void update_slack_weights(Eigen::Ref > could_be_slack, - bool & need_reset); + void update_slack_weights(Eigen::Ref > could_be_slack, SolverControl & solver_control); - void deactivate(int gen_id, bool & need_reset) {_deactivate(gen_id, status_, need_reset);} - void reactivate(int gen_id, bool & need_reset) {_reactivate(gen_id, status_, need_reset);} - void change_bus(int gen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void deactivate(int gen_id, SolverControl & solver_control) {_deactivate(gen_id, status_, need_reset);} + void reactivate(int gen_id, SolverControl & sovler_control) {_reactivate(gen_id, status_, need_reset);} + void change_bus(int gen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);} int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} - void change_p(int gen_id, real_type new_p, bool & need_reset); - void change_v(int gen_id, real_type new_v_pu, bool & need_reset); + void change_p(int gen_id, real_type new_p, SolverControl & solver_control); + void change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; virtual void fillpv(std::vector& bus_pv, diff --git a/src/DataGeneric.cpp b/src/DataGeneric.cpp index 6d246d34..832be6af 100644 --- a/src/DataGeneric.cpp +++ b/src/DataGeneric.cpp @@ -26,14 +26,12 @@ void DataGeneric::_get_amps(RealVect & a, const RealVect & p, const RealVect & q } a = p2q2.array() * _1_sqrt_3 / v_tmp.array(); } -void DataGeneric::_reactivate(int el_id, std::vector & status, bool & need_reset){ +void DataGeneric::_reactivate(int el_id, std::vector & status){ bool val = status.at(el_id); - if(!val) need_reset = true; // I need to recompute the grid, if a status has changed status.at(el_id) = true; //TODO why it's needed to do that again } -void DataGeneric::_deactivate(int el_id, std::vector & status, bool & need_reset){ +void DataGeneric::_deactivate(int el_id, std::vector & status){ bool val = status.at(el_id); - if(val) need_reset = true; // I need to recompute the grid, if a status has changed status.at(el_id) = false; //TODO why it's needed to do that again } void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, bool & need_reset, int nb_bus){ diff --git a/src/DataGeneric.h b/src/DataGeneric.h index 8d9bc606..f3db5427 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -98,9 +98,9 @@ class DataGeneric : public BaseConstants /** activation / deactivation of elements **/ - void _reactivate(int el_id, std::vector & status, bool & need_reset); - void _deactivate(int el_id, std::vector & status, bool & need_reset); - void _change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, bool & need_reset, int nb_bus); + void _reactivate(int el_id, std::vector & status); + void _deactivate(int el_id, std::vector & status); + void _change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus); int _get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_); /** diff --git a/src/DataLine.h b/src/DataLine.h index dae3e8a5..e76813b5 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -178,10 +178,10 @@ class DataLine : public DataGeneric return LineInfo(*this, id); } - void deactivate(int powerline_id, bool & need_reset) {_deactivate(powerline_id, status_, need_reset);} - void reactivate(int powerline_id, bool & need_reset) {_reactivate(powerline_id, status_, need_reset);} - void change_bus_or(int powerline_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_or_id_, need_reset, nb_bus);} - void change_bus_ex(int powerline_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_ex_id_, need_reset, nb_bus);} + void deactivate(int powerline_id, SolverControl & solver_control) {_deactivate(powerline_id, status_, need_reset);} + void reactivate(int powerline_id, SolverControl & solver_control) {_reactivate(powerline_id, status_, need_reset);} + void change_bus_or(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_or_id_, need_reset, nb_bus);} + void change_bus_ex(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_ex_id_, need_reset, nb_bus);} int get_bus_or(int powerline_id) {return _get_bus(powerline_id, status_, bus_or_id_);} int get_bus_ex(int powerline_id) {return _get_bus(powerline_id, status_, bus_ex_id_);} virtual void fillYbus(std::vector > & res, diff --git a/src/DataLoad.h b/src/DataLoad.h index 78effd64..52f54298 100644 --- a/src/DataLoad.h +++ b/src/DataLoad.h @@ -132,12 +132,12 @@ class DataLoad : public DataGeneric int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int load_id, bool & need_reset) {_deactivate(load_id, status_, need_reset);} - void reactivate(int load_id, bool & need_reset) {_reactivate(load_id, status_, need_reset);} - void change_bus(int load_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(load_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void deactivate(int load_id, SolverControl & solver_control) {_deactivate(load_id, status_, need_reset);} + void reactivate(int load_id, SolverControl & solver_control) {_reactivate(load_id, status_, need_reset);} + void change_bus(int load_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(load_id, new_bus_id, bus_id_, need_reset, nb_bus);} int get_bus(int load_id) {return _get_bus(load_id, status_, bus_id_);} - void change_p(int load_id, real_type new_p, bool & need_reset); - void change_q(int load_id, real_type new_q, bool & need_reset); + void change_p(int load_id, real_type new_p, SolverControl & solver_control); + void change_q(int load_id, real_type new_q, SolverControl & solver_control); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; diff --git a/src/DataSGen.h b/src/DataSGen.h index 41ec3ac0..b99ef353 100644 --- a/src/DataSGen.h +++ b/src/DataSGen.h @@ -152,12 +152,12 @@ class DataSGen: public DataGeneric int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int sgen_id, bool & need_reset) {_deactivate(sgen_id, status_, need_reset);} - void reactivate(int sgen_id, bool & need_reset) {_reactivate(sgen_id, status_, need_reset);} - void change_bus(int sgen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(sgen_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void deactivate(int sgen_id, SolverControl & solver_control) {_deactivate(sgen_id, status_, need_reset);} + void reactivate(int sgen_id, SolverControl & solver_control) {_reactivate(sgen_id, status_, need_reset);} + void change_bus(int sgen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(sgen_id, new_bus_id, bus_id_, need_reset, nb_bus);} int get_bus(int sgen_id) {return _get_bus(sgen_id, status_, bus_id_);} - void change_p(int sgen_id, real_type new_p, bool & need_reset); - void change_q(int sgen_id, real_type new_q, bool & need_reset); + void change_p(int sgen_id, real_type new_p, SolverControl & solver_control); + void change_q(int sgen_id, real_type new_q, SolverControl & solver_control); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const ; diff --git a/src/DataShunt.h b/src/DataShunt.h index 9a74be97..3a6a6be8 100644 --- a/src/DataShunt.h +++ b/src/DataShunt.h @@ -125,11 +125,11 @@ class DataShunt : public DataGeneric int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int shunt_id, bool & need_reset) {_deactivate(shunt_id, status_, need_reset);} - void reactivate(int shunt_id, bool & need_reset) {_reactivate(shunt_id, status_, need_reset);} - void change_bus(int shunt_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(shunt_id, new_bus_id, bus_id_, need_reset, nb_bus);} - void change_p(int shunt_id, real_type new_p, bool & need_reset); - void change_q(int shunt_id, real_type new_q, bool & need_reset); + void deactivate(int shunt_id, SolverControl & solver_control) {_deactivate(shunt_id, status_, need_reset);} + void reactivate(int shunt_id, SolverControl & solver_control) {_reactivate(shunt_id, status_, need_reset);} + void change_bus(int shunt_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(shunt_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void change_p(int shunt_id, real_type new_p, SolverControl & solver_control); + void change_q(int shunt_id, real_type new_q, SolverControl & solver_control); int get_bus(int shunt_id) {return _get_bus(shunt_id, status_, bus_id_);} virtual void fillYbus(std::vector > & res, diff --git a/src/DataTrafo.h b/src/DataTrafo.h index 84f29e61..0eca8ad2 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -168,10 +168,10 @@ class DataTrafo : public DataGeneric } // method used within lightsim - void deactivate(int trafo_id, bool & need_reset) {_deactivate(trafo_id, status_, need_reset);} - void reactivate(int trafo_id, bool & need_reset) {_reactivate(trafo_id, status_, need_reset);} - void change_bus_hv(int trafo_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_hv_id_, need_reset, nb_bus);} - void change_bus_lv(int trafo_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_lv_id_, need_reset, nb_bus);} + void deactivate(int trafo_id, SolverControl & solver_control) {_deactivate(trafo_id, status_, need_reset);} + void reactivate(int trafo_id, SolverControl & solver_control) {_reactivate(trafo_id, status_, need_reset);} + void change_bus_hv(int trafo_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_hv_id_, need_reset, nb_bus);} + void change_bus_lv(int trafo_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_lv_id_, need_reset, nb_bus);} int get_bus_hv(int trafo_id) {return _get_bus(trafo_id, status_, bus_hv_id_);} int get_bus_lv(int trafo_id) {return _get_bus(trafo_id, status_, bus_lv_id_);} diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 31b01353..8aa3be6e 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -118,9 +118,8 @@ void GridModel::set_state(GridModel::StateRes & my_state) // after loading back, the instance need to be reset anyway // TODO see if it's worth the trouble NOT to do it reset(true, true, true); - need_reset_ = true; + solver_control_.tell_all_changed(); compute_results_ = true; - topo_changed_ = true; // extract data from the state int version_major = std::get<0>(my_state); @@ -252,8 +251,7 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); slack_weights_ = RealVect(); - need_reset_ = true; - topo_changed_ = true; + solver_control_.tell_all_changed(); // reset the solvers if (reset_solver){ @@ -282,12 +280,12 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, // pre process the data to define a proper jacobian matrix, the proper voltage vector etc. bool is_ac = true; - bool reset_solver = topo_changed_; // I reset the solver only if the topology change CplxVect V = pre_process_solver(Vinit, Ybus_ac_, id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_, - is_ac, reset_solver); + is_ac, + solver_control_); // start the solver slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_); @@ -364,7 +362,8 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim // pre process the data to define a proper jacobian matrix, the proper voltage vector etc. const int nb_bus = static_cast(V_proposed.size()); bool is_ac = true; - bool reset_solver = false; + SolverControl reset_solver; + reset_solver.tell_none_changed(); // TODO reset solver CplxVect V = pre_process_solver(V_proposed, Ybus_ac_, id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_, is_ac, reset_solver); @@ -399,40 +398,26 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, std::vector & id_solver_to_me, Eigen::VectorXi & slack_bus_id_solver, bool is_ac, - bool reset_solver) + const SolverControl & solver_control) { // TODO get rid of the "is_ac" argument: this info is available in the _solver already - // std::cout << "GridModel::pre_process_solver : topo_changed_ " << topo_changed_ << std::endl; - // std::cout << "GridModel::pre_process_solver : reset_solver " << reset_solver << std::endl; - - bool reset_ac = topo_changed_ && is_ac; - bool reset_dc = topo_changed_ && !is_ac; - // if(need_reset_){ // TODO optimization when it's not mandatory to start from scratch - if(topo_changed_) reset(reset_solver, reset_ac, reset_dc); // TODO what if pv and pq changed ? :O - else{ - // topo is not changed, but i can still reset the solver (TODO: no necessarily needed !) - if (reset_solver) - { - if(is_ac) _solver.reset(); - else _dc_solver.reset(); - } - } + if(is_ac) _solver.reset(solver_control); + else _dc_solver.reset(solver_control); slack_bus_id_ = generators_.get_slack_bus_id(); - if(topo_changed_){ - // TODO do not reinit Ybus if the topology does not change - init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); - fillYbus(Ybus, is_ac, id_me_to_solver); - } - init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); - fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver); // TODO what if pv and pq changed ? :O + if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed()) init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); + if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed() || solver_control.need_recompute_ybus()) fillYbus(Ybus, is_ac, id_me_to_solver); + if (solver_control.has_dimension_changed()) init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); + fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); - int nb_bus_total = static_cast(bus_vn_kv_.size()); - total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); - total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); - total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0); - generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - fillSbus_me(Sbus_, is_ac, id_me_to_solver); + if (solver_control.need_recompute_sbus()){ + int nb_bus_total = static_cast(bus_vn_kv_.size()); + total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); + total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); + total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0); + generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); + dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); + fillSbus_me(Sbus_, is_ac, id_me_to_solver); + } const int nb_bus_solver = static_cast(id_solver_to_me.size()); CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_); @@ -576,13 +561,19 @@ void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id void GridModel::fillpv_pq(const std::vector& id_me_to_solver, std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver) + Eigen::VectorXi & slack_bus_id_solver, + const SolverControl & solver_control) { + // Nothing to do if neither pv, nor pq nor the dimension of the problem has changed + if(!solver_control.has_pq_changed() && !solver_control.has_pv_changed() && !solver_control.has_dimension_changed()) return; + // init pq and pv vector // TODO remove the order here..., i could be faster in this piece of code (looping once through the buses) const int nb_bus = static_cast(id_solver_to_me.size()); // number of bus in the solver! std::vector bus_pq; + bus_pq.reserve(nb_bus); std::vector bus_pv; + bus_pv.reserve(nb_bus); std::vector has_bus_been_added(nb_bus, false); // std::cout << "id_me_to_solver.size(): " << id_me_to_solver.size() << std::endl; diff --git a/src/GridModel.h b/src/GridModel.h index 751a9608..54d88b78 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -72,7 +72,10 @@ class GridModel : public DataGeneric DataDCLine::StateRes > StateRes; - GridModel():need_reset_(true), topo_changed_(true), compute_results_(true), init_vm_pu_(1.04), sn_mva_(1.0){ + GridModel(): + solver_control_(), + init_vm_pu_(1.04), + sn_mva_(1.0){ _dc_solver.change_solver(SolverType::DC); _solver.set_gridmodel(this); } @@ -99,7 +102,7 @@ class GridModel : public DataGeneric void turnedoff_pv(){generators_.turnedoff_pv();} // turned off generators are pv bool get_turnedoff_gen_pv() {return generators_.get_turnedoff_gen_pv();} void update_slack_weights(Eigen::Ref > could_be_slack){ - generators_.update_slack_weights(could_be_slack, topo_changed_); + generators_.update_slack_weights(could_be_slack, solver_control_); } const DataSGen & get_static_generators_as_data() const {return sgens_;} @@ -111,8 +114,7 @@ class GridModel : public DataGeneric // solver "control" void change_solver(const SolverType & type){ - need_reset_ = true; - topo_changed_ = true; + solver_control_.tell_all_changed(); if(_solver.is_dc(type)) _dc_solver.change_solver(type); else _solver.change_solver(type); } @@ -235,8 +237,14 @@ class GridModel : public DataGeneric //powerflows // control the need to refactorize the topology - void unset_topo_changed(){topo_changed_ = false;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. - void tell_topo_changed(){topo_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void unset_changes(){ + solver_control_.tell_none_changed(); + } //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_recompute_ybus(){solver_control_.tell_recompute_ybus();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_recompute_sbus(){solver_control_.tell_recompute_sbus();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_solver_need_reset(){solver_control_.tell_solver_need_reset();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_ybus_change_sparsity_pattern(){solver_control_.tell_ybus_change_sparsity_pattern();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + const SolverControl & get_solver_control() const { return solver_control_;} // dc powerflow CplxVect dc_pf(const CplxVect & Vinit, @@ -254,9 +262,27 @@ class GridModel : public DataGeneric // deactivate a bus. Be careful, if a bus is deactivated, but an element is //still connected to it, it will throw an exception - void deactivate_bus(int bus_id) {_deactivate(bus_id, bus_status_, topo_changed_); } + void deactivate_bus(int bus_id) { + if(bus_status_[bus_id]){ + // bus was connected, dim of matrix change + solver_control_.need_reset_solver(); + solver_control_.need_recompute_sbus(); + solver_control_.need_recompute_ybus(); + solver_control_.ybus_change_sparsity_pattern(); + _deactivate(bus_id, bus_status_); + } + } // if a bus is connected, but isolated, it will make the powerflow diverge - void reactivate_bus(int bus_id) {_reactivate(bus_id, bus_status_, topo_changed_); } + void reactivate_bus(int bus_id) { + if(!bus_status_[bus_id]){ + // bus was not connected, dim of matrix change + solver_control_.need_reset_solver(); + solver_control_.need_recompute_sbus(); + solver_control_.need_recompute_ybus(); + solver_control_.ybus_change_sparsity_pattern(); + _reactivate(bus_id, bus_status_); + } + } int nb_bus() const; // number of activated buses Eigen::Index nb_powerline() const {return powerlines_.nb();} Eigen::Index nb_trafo() const {return trafos_.nb();} @@ -273,57 +299,57 @@ class GridModel : public DataGeneric const RealVect & get_buses() const {return bus_vn_kv_;} //deactivate a powerline (disconnect it) - void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); } - void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, topo_changed_); } - void change_bus_powerline_or(int powerline_id, int new_bus_id) {powerlines_.change_bus_or(powerline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_bus_powerline_ex(int powerline_id, int new_bus_id) {powerlines_.change_bus_ex(powerline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } + void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, solver_control_); } + void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, solver_control_); } + void change_bus_powerline_or(int powerline_id, int new_bus_id) {powerlines_.change_bus_or(powerline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_bus_powerline_ex(int powerline_id, int new_bus_id) {powerlines_.change_bus_ex(powerline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } int get_bus_powerline_or(int powerline_id) {return powerlines_.get_bus_or(powerline_id);} int get_bus_powerline_ex(int powerline_id) {return powerlines_.get_bus_ex(powerline_id);} //deactivate trafo - void deactivate_trafo(int trafo_id) {trafos_.deactivate(trafo_id, topo_changed_); } - void reactivate_trafo(int trafo_id) {trafos_.reactivate(trafo_id, topo_changed_); } - void change_bus_trafo_hv(int trafo_id, int new_bus_id) {trafos_.change_bus_hv(trafo_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_bus_trafo_lv(int trafo_id, int new_bus_id) {trafos_.change_bus_lv(trafo_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } + void deactivate_trafo(int trafo_id) {trafos_.deactivate(trafo_id, solver_control_); } + void reactivate_trafo(int trafo_id) {trafos_.reactivate(trafo_id, solver_control_); } + void change_bus_trafo_hv(int trafo_id, int new_bus_id) {trafos_.change_bus_hv(trafo_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_bus_trafo_lv(int trafo_id, int new_bus_id) {trafos_.change_bus_lv(trafo_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } int get_bus_trafo_hv(int trafo_id) {return trafos_.get_bus_hv(trafo_id);} int get_bus_trafo_lv(int trafo_id) {return trafos_.get_bus_lv(trafo_id);} //load - void deactivate_load(int load_id) {loads_.deactivate(load_id, topo_changed_); } - void reactivate_load(int load_id) {loads_.reactivate(load_id, topo_changed_); } - void change_bus_load(int load_id, int new_bus_id) {loads_.change_bus(load_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_p_load(int load_id, real_type new_p) {loads_.change_p(load_id, new_p, topo_changed_); } - void change_q_load(int load_id, real_type new_q) {loads_.change_q(load_id, new_q, topo_changed_); } + void deactivate_load(int load_id) {loads_.deactivate(load_id, solver_control_); } + void reactivate_load(int load_id) {loads_.reactivate(load_id, solver_control_); } + void change_bus_load(int load_id, int new_bus_id) {loads_.change_bus(load_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_p_load(int load_id, real_type new_p) {loads_.change_p(load_id, new_p, solver_control_); } + void change_q_load(int load_id, real_type new_q) {loads_.change_q(load_id, new_q, solver_control_); } int get_bus_load(int load_id) {return loads_.get_bus(load_id);} //generator - void deactivate_gen(int gen_id) {generators_.deactivate(gen_id, topo_changed_); } - void reactivate_gen(int gen_id) {generators_.reactivate(gen_id, topo_changed_); } - void change_bus_gen(int gen_id, int new_bus_id) {generators_.change_bus(gen_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_p_gen(int gen_id, real_type new_p) {generators_.change_p(gen_id, new_p, topo_changed_); } - void change_v_gen(int gen_id, real_type new_v_pu) {generators_.change_v(gen_id, new_v_pu, topo_changed_); } + void deactivate_gen(int gen_id) {generators_.deactivate(gen_id, solver_control_); } + void reactivate_gen(int gen_id) {generators_.reactivate(gen_id, solver_control_); } + void change_bus_gen(int gen_id, int new_bus_id) {generators_.change_bus(gen_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_p_gen(int gen_id, real_type new_p) {generators_.change_p(gen_id, new_p, solver_control_); } + void change_v_gen(int gen_id, real_type new_v_pu) {generators_.change_v(gen_id, new_v_pu, solver_control_); } int get_bus_gen(int gen_id) {return generators_.get_bus(gen_id);} //shunt - void deactivate_shunt(int shunt_id) {shunts_.deactivate(shunt_id, topo_changed_); } - void reactivate_shunt(int shunt_id) {shunts_.reactivate(shunt_id, topo_changed_); } - void change_bus_shunt(int shunt_id, int new_bus_id) {shunts_.change_bus(shunt_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_p_shunt(int shunt_id, real_type new_p) {shunts_.change_p(shunt_id, new_p, topo_changed_); } - void change_q_shunt(int shunt_id, real_type new_q) {shunts_.change_q(shunt_id, new_q, topo_changed_); } + void deactivate_shunt(int shunt_id) {shunts_.deactivate(shunt_id, solver_control_); } + void reactivate_shunt(int shunt_id) {shunts_.reactivate(shunt_id, solver_control_); } + void change_bus_shunt(int shunt_id, int new_bus_id) {shunts_.change_bus(shunt_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_p_shunt(int shunt_id, real_type new_p) {shunts_.change_p(shunt_id, new_p, solver_control_); } + void change_q_shunt(int shunt_id, real_type new_q) {shunts_.change_q(shunt_id, new_q, solver_control_); } int get_bus_shunt(int shunt_id) {return shunts_.get_bus(shunt_id);} //static gen - void deactivate_sgen(int sgen_id) {sgens_.deactivate(sgen_id, topo_changed_); } - void reactivate_sgen(int sgen_id) {sgens_.reactivate(sgen_id, topo_changed_); } - void change_bus_sgen(int sgen_id, int new_bus_id) {sgens_.change_bus(sgen_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_p_sgen(int sgen_id, real_type new_p) {sgens_.change_p(sgen_id, new_p, topo_changed_); } - void change_q_sgen(int sgen_id, real_type new_q) {sgens_.change_q(sgen_id, new_q, topo_changed_); } + void deactivate_sgen(int sgen_id) {sgens_.deactivate(sgen_id, solver_control_); } + void reactivate_sgen(int sgen_id) {sgens_.reactivate(sgen_id, solver_control_); } + void change_bus_sgen(int sgen_id, int new_bus_id) {sgens_.change_bus(sgen_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_p_sgen(int sgen_id, real_type new_p) {sgens_.change_p(sgen_id, new_p, solver_control_); } + void change_q_sgen(int sgen_id, real_type new_q) {sgens_.change_q(sgen_id, new_q, solver_control_); } int get_bus_sgen(int sgen_id) {return sgens_.get_bus(sgen_id);} //storage units - void deactivate_storage(int storage_id) {storages_.deactivate(storage_id, topo_changed_); } - void reactivate_storage(int storage_id) {storages_.reactivate(storage_id, topo_changed_); } - void change_bus_storage(int storage_id, int new_bus_id) {storages_.change_bus(storage_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } + void deactivate_storage(int storage_id) {storages_.deactivate(storage_id, solver_control_); } + void reactivate_storage(int storage_id) {storages_.reactivate(storage_id, solver_control_); } + void change_bus_storage(int storage_id, int new_bus_id) {storages_.change_bus(storage_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } void change_p_storage(int storage_id, real_type new_p) { // if(new_p == 0.) // { @@ -334,19 +360,19 @@ class GridModel : public DataGeneric // reactivate_storage(storage_id); // requirement from grid2op, might be discussed // storages_.change_p(storage_id, new_p, need_reset_); // } - storages_.change_p(storage_id, new_p, topo_changed_); + storages_.change_p(storage_id, new_p, solver_control_); } - void change_q_storage(int storage_id, real_type new_q) {storages_.change_q(storage_id, new_q, topo_changed_); } + void change_q_storage(int storage_id, real_type new_q) {storages_.change_q(storage_id, new_q, solver_control_); } int get_bus_storage(int storage_id) {return storages_.get_bus(storage_id);} //deactivate a powerline (disconnect it) - void deactivate_dcline(int dcline_id) {dc_lines_.deactivate(dcline_id, topo_changed_); } - void reactivate_dcline(int dcline_id) {dc_lines_.reactivate(dcline_id, topo_changed_); } - void change_p_dcline(int dcline_id, real_type new_p) {dc_lines_.change_p(dcline_id, new_p, topo_changed_); } - void change_v_or_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_or(dcline_id, new_v_pu, topo_changed_); } - void change_v_ex_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_ex(dcline_id, new_v_pu, topo_changed_); } - void change_bus_dcline_or(int dcline_id, int new_bus_id) {dc_lines_.change_bus_or(dcline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } - void change_bus_dcline_ex(int dcline_id, int new_bus_id) {dc_lines_.change_bus_ex(dcline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); } + void deactivate_dcline(int dcline_id) {dc_lines_.deactivate(dcline_id, solver_control_); } + void reactivate_dcline(int dcline_id) {dc_lines_.reactivate(dcline_id, solver_control_); } + void change_p_dcline(int dcline_id, real_type new_p) {dc_lines_.change_p(dcline_id, new_p, solver_control_); } + void change_v_or_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_or(dcline_id, new_v_pu, solver_control_); } + void change_v_ex_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_ex(dcline_id, new_v_pu, solver_control_); } + void change_bus_dcline_or(int dcline_id, int new_bus_id) {dc_lines_.change_bus_or(dcline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } + void change_bus_dcline_ex(int dcline_id, int new_bus_id) {dc_lines_.change_bus_ex(dcline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); } int get_bus_dcline_or(int dcline_id) {return dc_lines_.get_bus_or(dcline_id);} int get_bus_dcline_ex(int dcline_id) {return dc_lines_.get_bus_ex(dcline_id);} @@ -550,7 +576,7 @@ class GridModel : public DataGeneric std::vector & id_solver_to_me, Eigen::VectorXi & slack_bus_id_solver, bool is_ac, - bool reset_solver); + const SolverControl & solver_control); void init_Ybus(Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector& id_solver_to_me); @@ -561,7 +587,8 @@ class GridModel : public DataGeneric void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver); void fillSbus_me(CplxVect & res, bool ac, const std::vector& id_me_to_solver); void fillpv_pq(const std::vector& id_me_to_solver, std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver); + Eigen::VectorXi & slack_bus_id_solver, + const SolverControl & solver_control); // results /**process the results from the solver to this instance @@ -652,8 +679,11 @@ class GridModel : public DataGeneric // member of the grid // static const int _deactivated_bus_id; - bool need_reset_; - bool topo_changed_; + // bool need_reset_solver_; // some matrices change size, needs to be computed + // bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it + // bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern + // bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff) + SolverControl solver_control_; bool compute_results_; real_type init_vm_pu_; // default vm initialization, mainly for dc powerflow real_type sn_mva_; diff --git a/src/Utils.h b/src/Utils.h index f8a6d021..7bbf95f4 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -49,4 +49,69 @@ enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue #define VERSION_MINOR -1 #endif +class SolverControl +{ + public: + SolverControl(): + change_dimension_(true), + pv_changed_(true), + pq_changed_(true), + slack_participate_changed_(true), + need_reset_solver_(true), + need_recompute_sbus_(true), + need_recompute_ybus_(true), + ybus_change_sparsity_pattern_(true) + {}; + + void tell_all_changed(){ + change_dimension_ = true; + pv_changed_ = true; + pq_changed_ = true; + slack_participate_changed_ = true; + need_reset_solver_ = true; + need_recompute_sbus_ = true; + need_recompute_ybus_ = true; + ybus_change_sparsity_pattern_ = true; + } + + void tell_none_changed(){ + change_dimension_ = false; + pv_changed_ = false; + pq_changed_ = true; + slack_participate_changed_ = false; + need_reset_solver_ = false; + need_recompute_sbus_ = false; + need_recompute_ybus_ = false; + ybus_change_sparsity_pattern_ = false; + } + + void tell_dimension_changed(){change_dimension_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_pv_changed(){pv_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_pq_changed(){pq_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_slack_participate_changed(){slack_participate_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_recompute_ybus(){need_recompute_ybus_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_recompute_sbus(){need_recompute_sbus_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_solver_need_reset(){need_reset_solver_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + void tell_ybus_change_sparsity_pattern(){ybus_change_sparsity_pattern_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + + bool has_dimension_changed() const {return change_dimension_;} + bool has_pv_changed() const {return pv_changed_;} + bool has_pq_changed() const {return pq_changed_;} + bool has_tell_slack_participate_changed() const {return slack_participate_changed_;} + bool need_reset_solver() const {return need_reset_solver_;} + bool need_recompute_sbus() const {return need_recompute_sbus_;} + bool need_recompute_ybus() const {return need_recompute_ybus_;} + bool ybus_change_sparsity_pattern() const {return ybus_change_sparsity_pattern_;} + + protected: + bool change_dimension_; + bool pv_changed_; + bool pq_changed_; + bool slack_participate_changed_; + bool need_reset_solver_; // some matrices change size, needs to be computed + bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it + bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern + bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff), or ybus change of dimension +}; + #endif // UTILS_H diff --git a/src/main.cpp b/src/main.cpp index c5130a44..f44cb456 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -583,6 +583,19 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_line_param", &PandaPowerConverter::get_line_param) .def("get_trafo_param", &PandaPowerConverter::get_trafo_param); + + py::class_(m, "SolverControl", "TODO") + .def(py::init<>()) + .def("has_dimension_changed", &SolverControl::has_dimension_changed, "TODO") + .def("has_pv_changed", &SolverControl::has_pv_changed, "TODO") + .def("has_pq_changed", &SolverControl::has_pq_changed, "TODO") + .def("has_tell_slack_participate_changed", &SolverControl::has_tell_slack_participate_changed, "TODO") + .def("need_reset_solver", &SolverControl::need_reset_solver, "TODO") + .def("need_recompute_sbus", &SolverControl::need_recompute_sbus, "TODO") + .def("need_recompute_ybus", &SolverControl::need_recompute_ybus, "TODO") + .def("ybus_change_sparsity_pattern", &SolverControl::ybus_change_sparsity_pattern, "TODO") + ; + py::class_(m, "GridModel", DocGridModel::GridModel.c_str()) .def(py::init<>()) .def("copy", &GridModel::copy) @@ -775,8 +788,12 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("reactivate_result_computation", &GridModel::reactivate_result_computation, DocGridModel::reactivate_result_computation.c_str()) .def("dc_pf", &GridModel::dc_pf, DocGridModel::dc_pf.c_str()) .def("ac_pf", &GridModel::ac_pf, DocGridModel::ac_pf.c_str()) - .def("unset_topo_changed", &GridModel::unset_topo_changed, DocGridModel::_internal_do_not_use.c_str()) - .def("tell_topo_changed", &GridModel::tell_topo_changed, DocGridModel::_internal_do_not_use.c_str()) + .def("unset_changes", &GridModel::unset_changes, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_recompute_ybus", &GridModel::tell_recompute_ybus, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_recompute_sbus", &GridModel::tell_recompute_sbus, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_solver_need_reset", &GridModel::tell_solver_need_reset, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_ybus_change_sparsity_pattern", &GridModel::tell_ybus_change_sparsity_pattern, DocGridModel::_internal_do_not_use.c_str()) + .def("get_solver_control", &GridModel::get_solver_control, "TODO") .def("compute_newton", &GridModel::ac_pf, DocGridModel::ac_pf.c_str()) // apply action faster (optimized for grid2op representation) From 93f90dda9656e36a9ee38f26092ec6a47a88caaa Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 23 Oct 2023 17:35:05 +0200 Subject: [PATCH 14/66] fixing some broken tests --- lightsim2grid/lightSimBackend.py | 4 ++-- lightsim2grid/tests/test_backend_pypowsybl.py | 6 +++--- lightsim2grid/tests/test_basic_backend_api.py | 16 +++++----------- src/GridModel.cpp | 3 +-- src/GridModel.h | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 461b13db..60a20671 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -513,14 +513,13 @@ def make_complete_path(path, filename): for i in range(self.__nb_bus_before): self._grid.deactivate_bus(i + self.__nb_bus_before) new_orig_to_ls = np.concatenate((orig_to_ls, - np.zeros(orig_to_ls.shape[0], dtype=orig_to_ls.dtype) + self.__nb_bus_before) + orig_to_ls + self.__nb_bus_before) ) self._grid._orig_to_ls = new_orig_to_ls self.nb_bus_total = len(self._grid.get_buses()) # and now things needed by the backend (legacy) self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)] - self._aux_finish_setup_after_reading() self.prod_pu_to_kv = 1.0 * self._grid.get_buses()[[el.bus_id for el in self._grid.get_generators()]] self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float) @@ -530,6 +529,7 @@ def make_complete_path(path, filename): bus_vn_kv = np.array(self._grid.get_buses()) shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()]) self._sh_vnkv = bus_vn_kv[shunt_bus_id] + self._aux_finish_setup_after_reading() def _aux_setup_right_after_grid_init(self): self._handle_turnedoff_pv() diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py index 4cc8528d..bce90c25 100644 --- a/lightsim2grid/tests/test_backend_pypowsybl.py +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -114,9 +114,9 @@ def get_casefile(self): return "grid.xiidm" def make_backend(self, detailed_infos_for_cascading_failures=False): - return LightSimBackend(loader_method="pypowsybl", - loader_kwargs=_aux_get_loader_kwargs_storage(), - detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + return LightSimBackend(loader_method="pypowsybl", + loader_kwargs=_aux_get_loader_kwargs_storage(), + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) # # add test of grid2op for the backend based on pypowsybl # def this_make_backend(self, detailed_infos_for_cascading_failures=False): diff --git a/lightsim2grid/tests/test_basic_backend_api.py b/lightsim2grid/tests/test_basic_backend_api.py index c404bad7..08a8dde0 100644 --- a/lightsim2grid/tests/test_basic_backend_api.py +++ b/lightsim2grid/tests/test_basic_backend_api.py @@ -17,17 +17,11 @@ from lightsim2grid import LightSimBackend if CAN_PERFORM_THESE: - def this_make_backend(self, detailed_infos_for_cascading_failures=False): - return LightSimBackend( - detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures - ) - add_name_cls = "test_LightSimBackend" - - res = create_test_suite(make_backend_fun=this_make_backend, - add_name_cls=add_name_cls, - add_to_module=__name__, - extended_test=False, # for now keep `extended_test=False` until all problems are solved - ) + from grid2op.tests.aaa_test_backend_interface import AAATestBackendAPI + class TestBackendAPI_LSTester(AAATestBackendAPI, unittest.TestCase): + def make_backend(self, detailed_infos_for_cascading_failures=False): + return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + else: print("Have you installed grid2op in dev / editable mode ? We cannot make the `create_test_suite` :-(") diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 31b01353..2ee2fbb7 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -14,8 +14,6 @@ GridModel::GridModel(const GridModel & other) { reset(true, true, true); - set_ls_to_orig(other._ls_to_orig); // set also orig_to_ls - init_vm_pu_ = other.init_vm_pu_; sn_mva_ = other.sn_mva_; @@ -23,6 +21,7 @@ GridModel::GridModel(const GridModel & other) // 1. bus bus_vn_kv_ = other.bus_vn_kv_; bus_status_ = other.bus_status_; + set_ls_to_orig(other._ls_to_orig); // set also orig_to_ls // 2. powerline powerlines_ = other.powerlines_; diff --git a/src/GridModel.h b/src/GridModel.h index 751a9608..85b384fb 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -49,7 +49,7 @@ class GridModel : public DataGeneric int, // version major int, // version medium int, // version minor - std::vector, // ls_to_pp + std::vector, // ls_to_orig real_type, // init_vm_pu real_type, //sn_mva std::vector, // bus_vn_kv From 1a2ababb25f64839c6f9a30308cdbaa2effce7c6 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 24 Oct 2023 08:53:37 +0200 Subject: [PATCH 15/66] fix broken tests --- .github/workflows/main.yml | 10 +++++++++- src/GridModel.cpp | 13 +++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc48a5a5..8ce98074 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -195,12 +195,16 @@ jobs: - name: Check package can be imported run: | + mkdir tmp_for_import_checking + cd tmp_for_import_checking python -c "import lightsim2grid" python -c "from lightsim2grid import *" python -c "from lightsim2grid.newtonpf import newtonpf" + cd .. - name: Check LightSimBackend can be imported run: | + cd tmp_for_import_checking python -m pip install grid2op python -c "from lightsim2grid import LightSimBackend" python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())" @@ -253,16 +257,20 @@ jobs: - name: Check package can be imported run: | + mkdir tmp_for_import_checking + cd tmp_for_import_checking python -c "import lightsim2grid" python -c "from lightsim2grid import *" python -c "from lightsim2grid.newtonpf import newtonpf" + cd .. - name: Check LightSimBackend can be imported run: | + cd tmp_for_import_checking python -m pip install grid2op python -c "from lightsim2grid import LightSimBackend" python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())" - + - name: Upload wheel uses: actions/upload-artifact@v3 with: diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 2ee2fbb7..ca07f9aa 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -188,6 +188,12 @@ void GridModel::set_state(GridModel::StateRes & my_state) }; void GridModel::set_ls_to_orig(const IntVect & ls_to_orig){ + if(ls_to_orig.size() == 0){ + _ls_to_orig = IntVect(); + _orig_to_ls = IntVect(); + return; + } + if(ls_to_orig.size() != bus_vn_kv_.size()) throw std::runtime_error("Impossible to set the converter ls_to_orig: the provided vector has not the same size as the number of bus on the grid."); _ls_to_orig = ls_to_orig; @@ -200,11 +206,18 @@ void GridModel::set_ls_to_orig(const IntVect & ls_to_orig){ } void GridModel::set_orig_to_ls(const IntVect & orig_to_ls){ + if(orig_to_ls.size() == 0){ + _ls_to_orig = IntVect(); + _orig_to_ls = IntVect(); + return; + } _orig_to_ls = orig_to_ls; Eigen::Index nb_bus_ls = 0; for(const auto el : orig_to_ls){ if (el != -1) nb_bus_ls += 1; } + if(nb_bus_ls != bus_vn_kv_.size()) + throw std::runtime_error("Impossible to set the converter orig_to_ls: the number of 'non -1' component in the provided vector does not match the number of buses on the grid."); _ls_to_orig = IntVect::Zero(nb_bus_ls); Eigen::Index ls2or_ind = 0; for(auto or2ls_ind = 0; or2ls_ind < nb_bus_ls; ++or2ls_ind){ From 8691f0b86596a0cb8ca52dc7258c19fbc2f68e3b Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 27 Oct 2023 15:17:58 +0200 Subject: [PATCH 16/66] improving reading from iidm file, fixing broken tests --- CHANGELOG.rst | 6 +- lightsim2grid/gridmodel/from_pypowsybl.py | 100 ++++++++++++++------ lightsim2grid/lightSimBackend.py | 27 +++--- lightsim2grid/tests/test_LightSimBackend.py | 88 +++++++---------- lightsim2grid/tests/test_RedispatchEnv.py | 77 +++------------ lightsim2grid/tests/test_Runner.py | 2 +- src/DataDCLine.h | 5 + src/DataGen.cpp | 18 ++++ src/DataGen.h | 1 + src/DataGeneric.h | 1 + src/DataLine.cpp | 31 ++++++ src/DataLine.h | 1 + src/DataLoad.cpp | 18 ++++ src/DataLoad.h | 1 + src/DataSGen.cpp | 21 +++- src/DataSGen.h | 1 + src/DataShunt.cpp | 20 +++- src/DataShunt.h | 1 + src/DataTrafo.cpp | 35 ++++++- src/DataTrafo.h | 3 +- src/GridModel.h | 14 ++- src/main.cpp | 4 +- 22 files changed, 304 insertions(+), 171 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7ccb37f2..fd3a497a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,7 +25,11 @@ Change Log - [FIXED] now voltage is properly set to 0. when storage units are disconnected - [FIXED] a bug where non connected grid were not spotted in DC - [FIXED] a bug when trying to set the slack for a non existing genererator -- [IMPROVED] now making the new grid2op `create_test_suite` +- [FIXED] a bug in init from pypowsybl when some object were disconnected. It raises + an error (because they are not connected to a bus): now this function properly handles + these cases. +- [IMPROVED] now performing the new grid2op `create_test_suite` +- [IMPROVED] now lightsim2grid properly throw BackendError [0.7.5] 2023-10-05 -------------------- diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index c8ad3d35..70f04b10 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -6,14 +6,33 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -import copy import numpy as np import pypowsybl as pypo -# import pypowsybl.loadflow as lf from lightsim2grid_cpp import GridModel +def _aux_get_bus(bus_df, df, conn_key="connected", bus_key="bus_id"): + if df.shape[0] == 0: + # no element of this type so no problem + return np.zeros(0, dtype=int), np.ones(0, dtype=bool) + # retrieve which elements are disconnected + mask_disco = ~df[conn_key] + if mask_disco.all(): + raise RuntimeError("All element of the same type are disconnected, the init will not work.") + first_el_co = np.where(~mask_disco.values)[0][0] + # retrieve the bus where the element are + tmp_bus_id = df[bus_key].copy() + tmp_bus_id[mask_disco] = df.iloc[first_el_co][bus_key] # assign a "random" bus to disco element + bus_id = bus_df.loc[tmp_bus_id.values]["bus_id"].values + # deactivate the element not on the main component + wrong_component = bus_df.loc[tmp_bus_id.values]["connected_component"].values != 0 + mask_disco[wrong_component] = True + # assign bus -1 to disconnected elements + bus_id[mask_disco] = -1 + return bus_id , mask_disco.values + + def init(net : pypo.network, gen_slack_id: int = None, slack_bus_id: int = None, @@ -50,18 +69,22 @@ def init(net : pypo.network, df_gen = net.get_generators().sort_index() else: 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 * 0.5 + 1. max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 0.5 - 1. + gen_bus, gen_disco = _aux_get_bus(bus_df, df_gen) model.init_generators(df_gen["target_p"].values, df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values, min_q, max_q, - 1 * bus_df.loc[df_gen["bus_id"].values]["bus_id"].values + gen_bus ) + for gen_id, is_disco in enumerate(gen_disco): + if is_disco: + model.deactivate_gen(gen_id) + # TODO dist slack if gen_slack_id is not None: model.add_gen_slackbus(gen_slack_id, 1.) @@ -82,10 +105,14 @@ def init(net : pypo.network, df_load = net.get_loads().sort_index() else: df_load = net.get_loads() + load_bus, load_disco = _aux_get_bus(bus_df, df_load) model.init_loads(df_load["p0"].values, df_load["q0"].values, - 1 * bus_df.loc[df_load["bus_id"].values]["bus_id"].values + load_bus ) + for load_id, is_disco in enumerate(load_disco): + if is_disco: + model.deactivate_load(load_id) # for lines if sort_index: @@ -117,13 +144,18 @@ def init(net : pypo.network, g2 = df_line["g2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.real*v2/sn_mva line_h_or = (b1 + 1j * g1) line_h_ex = (b2 + 1j * g2) + lor_bus, lor_disco = _aux_get_bus(bus_df, df_line, conn_key="connected1", bus_key="bus1_id") + lex_bus, lex_disco = _aux_get_bus(bus_df, df_line, conn_key="connected2", bus_key="bus2_id") model.init_powerlines_full(line_r, line_x, line_h_or, line_h_ex, - 1 * bus_df.loc[df_line["bus1_id"].values]["bus_id"].values, - 1 * bus_df.loc[df_line["bus2_id"].values]["bus_id"].values + lor_bus, + lex_bus ) + for line_id, (is_or_disc, is_ex_disc) in enumerate(zip(lor_disco, lex_disco)): + if is_or_disc or is_ex_disc: + model.deactivate_powerline(line_id) # for trafo if sort_index: @@ -145,6 +177,8 @@ def init(net : pypo.network, has_tap = tap_step_pct != 0. tap_pos[has_tap] += 1 tap_step_pct[~has_tap] = 1.0 # or any other values... + tor_bus, tor_disco = _aux_get_bus(bus_df, df_trafo, conn_key="connected1", bus_key="bus1_id") + tex_bus, tex_disco = _aux_get_bus(bus_df, df_trafo, conn_key="connected2", bus_key="bus2_id") model.init_trafo(df_trafo["r"].values / trafo_to_pu, df_trafo["x"].values / trafo_to_pu, 2.*(1j*df_trafo["g"].values + df_trafo["b"].values) * trafo_to_pu, @@ -152,8 +186,11 @@ def init(net : pypo.network, tap_pos, shift_, is_tap_hv_side, - 1 * bus_df.loc[df_trafo["bus1_id"].values]["bus_id"].values, # TODO do I need to change hv / lv - 1 * bus_df.loc[df_trafo["bus2_id"].values]["bus_id"].values) + tor_bus, # TODO do I need to change hv / lv + tex_bus) + for t_id, (is_or_disc, is_ex_disc) in enumerate(zip(tor_disco, tex_disco)): + if is_or_disc or is_ex_disc: + model.deactivate_trafo(t_id) # for shunt if sort_index: @@ -161,35 +198,27 @@ def init(net : pypo.network, else: df_shunt = net.get_shunt_compensators() - is_on = copy.deepcopy(df_shunt["connected"]) - if (~is_on).any(): - df_shunt["connected"] = True - net.update_shunt_compensators(df_shunt[["connected"]]) - if sort_index: - df_shunt = net.get_shunt_compensators().sort_index() - else: - df_shunt = net.get_shunt_compensators() - df_shunt["connected"] = is_on - net.update_shunt_compensators(df_shunt[["connected"]]) - + sh_bus, sh_disco = _aux_get_bus(bus_df, df_shunt) shunt_kv = voltage_levels.loc[df_shunt["voltage_level_id"].values]["nominal_v"].values model.init_shunt(-df_shunt["g"].values * shunt_kv**2, -df_shunt["b"].values * shunt_kv**2, - 1 * bus_df.loc[df_shunt["bus_id"].values]["bus_id"].values + sh_bus ) - for shunt_id, conn in enumerate(is_on): - if not conn: + for shunt_id, disco in enumerate(sh_disco): + if disco: model.deactivate_shunt(shunt_id) # for hvdc (TODO not tested yet) df_dc = net.get_hvdc_lines().sort_index() df_sations = net.get_vsc_converter_stations().sort_index() - bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values - bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values + # bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values + # bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values + bus_from_id, hvdc_from_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station1_id"].values]) + bus_to_id, hvdc_to_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station2_id"].values]) loss_percent = np.zeros(df_dc.shape[0]) # TODO loss_mw = np.zeros(df_dc.shape[0]) # TODO - model.init_dclines(bus_df.loc[bus_from_id]["bus_id"].values, - bus_df.loc[bus_to_id]["bus_id"].values, + model.init_dclines(bus_from_id, + bus_to_id, df_dc["target_p"].values, loss_percent, loss_mw, @@ -200,16 +229,24 @@ def init(net : pypo.network, df_sations.loc[df_dc["converter_station2_id"].values]["min_q"].values, df_sations.loc[df_dc["converter_station2_id"].values]["max_q"].values ) - + # TODO will probably not work ! + for hvdc_id, (is_or_disc, is_ex_disc) in enumerate(zip(hvdc_from_disco, hvdc_to_disco)): + if is_or_disc or is_ex_disc: + model.deactivate_hvdc(hvdc_id) + # storage units (TODO not tested yet) if sort_index: df_batt = net.get_batteries().sort_index() else: df_batt = net.get_batteries() + batt_bus, batt_disco = _aux_get_bus(bus_df, df_batt) model.init_storages(df_batt["target_p"].values, df_batt["target_q"].values, - 1 * bus_df.loc[df_batt["bus_id"].values]["bus_id"].values + batt_bus ) + for batt_id, disco in enumerate(batt_disco): + if disco: + model.deactivate_storage(batt_id) # TODO # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO @@ -217,5 +254,8 @@ def init(net : pypo.network, # TODO checks # no 3windings trafo and other exotic stuff if net.get_phase_tap_changers().shape[0] > 0: - raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") + pass + # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") + model.init_bus_status() + np.sum(model.get_bus_status()) return model diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 60a20671..6b068e4c 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -14,7 +14,7 @@ from grid2op.Action import CompleteAction from grid2op.Backend import Backend -from grid2op.Exceptions import BackendError, DivergingPowerFlow +from grid2op.Exceptions import BackendError from grid2op.dtypes import dt_float, dt_int, dt_bool try: from grid2op.Action._backendAction import _BackendAction @@ -497,7 +497,7 @@ def make_complete_path(path, filename): self._compute_pos_big_topo() self.__nb_powerline = len(self._grid.get_lines()) - self.__nb_bus_before = len(self._grid.get_buses()) + self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) # init this self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) @@ -506,7 +506,7 @@ def make_complete_path(path, filename): # now handle the legacy "make as if there are 2 busbars per substation" # as it is done with grid2Op simulated environment if "double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]: - bus_init = self._grid.get_buses() + bus_init = self._grid.get_bus_vn_kv() orig_to_ls = np.array(self._grid._orig_to_ls) bus_doubled = np.concatenate((bus_init, bus_init)) self._grid.init_bus(bus_doubled, 0, 0) @@ -516,17 +516,17 @@ def make_complete_path(path, filename): orig_to_ls + self.__nb_bus_before) ) self._grid._orig_to_ls = new_orig_to_ls - self.nb_bus_total = len(self._grid.get_buses()) + self.nb_bus_total = len(self._grid.get_bus_vn_kv()) # and now things needed by the backend (legacy) self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)] - self.prod_pu_to_kv = 1.0 * self._grid.get_buses()[[el.bus_id for el in self._grid.get_generators()]] + self.prod_pu_to_kv = 1.0 * self._grid.get_bus_vn_kv()[[el.bus_id for el in self._grid.get_generators()]] self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float) # TODO max_not_too_max = (np.finfo(dt_float).max * 0.5 - 1.) self.thermal_limit_a = max_not_too_max * np.ones(self.n_line, dtype=dt_float) - bus_vn_kv = np.array(self._grid.get_buses()) + bus_vn_kv = np.array(self._grid.get_bus_vn_kv()) shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()]) self._sh_vnkv = bus_vn_kv[shunt_bus_id] self._aux_finish_setup_after_reading() @@ -886,7 +886,7 @@ def runpf(self, is_dc=False): V = self._grid.dc_pf(self.V, self.max_it, self.tol) self._timer_solver += time.perf_counter() - tick if V.shape[0] == 0: - raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid). Detailed error: {self._grid.get_dc_solver().get_error()}") + raise BackendError(f"Divergence of DC powerflow (non connected grid). Detailed error: {self._grid.get_dc_solver().get_error()}") else: if (self.V is None) or (self.V.shape[0] == 0): # create the vector V as it is not created @@ -898,7 +898,7 @@ def runpf(self, is_dc=False): Vdc = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it, self.tol) self._grid.reactivate_result_computation() if Vdc.shape[0] == 0: - raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}") + raise BackendError(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}") V_init = Vdc else: V_init = copy.deepcopy(self.V) @@ -907,7 +907,7 @@ def runpf(self, is_dc=False): V = self._grid.ac_pf(V_init, self.max_it, self.tol) self._timer_solver += time.perf_counter() - tick if V.shape[0] == 0: - raise DivergingPowerFlow(f"Divergence of AC powerflow. Detailed error: {self._grid.get_solver().get_error()}") + raise BackendError(f"Divergence of AC powerflow. Detailed error: {self._grid.get_solver().get_error()}") beg_postroc = time.perf_counter() if is_dc: @@ -953,12 +953,12 @@ def runpf(self, is_dc=False): disco = (~np.isfinite(self.load_v)) | (self.load_v <= 0.) load_disco = np.where(disco)[0] self._timer_postproc += time.perf_counter() - beg_postroc - raise DivergingPowerFlow(f"At least one load is disconnected (check loads {load_disco})") + raise BackendError(f"At least one load is disconnected (check loads {load_disco})") if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.): disco = (~np.isfinite(self.prod_v)) | (self.prod_v <= 0.) gen_disco = np.where(disco)[0] self._timer_postproc += time.perf_counter() - beg_postroc - raise DivergingPowerFlow(f"At least one generator is disconnected (check gen {gen_disco})") + raise BackendError(f"At least one generator is disconnected (check gen {gen_disco})") # TODO storage case of divergence ! if type(self).shunts_data_available: @@ -967,7 +967,7 @@ def runpf(self, is_dc=False): self._fill_theta() if (self.line_or_theta >= 1e6).any() or (self.line_ex_theta >= 1e6).any(): - raise DivergingPowerFlow(f"Some theta are above 1e6 which should not be happening !") + raise BackendError(f"Some theta are above 1e6 which should not be happening !") res = True self._grid.unset_topo_changed() self._timer_postproc += time.perf_counter() - beg_postroc @@ -977,7 +977,8 @@ def runpf(self, is_dc=False): self._fill_nans() res = False my_exc_ = exc_ - + if not isinstance(my_exc_, BackendError): + my_exc_ = BackendError(f"Converted the error of type {type(my_exc_)}, message was: {my_exc_}") if is_dc: # set back the solver to its previous state self._grid.change_solver(self.__current_solver_type) diff --git a/lightsim2grid/tests/test_LightSimBackend.py b/lightsim2grid/tests/test_LightSimBackend.py index f1a8a627..52103bd5 100644 --- a/lightsim2grid/tests/test_LightSimBackend.py +++ b/lightsim2grid/tests/test_LightSimBackend.py @@ -46,13 +46,23 @@ def tearDown(self) -> None: except ImportError as exc_: # old way of doing, need to inherit from that from grid2op.tests.helper_path_test import HelperTests -from grid2op.tests.BaseBackendTest import BaseTestNames, BaseTestLoadingCase, BaseTestLoadingBackendFunc -from grid2op.tests.BaseBackendTest import BaseTestTopoAction, BaseTestEnvPerformsCorrectCascadingFailures -from grid2op.tests.BaseBackendTest import BaseTestChangeBusAffectRightBus, BaseTestShuntAction -from grid2op.tests.BaseBackendTest import BaseTestResetEqualsLoadGrid, BaseTestVoltageOWhenDisco, BaseTestChangeBusSlack -from grid2op.tests.BaseBackendTest import BaseIssuesTest, BaseStatusActions -from grid2op.tests.test_Environment import TestLoadingBackendPandaPower, TestResetOk -from grid2op.tests.test_Environment import TestResetAfterCascadingFailure, TestCascadingFailure +from grid2op.tests.BaseBackendTest import (BaseTestNames, + BaseTestLoadingCase, + BaseTestLoadingBackendFunc, + BaseTestTopoAction, + BaseTestEnvPerformsCorrectCascadingFailures, + BaseTestChangeBusAffectRightBus, + BaseTestShuntAction, + BaseTestResetEqualsLoadGrid, + BaseTestVoltageOWhenDisco, + BaseTestChangeBusSlack, + BaseIssuesTest, + BaseStatusActions) + +from grid2op.tests.test_Environment import (BaseTestLoadingBackendPandaPower, + BaseTestResetOk, + BaseTestResetAfterCascadingFailure, + BaseTestCascadingFailure) if __has_storage: from grid2op.tests.BaseBackendTest import BaseTestStorageAction @@ -64,32 +74,23 @@ def tearDown(self) -> None: from grid2op.Runner import Runner -class TestNames(HelperTests, BaseTestNames): +class TestNames(BaseTestNames, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST_INIT - -class TestLoadingCase(HelperTests, BaseTestLoadingCase): +class TestLoadingCase(BaseTestLoadingCase, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST - - def get_casefile(self): - return "test_case14.json" - -class TestLoadingBackendFunc(HelperTests, BaseTestLoadingBackendFunc): +class TestLoadingBackendFunc(BaseTestLoadingBackendFunc, unittest.TestCase): def setUp(self): # TODO find something more elegant BaseTestLoadingBackendFunc.setUp(self) @@ -111,14 +112,8 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST - def get_casefile(self): - return "test_case14.json" - - -class TestTopoAction(HelperTests, BaseTestTopoAction): +class TestTopoAction(BaseTestTopoAction, unittest.TestCase): def setUp(self): BaseTestTopoAction.setUp(self) @@ -132,14 +127,8 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST - - def get_casefile(self): - return "test_case14.json" - -class TestEnvPerformsCorrectCascadingFailures(HelperTests, BaseTestEnvPerformsCorrectCascadingFailures): +class TestEnvPerformsCorrectCascadingFailures(BaseTestEnvPerformsCorrectCascadingFailures, unittest.TestCase): def setUp(self): BaseTestEnvPerformsCorrectCascadingFailures.setUp(self) @@ -152,15 +141,9 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): warnings.filterwarnings("ignore") bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk + - def get_casefile(self): - return "test_case14.json" - - def get_path(self): - return PATH_DATA_TEST - - -class TestChangeBusAffectRightBus(HelperTests, BaseTestChangeBusAffectRightBus): +class TestChangeBusAffectRightBus(BaseTestChangeBusAffectRightBus, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -168,7 +151,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestShuntAction(HelperTests, BaseTestShuntAction): +class TestShuntAction(BaseTestShuntAction, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -176,7 +159,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestResetEqualsLoadGrid(HelperTests, BaseTestResetEqualsLoadGrid): +class TestResetEqualsLoadGrid(BaseTestResetEqualsLoadGrid, unittest.TestCase): def setUp(self): BaseTestResetEqualsLoadGrid.setUp(self) @@ -187,7 +170,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestVoltageOWhenDisco(HelperTests, BaseTestVoltageOWhenDisco): +class TestVoltageOWhenDisco(BaseTestVoltageOWhenDisco, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -195,7 +178,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestChangeBusSlack(HelperTests, BaseTestChangeBusSlack): +class TestChangeBusSlack(BaseTestChangeBusSlack, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -203,7 +186,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestIssuesTest(HelperTests, BaseIssuesTest): +class TestIssuesTest(BaseIssuesTest, unittest.TestCase): tests_skipped = ["test_issue_125"] if version.parse(grid2op.__version__) < version.parse("1.9.2") else [] def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): @@ -212,7 +195,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestStatusAction(HelperTests, BaseStatusActions): +class TestStatusAction(BaseStatusActions, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -221,8 +204,9 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): if __has_storage: - class TestStorageAction(HelperTests, BaseTestStorageAction): + class TestStorageAction(BaseTestStorageAction, unittest.TestCase): def setUp(self): + super().setUp() self.tests_skipped = ["test_storage_action_topo"] # TODO this test is super weird ! It's like we impose # TODO a behaviour from pandapower (weird one) to all backends... @@ -233,22 +217,22 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestLoadingBackendLightSim(TestLoadingBackendPandaPower): +class TestLoadingBackendLightSim(BaseTestLoadingBackendPandaPower, unittest.TestCase): def get_backend(self, detailed_infos_for_cascading_failures=True): return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) -class TestResetOkLS(TestResetOk): +class TestResetOkLS(BaseTestResetOk, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) -class TestResetAfterCascadingFailureLS(TestResetAfterCascadingFailure): +class TestResetAfterCascadingFailureLS(BaseTestResetAfterCascadingFailure, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) -class TestCascadingFailureLS(TestCascadingFailure): +class TestCascadingFailureLS(BaseTestCascadingFailure, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) diff --git a/lightsim2grid/tests/test_RedispatchEnv.py b/lightsim2grid/tests/test_RedispatchEnv.py index f49451bc..e7658fd3 100644 --- a/lightsim2grid/tests/test_RedispatchEnv.py +++ b/lightsim2grid/tests/test_RedispatchEnv.py @@ -5,75 +5,36 @@ # you can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + import unittest import warnings -from grid2op.tests.helper_path_test import PATH_DATA_TEST_PP, PATH_DATA_TEST - -from grid2op.tests.helper_path_test import HelperTests -from grid2op.tests.BaseRedispTest import BaseTestRedispatch, BaseTestRedispatchChangeNothingEnvironment -from grid2op.tests.BaseRedispTest import BaseTestRedispTooLowHigh, BaseTestDispatchRampingIllegalETC -from grid2op.tests.BaseRedispTest import BaseTestLoadingAcceptAlmostZeroSumRedisp +from grid2op.tests.BaseRedispTest import (BaseTestRedispatch, + BaseTestRedispatchChangeNothingEnvironment, + BaseTestRedispTooLowHigh, + BaseTestDispatchRampingIllegalETC, + BaseTestLoadingAcceptAlmostZeroSumRedisp) from lightsim2grid.lightSimBackend import LightSimBackend -PATH_DATA_TEST_INIT = PATH_DATA_TEST -PATH_DATA_TEST = PATH_DATA_TEST_PP - - -class TestRedispatch(HelperTests, BaseTestRedispatch): - def setUp(self): - # TODO find something more elegant - BaseTestRedispatch.setUp(self) - - def tearDown(self): - # TODO find something more elegant - BaseTestRedispatch.tearDown(self) +class TestRedispatch(BaseTestRedispatch, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST_PP - - def get_casefile(self): - return "test_case14.json" - - -class TestRedispatchChangeNothingEnvironment(HelperTests, BaseTestRedispatchChangeNothingEnvironment): - def setUp(self): - # TODO find something more elegant - BaseTestRedispatchChangeNothingEnvironment.setUp(self) - - def tearDown(self): - # TODO find something more elegant - BaseTestRedispatchChangeNothingEnvironment.tearDown(self) +class TestRedispatchChangeNothingEnvironment(BaseTestRedispatchChangeNothingEnvironment, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") bk = LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) return bk - def get_path(self): - return PATH_DATA_TEST_PP - - def get_casefile(self): - return "test_case14.json" - - -class TestRedispTooLowHigh(HelperTests, BaseTestRedispTooLowHigh): - def setUp(self): - # TODO find something more elegant - BaseTestRedispTooLowHigh.setUp(self) - - def tearDown(self): - # TODO find something more elegant - BaseTestRedispTooLowHigh.tearDown(self) +class TestRedispTooLowHigh(BaseTestRedispTooLowHigh, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -81,15 +42,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestDispatchRampingIllegalETC(HelperTests, BaseTestDispatchRampingIllegalETC): - def setUp(self): - # TODO find something more elegant - BaseTestDispatchRampingIllegalETC.setUp(self) - - def tearDown(self): - # TODO find something more elegant - BaseTestDispatchRampingIllegalETC.tearDown(self) - +class TestDispatchRampingIllegalETC(BaseTestDispatchRampingIllegalETC, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -97,15 +50,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return bk -class TestLoadingAcceptAlmostZeroSumRedisp(HelperTests, BaseTestLoadingAcceptAlmostZeroSumRedisp): - def setUp(self): - # TODO find something more elegant - BaseTestLoadingAcceptAlmostZeroSumRedisp.setUp(self) - - def tearDown(self): - # TODO find something more elegant - BaseTestLoadingAcceptAlmostZeroSumRedisp.tearDown(self) - +class TestLoadingAcceptAlmostZeroSumRedisp(BaseTestLoadingAcceptAlmostZeroSumRedisp, unittest.TestCase): def make_backend(self, detailed_infos_for_cascading_failures=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore") diff --git a/lightsim2grid/tests/test_Runner.py b/lightsim2grid/tests/test_Runner.py index e998a8db..734c992a 100644 --- a/lightsim2grid/tests/test_Runner.py +++ b/lightsim2grid/tests/test_Runner.py @@ -11,7 +11,7 @@ import grid2op from grid2op.tests.test_Runner import TestRunner as TestRunner_glop from grid2op.tests.test_RunnerFast import TestRunner as TestRunnerFast_glop -from grid2op.tests.test_Runner import HelperTests, L2RPNReward, make +from grid2op.tests.test_Runner import HelperTests, L2RPNReward from grid2op.Runner import Runner from lightsim2grid.lightSimBackend import LightSimBackend diff --git a/src/DataDCLine.h b/src/DataDCLine.h index 185be259..492ac9a2 100644 --- a/src/DataDCLine.h +++ b/src/DataDCLine.h @@ -172,6 +172,11 @@ class DataDCLine : public DataGeneric to_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} int get_bus_or(int dcline_id) {return from_gen_.get_bus(dcline_id);} int get_bus_ex(int dcline_id) {return to_gen_.get_bus(dcline_id);} + virtual void reconnect_connected_buses(std::vector & bus_status) const { + from_gen_.reconnect_connected_buses(bus_status); + to_gen_.reconnect_connected_buses(bus_status); + } + real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);} real_type get_qmax_or(int dcline_id) {return from_gen_.get_qmax(dcline_id);} real_type get_qmin_ex(int dcline_id) {return to_gen_.get_qmin(dcline_id);} diff --git a/src/DataGen.cpp b/src/DataGen.cpp index 238a6586..d90fbc99 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -400,3 +400,21 @@ void DataGen::update_slack_weights(Eigen::Ref & bus_status) const { + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + const auto my_bus = bus_id_(gen_id); + if(my_bus == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataGen::reconnect_connected_buses: Generator with id "; + exc_ << gen_id; + exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_gen(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[my_bus] = true; // this bus is connected + } +} \ No newline at end of file diff --git a/src/DataGen.h b/src/DataGen.h index 237f118e..4ddd0e32 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -178,6 +178,7 @@ class DataGen: public DataGeneric void reactivate(int gen_id, bool & need_reset) {_reactivate(gen_id, status_, need_reset);} void change_bus(int gen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);} int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} + virtual void reconnect_connected_buses(std::vector & bus_status) const; real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} void change_p(int gen_id, real_type new_p, bool & need_reset); diff --git a/src/DataGeneric.h b/src/DataGeneric.h index 8d9bc606..13cd0ecd 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -90,6 +90,7 @@ class DataGeneric : public BaseConstants void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver) {}; static const int _deactivated_bus_id; + virtual void reconnect_connected_buses(std::vector & bus_status) const {}; /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/ virtual ~DataGeneric() {}; diff --git a/src/DataLine.cpp b/src/DataLine.cpp index ecf4f590..c7eae7d7 100644 --- a/src/DataLine.cpp +++ b/src/DataLine.cpp @@ -390,3 +390,34 @@ void DataLine::compute_results(const Eigen::Ref & Va, _get_amps(res_powerline_aor_, res_powerline_por_, res_powerline_qor_, res_powerline_vor_); _get_amps(res_powerline_aex_, res_powerline_pex_, res_powerline_qex_, res_powerline_vex_); } + +void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ + + const auto my_size = powerlines_r_.size(); + for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ + // don't do anything if the element is disconnected + if(!status_[line_id]) continue; + + const auto bus_or_id_me = bus_or_id_(line_id); + if(bus_or_id_me == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << line_id; + exc_ << " is connected (origin) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[bus_or_id_me] = true; + + const auto bus_ex_id_me = bus_ex_id_(line_id); + if(bus_ex_id_me == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << line_id; + exc_ << " is connected (ext) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[bus_ex_id_me] = true; + } +} \ No newline at end of file diff --git a/src/DataLine.h b/src/DataLine.h index dae3e8a5..0de8db69 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -177,6 +177,7 @@ class DataLine : public DataGeneric } return LineInfo(*this, id); } + virtual void reconnect_connected_buses(std::vector & bus_status) const; void deactivate(int powerline_id, bool & need_reset) {_deactivate(powerline_id, status_, need_reset);} void reactivate(int powerline_id, bool & need_reset) {_reactivate(powerline_id, status_, need_reset);} diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp index 003ca81c..11e72c11 100644 --- a/src/DataLoad.cpp +++ b/src/DataLoad.cpp @@ -118,3 +118,21 @@ void DataLoad::change_q(int load_id, real_type new_q, bool & need_reset) } q_mvar_(load_id) = new_q; } + +void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { + const int nb_load = nb(); + for(int load_id = 0; load_id < nb_load; ++load_id) + { + if(!status_[load_id]) continue; + const auto my_bus = bus_id_(load_id); + if(my_bus == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataLoad::reconnect_connected_buses: Load with id "; + exc_ << load_id; + exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_load(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[my_bus] = true; // this bus is connected + } +} diff --git a/src/DataLoad.h b/src/DataLoad.h index 78effd64..11ab50c9 100644 --- a/src/DataLoad.h +++ b/src/DataLoad.h @@ -138,6 +138,7 @@ class DataLoad : public DataGeneric int get_bus(int load_id) {return _get_bus(load_id, status_, bus_id_);} void change_p(int load_id, real_type new_p, bool & need_reset); void change_q(int load_id, real_type new_q, bool & need_reset); + virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp index c0c6fec7..b1c5ef91 100644 --- a/src/DataSGen.cpp +++ b/src/DataSGen.cpp @@ -34,7 +34,6 @@ void DataSGen::init(const RealVect & sgen_p, status_ = std::vector(sgen_p.size(), true); } - DataSGen::StateRes DataSGen::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); @@ -48,6 +47,7 @@ DataSGen::StateRes DataSGen::get_state() const DataSGen::StateRes res(p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); return res; } + void DataSGen::set_state(DataSGen::StateRes & my_state ) { reset_results(); @@ -81,7 +81,6 @@ void DataSGen::set_state(DataSGen::StateRes & my_state ) status_ = status; } - void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { const int nb_sgen = nb(); int bus_id_me, bus_id_solver; @@ -154,3 +153,21 @@ void DataSGen::change_q(int sgen_id, real_type new_q, bool & need_reset) } q_mvar_(sgen_id) = new_q; } + +void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { + const int nb_sgen = nb(); + for(int sgen_id = 0; sgen_id < nb_sgen; ++sgen_id) + { + if(!status_[sgen_id]) continue; + const auto my_bus = bus_id_(sgen_id); + if(my_bus == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataSGen::reconnect_connected_buses: Static Generator with id "; + exc_ << sgen_id; + exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_sgen(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[my_bus] = true; // this bus is connected + } +} diff --git a/src/DataSGen.h b/src/DataSGen.h index 41ec3ac0..1f189cb4 100644 --- a/src/DataSGen.h +++ b/src/DataSGen.h @@ -158,6 +158,7 @@ class DataSGen: public DataGeneric int get_bus(int sgen_id) {return _get_bus(sgen_id, status_, bus_id_);} void change_p(int sgen_id, real_type new_p, bool & need_reset); void change_q(int sgen_id, real_type new_q, bool & need_reset); + virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const ; diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp index 4c697b20..37caf15d 100644 --- a/src/DataShunt.cpp +++ b/src/DataShunt.cpp @@ -19,7 +19,6 @@ void DataShunt::init(const RealVect & shunt_p_mw, status_ = std::vector(p_mw_.size(), true); // by default everything is connected } - DataShunt::StateRes DataShunt::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); @@ -29,6 +28,7 @@ DataShunt::StateRes DataShunt::get_state() const DataShunt::StateRes res(p_mw, q_mvar, bus_id, status); return res; } + void DataShunt::set_state(DataShunt::StateRes & my_state ) { reset_results(); @@ -182,3 +182,21 @@ void DataShunt::change_q(int shunt_id, real_type new_q, bool & need_reset) if(q_mvar_(shunt_id) != new_q) need_reset = true; q_mvar_(shunt_id) = new_q; } + +void DataShunt::reconnect_connected_buses(std::vector & bus_status) const { + const int nb_shunt = nb(); + for(int shunt_id = 0; shunt_id < nb_shunt; ++shunt_id) + { + if(!status_[shunt_id]) continue; + const auto my_bus = bus_id_(shunt_id); + if(my_bus == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataShunt::reconnect_connected_buses: Shunt with id "; + exc_ << shunt_id; + exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_shunt(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[my_bus] = true; // this bus is connected + } +} diff --git a/src/DataShunt.h b/src/DataShunt.h index 9a74be97..0b8ed631 100644 --- a/src/DataShunt.h +++ b/src/DataShunt.h @@ -131,6 +131,7 @@ class DataShunt : public DataGeneric void change_p(int shunt_id, real_type new_p, bool & need_reset); void change_q(int shunt_id, real_type new_q, bool & need_reset); int get_bus(int shunt_id) {return _get_bus(shunt_id, status_, bus_id_);} + virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void fillYbus(std::vector > & res, bool ac, diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp index ab536040..19e858e8 100644 --- a/src/DataTrafo.cpp +++ b/src/DataTrafo.cpp @@ -369,7 +369,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, // temp_branch[:, SHIFT] = zeros(nl) ## zero out phase shifters // if alg == 3: ## if BX method // temp_branch[:, BR_R] = zeros(nl) ## zero out line resistance - const Eigen::Index nb_trafo = static_cast(nb()); + const Eigen::Index nb_trafo = nb(); real_type yft_bp, ytf_bp, yff_bp, ytt_bp; real_type yft_bpp, ytf_bpp, yff_bpp, ytt_bpp; @@ -450,4 +450,35 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, Bpp.push_back(Eigen::Triplet (bus_ex_solver_id, bus_ex_solver_id, -ytt_bpp)); } -} \ No newline at end of file +} + +void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ + + const Eigen::Index nb_trafo = nb(); + for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ + // don't do anything if the element is disconnected + if(!status_[trafo_id]) continue; + + const auto bus_or_id_me = bus_hv_id_(trafo_id); + if(bus_or_id_me == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << trafo_id; + exc_ << " is connected (hv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[bus_or_id_me] = true; + + const auto bus_ex_id_me = bus_lv_id_(trafo_id); + if(bus_ex_id_me == _deactivated_bus_id){ + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << trafo_id; + exc_ << " is connected (lv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; + throw std::runtime_error(exc_.str()); + } + bus_status[bus_ex_id_me] = true; + } +} diff --git a/src/DataTrafo.h b/src/DataTrafo.h index 84f29e61..41b6c002 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -174,7 +174,8 @@ class DataTrafo : public DataGeneric void change_bus_lv(int trafo_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_lv_id_, need_reset, nb_bus);} int get_bus_hv(int trafo_id) {return _get_bus(trafo_id, status_, bus_hv_id_);} int get_bus_lv(int trafo_id) {return _get_bus(trafo_id, status_, bus_lv_id_);} - + void reconnect_connected_buses(std::vector & bus_status) const; + virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); virtual void fillYbus(std::vector > & res, bool ac, diff --git a/src/GridModel.h b/src/GridModel.h index 85b384fb..0c129bc4 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -213,6 +213,18 @@ class GridModel : public DataGeneric loss_percent, loss_mw, vm_or_pu, vm_ex_pu, min_q_or, max_q_or, min_q_ex, max_q_ex); } + void init_bus_status(){ + const int nb_bus = static_cast(bus_status_.size()); + for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false; + powerlines_.reconnect_connected_buses(bus_status_); + shunts_.reconnect_connected_buses(bus_status_); + trafos_.reconnect_connected_buses(bus_status_); + generators_.reconnect_connected_buses(bus_status_); + loads_.reconnect_connected_buses(bus_status_); + sgens_.reconnect_connected_buses(bus_status_); + storages_.reconnect_connected_buses(bus_status_); + dc_lines_.reconnect_connected_buses(bus_status_); + } void add_gen_slackbus(int gen_id, real_type weight); void remove_gen_slackbus(int gen_id); @@ -270,7 +282,7 @@ class GridModel : public DataGeneric const DataLoad & get_storages() const {return storages_;} const DataSGen & get_static_generators() const {return sgens_;} const DataShunt & get_shunts() const {return shunts_;} - const RealVect & get_buses() const {return bus_vn_kv_;} + const std::vector & get_bus_status() const {return bus_status_;} //deactivate a powerline (disconnect it) void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); } diff --git a/src/main.cpp b/src/main.cpp index c5130a44..44325792 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -623,6 +623,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) // init the grid .def("init_bus", &GridModel::init_bus, DocGridModel::_internal_do_not_use.c_str()) + .def("init_bus_status", &GridModel::init_bus_status, DocGridModel::_internal_do_not_use.c_str()) .def("set_init_vm_pu", &GridModel::set_init_vm_pu, DocGridModel::_internal_do_not_use.c_str()) // TODO use python "property" for that .def("get_init_vm_pu", &GridModel::get_init_vm_pu, DocGridModel::_internal_do_not_use.c_str()) .def("set_sn_mva", &GridModel::set_sn_mva, DocGridModel::_internal_do_not_use.c_str()) // TODO use python "property" for that @@ -650,7 +651,8 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_shunts", &GridModel::get_shunts, DocGridModel::get_shunts.c_str()) .def("get_storages", &GridModel::get_storages, DocGridModel::get_storages.c_str()) .def("get_loads", &GridModel::get_loads, DocGridModel::get_loads.c_str()) - .def("get_buses", &GridModel::get_buses, DocGridModel::_internal_do_not_use.c_str()) + .def("get_bus_vn_kv", &GridModel::get_bus_vn_kv, DocGridModel::_internal_do_not_use.c_str()) + .def("get_bus_status", &GridModel::get_bus_status, DocGridModel::_internal_do_not_use.c_str()) // modify the grid .def("turnedoff_no_pv", &GridModel::turnedoff_no_pv, "Turned off (or generators with p = 0) generators will not be pv buses, they will not maintain voltage") From 8cefb7fc1ad85534e7d4ad3b45e0188782259bde Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 27 Oct 2023 16:37:43 +0200 Subject: [PATCH 17/66] force the seed in some broken tests --- lightsim2grid/tests/test_dist_slack_backend.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py index 966c0ea3..02382aef 100644 --- a/lightsim2grid/tests/test_dist_slack_backend.py +++ b/lightsim2grid/tests/test_dist_slack_backend.py @@ -91,14 +91,20 @@ def test_after_reset(self): _ = self.env_ds.reset() self._prepare_env(self.env_ds) self._aux_test_different(self.env_ss, self.env_ds) - + + def _aux_get_kwargs_runner(self): + return dict(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, seed=0) + def test_after_runner(self): """test I can use the runner""" runner_ss = Runner(**self.env_ss.get_params_for_runner()) runner_ds = Runner(**self.env_ds.get_params_for_runner()) - res_ss = runner_ss.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) - res_ds = runner_ds.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) - assert res_ss[0][3] == res_ds[0][3], f"{res_ss[0][3]} vs {res_ds[0][3]}" # same number of steps survived + res_ss = runner_ss.run(self._aux_get_kwargs_runner()) + res_ds = runner_ds.run(self._aux_get_kwargs_runner()) + import pdb + pdb.set_trace() + if res_ss[0][3] != res_ds[0][3]: # same number of steps survived + raise RuntimeError(f"{res_ss[0][3]} vs {res_ds[0][3]}: ") assert res_ss[0][2] != res_ds[0][2] # not the same reward ep_ss = res_ss[0][-1] ep_ds = res_ds[0][-1] From 36c6829d81170038d19103cd63aed95659a8fa7c Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 27 Oct 2023 16:38:23 +0200 Subject: [PATCH 18/66] force the seed in some broken tests --- lightsim2grid/tests/test_dist_slack_backend.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py index 02382aef..b51f2818 100644 --- a/lightsim2grid/tests/test_dist_slack_backend.py +++ b/lightsim2grid/tests/test_dist_slack_backend.py @@ -101,8 +101,6 @@ def test_after_runner(self): runner_ds = Runner(**self.env_ds.get_params_for_runner()) res_ss = runner_ss.run(self._aux_get_kwargs_runner()) res_ds = runner_ds.run(self._aux_get_kwargs_runner()) - import pdb - pdb.set_trace() if res_ss[0][3] != res_ds[0][3]: # same number of steps survived raise RuntimeError(f"{res_ss[0][3]} vs {res_ds[0][3]}: ") assert res_ss[0][2] != res_ds[0][2] # not the same reward From c80aaa6ced33b102e26771b10168d36e3c1ba1af Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 27 Oct 2023 17:02:21 +0200 Subject: [PATCH 19/66] force the seed in some broken tests --- lightsim2grid/tests/test_dist_slack_backend.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py index b51f2818..40c492ae 100644 --- a/lightsim2grid/tests/test_dist_slack_backend.py +++ b/lightsim2grid/tests/test_dist_slack_backend.py @@ -93,14 +93,17 @@ def test_after_reset(self): self._aux_test_different(self.env_ss, self.env_ds) def _aux_get_kwargs_runner(self): - return dict(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, seed=0) + return dict(nb_episode=1, + max_iter=self.max_iter_real, + add_detailed_output=True, + env_seeds=[0]) def test_after_runner(self): """test I can use the runner""" runner_ss = Runner(**self.env_ss.get_params_for_runner()) runner_ds = Runner(**self.env_ds.get_params_for_runner()) - res_ss = runner_ss.run(self._aux_get_kwargs_runner()) - res_ds = runner_ds.run(self._aux_get_kwargs_runner()) + res_ss = runner_ss.run(**self._aux_get_kwargs_runner()) + res_ds = runner_ds.run(**self._aux_get_kwargs_runner()) if res_ss[0][3] != res_ds[0][3]: # same number of steps survived raise RuntimeError(f"{res_ss[0][3]} vs {res_ds[0][3]}: ") assert res_ss[0][2] != res_ds[0][2] # not the same reward From ef6b8061dd174eb3f910941b1f0da0ec42bd89e2 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 7 Nov 2023 18:04:05 +0100 Subject: [PATCH 20/66] adding more methods, mainly to extract main component of grid --- CHANGELOG.rst | 2 + lightsim2grid/gridmodel/from_pypowsybl.py | 42 ++++---- src/DataDCLine.cpp | 39 +++++++ src/DataDCLine.h | 12 ++- src/DataGen.cpp | 26 ++++- src/DataGen.h | 34 ++++++ src/DataGeneric.h | 6 ++ src/DataLine.cpp | 41 +++++++- src/DataLine.h | 3 + src/DataLoad.cpp | 13 +++ src/DataLoad.h | 1 + src/DataSGen.cpp | 24 +++++ src/DataSGen.h | 4 +- src/DataShunt.cpp | 13 +++ src/DataShunt.h | 3 +- src/DataTrafo.cpp | 40 +++++++ src/DataTrafo.h | 4 + src/GridModel.cpp | 123 ++++++++++++++++++++-- src/GridModel.h | 3 +- src/main.cpp | 3 +- 20 files changed, 404 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fd3a497a..b812f78f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,6 +28,8 @@ Change Log - [FIXED] a bug in init from pypowsybl when some object were disconnected. It raises an error (because they are not connected to a bus): now this function properly handles these cases. +- [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this + one. - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw BackendError diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 70f04b10..3e1b7033 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -84,21 +84,6 @@ def init(net : pypo.network, for gen_id, is_disco in enumerate(gen_disco): if is_disco: model.deactivate_gen(gen_id) - - # TODO dist slack - if gen_slack_id is not None: - model.add_gen_slackbus(gen_slack_id, 1.) - elif slack_bus_id is not None: - gen_bus = np.array([el.bus_id for el in model.get_generators()]) - gen_is_conn_slack = gen_bus == model._orig_to_ls[slack_bus_id] - nb_conn = gen_is_conn_slack.sum() - if nb_conn == 0: - raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack") - for gen_id, is_slack in enumerate(gen_is_conn_slack): - if is_slack: - model.add_gen_slackbus(gen_id, 1. / nb_conn) - else: - model.add_gen_slackbus(0, 1.) # for loads if sort_index: @@ -247,7 +232,26 @@ def init(net : pypo.network, for batt_id, disco in enumerate(batt_disco): if disco: model.deactivate_storage(batt_id) - + + # TODO dist slack + if gen_slack_id is None and slack_bus_id is None: + # if nothing is given, by default I assign a slack bus to a bus where a lot of lines are connected + # quite central in the grid + bus_id, gen_id = model.assign_slack_to_most_connected() + elif gen_slack_id is not None: + if slack_bus_id is not None: + raise RuntimeError(f"You provided both gen_slack_id and slack_bus_id which is not possible.") + model.add_gen_slackbus(gen_slack_id, 1.) + elif slack_bus_id is not None: + gen_bus = np.array([el.bus_id for el in model.get_generators()]) + gen_is_conn_slack = gen_bus == model._orig_to_ls[slack_bus_id] + nb_conn = gen_is_conn_slack.sum() + if nb_conn == 0: + raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack") + for gen_id, is_slack in enumerate(gen_is_conn_slack): + if is_slack: + model.add_gen_slackbus(gen_id, 1. / nb_conn) + # TODO # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO @@ -256,6 +260,8 @@ def init(net : pypo.network, if net.get_phase_tap_changers().shape[0] > 0: pass # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") - model.init_bus_status() - np.sum(model.get_bus_status()) + + # and now deactivate all elements and nodes not in the main component + # TODO DC LINE: one side might be in the connected comp and not the other ! + model.consider_only_main_component() return model diff --git a/src/DataDCLine.cpp b/src/DataDCLine.cpp index cc008f97..45694567 100644 --- a/src/DataDCLine.cpp +++ b/src/DataDCLine.cpp @@ -58,3 +58,42 @@ void DataDCLine::init(const Eigen::VectorXi & branch_from_id, } to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id); } + +void DataDCLine::nb_line_end(std::vector & res) const +{ + auto nb = from_gen_.nb(); + const auto & bus_or_id = get_bus_id_or(); + const auto & bus_ex_id = get_bus_id_ex(); + for(unsigned int i = 0; i < nb; ++i){ + if(!status_[i]) continue; + auto bus_or = bus_or_id(i); + auto bus_ex = bus_ex_id(i); + res[bus_or] += 1; + res[bus_ex] += 1; + } +} + +// TODO DC LINE: one side might be in the connected comp and not the other ! +void DataDCLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +{ + auto nb = from_gen_.nb(); + const auto & bus_or_id = get_bus_id_or(); + const auto & bus_ex_id = get_bus_id_ex(); + for(unsigned int i = 0; i < nb; ++i){ + if(!status_[i]) continue; + auto bus_or = bus_or_id(i); + auto bus_ex = bus_ex_id(i); + if(!busbar_in_main_component[bus_or]) { + bool tmp = false; + from_gen_.deactivate(i, tmp); + } + if(!busbar_in_main_component[bus_ex]) { + bool tmp = false; + to_gen_.deactivate(i, tmp); + } + // if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ + // bool tmp = false; + // deactivate(i, tmp); + // } + } +} diff --git a/src/DataDCLine.h b/src/DataDCLine.h index 492ac9a2..042791ce 100644 --- a/src/DataDCLine.h +++ b/src/DataDCLine.h @@ -172,10 +172,18 @@ class DataDCLine : public DataGeneric to_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} int get_bus_or(int dcline_id) {return from_gen_.get_bus(dcline_id);} int get_bus_ex(int dcline_id) {return to_gen_.get_bus(dcline_id);} + + // for buses only connected through dc line, i don't add them + // they are not in the same "connected component" virtual void reconnect_connected_buses(std::vector & bus_status) const { - from_gen_.reconnect_connected_buses(bus_status); - to_gen_.reconnect_connected_buses(bus_status); + // from_gen_.reconnect_connected_buses(bus_status); + // to_gen_.reconnect_connected_buses(bus_status); } + // for buses only connected through dc line, i don't add them + // they are not in the same "connected component" + virtual void get_graph(std::vector > & res) const {}; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + virtual void nb_line_end(std::vector & res) const; real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);} real_type get_qmax_or(int dcline_id) {return from_gen_.get_qmax(dcline_id);} diff --git a/src/DataGen.cpp b/src/DataGen.cpp index d90fbc99..3d064459 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -417,4 +417,28 @@ void DataGen::reconnect_connected_buses(std::vector & bus_status) const { } bus_status[my_bus] = true; // this bus is connected } -} \ No newline at end of file +} + +void DataGen::gen_p_per_bus(std::vector & res) const +{ + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + const auto my_bus = bus_id_(gen_id); + res[my_bus] += p_mw_(gen_id); + } +} + +void DataGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + const auto my_bus = bus_id_(gen_id); + if(!busbar_in_main_component[my_bus]){ + bool tmp = false; + deactivate(gen_id, tmp); + } + } +} diff --git a/src/DataGen.h b/src/DataGen.h index 4ddd0e32..a4c41cd0 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -154,11 +154,41 @@ class DataGen: public DataGeneric void add_slackbus(int gen_id, real_type weight){ gen_slackbus_[gen_id] = true; gen_slack_weight_[gen_id] = weight; + // TODO DEBUG MODE + if(weight <= 0.) throw std::runtime_error("DataGen::add_slackbus Cannot assign a negative weight to the slack bus."); } void remove_slackbus(int gen_id){ gen_slackbus_[gen_id] = false; gen_slack_weight_[gen_id] = 0.; } + void remove_all_slackbus(){ + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + remove_slackbus(gen_id); + } + } + // returns only the gen_id with the highest p that is connected to this bus ! + int assign_slack_bus(int slack_bus_id, const std::vector & gen_p_per_bus){ + const int nb_gen = nb(); + int res_gen_id = -1; + real_type max_p = -1.; + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + if(bus_id_(gen_id) != slack_bus_id) continue; + const real_type p_mw = p_mw_(gen_id); + add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id]); + if((p_mw > max_p) || (res_gen_id == -1) ){ + res_gen_id = gen_id; + max_p = p_mw; + } + } + // TODO DEBUG MODE + if(res_gen_id == -1) throw std::runtime_error("DataGen::assign_slack_bus No generator connected to the desired buses"); + return res_gen_id; + } + /** Retrieve the normalized (=sum to 1.000) slack weights for all the buses **/ @@ -179,6 +209,8 @@ class DataGen: public DataGeneric void change_bus(int gen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);} int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} void change_p(int gen_id, real_type new_p, bool & need_reset); @@ -216,6 +248,8 @@ class DataGen: public DataGeneric **/ void set_vm(CplxVect & V, const std::vector & id_grid_to_solver) const; + virtual void gen_p_per_bus(std::vector & res) const; + tuple3d get_res() const {return tuple3d(res_p_, res_q_, res_v_);} Eigen::Ref get_theta() const {return res_theta_;} diff --git a/src/DataGeneric.h b/src/DataGeneric.h index 13cd0ecd..85e8e1e2 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -92,6 +92,12 @@ class DataGeneric : public BaseConstants static const int _deactivated_bus_id; virtual void reconnect_connected_buses(std::vector & bus_status) const {}; + /**computes the total amount of power for each bus (for generator only)**/ + virtual void gen_p_per_bus(std::vector & res) const {}; + virtual void nb_line_end(std::vector & res) const {}; + virtual void get_graph(std::vector > & res) const {}; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) {}; + /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/ virtual ~DataGeneric() {}; diff --git a/src/DataLine.cpp b/src/DataLine.cpp index c7eae7d7..541df7d4 100644 --- a/src/DataLine.cpp +++ b/src/DataLine.cpp @@ -420,4 +420,43 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ } bus_status[bus_ex_id_me] = true; } -} \ No newline at end of file +} + +void DataLine::nb_line_end(std::vector & res) const{ + const auto my_size = powerlines_r_.size(); + for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ + // don't do anything if the element is disconnected + if(!status_[line_id]) continue; + const auto bus_or = bus_or_id_(line_id); + const auto bus_ex = bus_ex_id_(line_id); + res[bus_or] += 1; + res[bus_ex] += 1; + } +} + +void DataLine::get_graph(std::vector > & res) const +{ + const auto my_size = powerlines_r_.size(); + for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ + // don't do anything if the element is disconnected + if(!status_[line_id]) continue; + const auto bus_or = bus_or_id_(line_id); + const auto bus_ex = bus_ex_id_(line_id); + res.push_back(Eigen::Triplet(bus_or, bus_ex, 1.)); + res.push_back(Eigen::Triplet(bus_ex, bus_or, 1.)); + } +} + +void DataLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +{ + auto nb_line = nb(); + for(unsigned int i = 0; i < nb_line; ++i){ + if(!status_[i]) continue; + auto bus_or = bus_or_id_(i); + auto bus_ex = bus_ex_id_(i); + if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ + bool tmp = false; + deactivate(i, tmp); + } + } +} diff --git a/src/DataLine.h b/src/DataLine.h index 0de8db69..85ecd4fb 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -178,6 +178,9 @@ class DataLine : public DataGeneric return LineInfo(*this, id); } virtual void reconnect_connected_buses(std::vector & bus_status) const; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + virtual void nb_line_end(std::vector & res) const; + virtual void get_graph(std::vector > & res) const; void deactivate(int powerline_id, bool & need_reset) {_deactivate(powerline_id, status_, need_reset);} void reactivate(int powerline_id, bool & need_reset) {_reactivate(powerline_id, status_, need_reset);} diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp index 11e72c11..041d53e2 100644 --- a/src/DataLoad.cpp +++ b/src/DataLoad.cpp @@ -136,3 +136,16 @@ void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { bus_status[my_bus] = true; // this bus is connected } } + +void DataLoad::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ + const int nb_el = nb(); + for(int el_id = 0; el_id < nb_el; ++el_id) + { + if(!status_[el_id]) continue; + const auto my_bus = bus_id_(el_id); + if(!busbar_in_main_component[my_bus]){ + bool tmp = false; + deactivate(el_id, tmp); + } + } +} diff --git a/src/DataLoad.h b/src/DataLoad.h index 11ab50c9..aebd6b54 100644 --- a/src/DataLoad.h +++ b/src/DataLoad.h @@ -139,6 +139,7 @@ class DataLoad : public DataGeneric void change_p(int load_id, real_type new_p, bool & need_reset); void change_q(int load_id, real_type new_q, bool & need_reset); virtual void reconnect_connected_buses(std::vector & bus_status) const; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp index b1c5ef91..dc9a2b82 100644 --- a/src/DataSGen.cpp +++ b/src/DataSGen.cpp @@ -171,3 +171,27 @@ void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { bus_status[my_bus] = true; // this bus is connected } } + +void DataSGen::gen_p_per_bus(std::vector & res) const +{ + const int nb_gen = nb(); + for(int sgen_id = 0; sgen_id < nb_gen; ++sgen_id) + { + if(!status_[sgen_id]) continue; + const auto my_bus = bus_id_(sgen_id); + res[my_bus] += p_mw_(sgen_id); + } +} + +void DataSGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ + const int nb_el = nb(); + for(int el_id = 0; el_id < nb_el; ++el_id) + { + if(!status_[el_id]) continue; + const auto my_bus = bus_id_(el_id); + if(!busbar_in_main_component[my_bus]){ + bool tmp = false; + deactivate(el_id, tmp); + } + } +} diff --git a/src/DataSGen.h b/src/DataSGen.h index 1f189cb4..c2a5c7e7 100644 --- a/src/DataSGen.h +++ b/src/DataSGen.h @@ -159,8 +159,10 @@ class DataSGen: public DataGeneric void change_p(int sgen_id, real_type new_p, bool & need_reset); void change_q(int sgen_id, real_type new_q, bool & need_reset); virtual void reconnect_connected_buses(std::vector & bus_status) const; - + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const ; + virtual void gen_p_per_bus(std::vector & res) const; void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp index 37caf15d..42e057f5 100644 --- a/src/DataShunt.cpp +++ b/src/DataShunt.cpp @@ -200,3 +200,16 @@ void DataShunt::reconnect_connected_buses(std::vector & bus_status) const bus_status[my_bus] = true; // this bus is connected } } + +void DataShunt::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ + const int nb_el = nb(); + for(int el_id = 0; el_id < nb_el; ++el_id) + { + if(!status_[el_id]) continue; + const auto my_bus = bus_id_(el_id); + if(!busbar_in_main_component[my_bus]){ + bool tmp = false; + deactivate(el_id, tmp); + } + } +} diff --git a/src/DataShunt.h b/src/DataShunt.h index 0b8ed631..31e030c3 100644 --- a/src/DataShunt.h +++ b/src/DataShunt.h @@ -132,7 +132,8 @@ class DataShunt : public DataGeneric void change_q(int shunt_id, real_type new_q, bool & need_reset); int get_bus(int shunt_id) {return _get_bus(shunt_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; - + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + virtual void fillYbus(std::vector > & res, bool ac, const std::vector & id_grid_to_solver, diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp index 19e858e8..568f0e9f 100644 --- a/src/DataTrafo.cpp +++ b/src/DataTrafo.cpp @@ -482,3 +482,43 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ bus_status[bus_ex_id_me] = true; } } + +void DataTrafo::nb_line_end(std::vector & res) const{ + + const Eigen::Index nb_trafo = nb(); + for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ + // don't do anything if the element is disconnected + if(!status_[trafo_id]) continue; + const auto bus_or = bus_hv_id_(trafo_id); + const auto bus_ex = bus_lv_id_(trafo_id); + res[bus_or] += 1; + res[bus_ex] += 1; + } +} + +void DataTrafo::get_graph(std::vector > & res) const +{ + const auto my_size = nb(); + for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ + // don't do anything if the element is disconnected + if(!status_[line_id]) continue; + const auto bus_or = bus_hv_id_(line_id); + const auto bus_ex = bus_lv_id_(line_id); + res.push_back(Eigen::Triplet(bus_or, bus_ex, 1.)); + res.push_back(Eigen::Triplet(bus_ex, bus_or, 1.)); + } +} + +void DataTrafo::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +{ + auto nb_line = nb(); + for(unsigned int i = 0; i < nb_line; ++i){ + if(!status_[i]) continue; + auto bus_or = bus_hv_id_(i); + auto bus_ex = bus_lv_id_(i); + if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ + bool tmp = false; + deactivate(i, tmp); + } + } +} diff --git a/src/DataTrafo.h b/src/DataTrafo.h index 41b6c002..f162f60e 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -175,6 +175,10 @@ class DataTrafo : public DataGeneric int get_bus_hv(int trafo_id) {return _get_bus(trafo_id, status_, bus_hv_id_);} int get_bus_lv(int trafo_id) {return _get_bus(trafo_id, status_, bus_lv_id_);} void reconnect_connected_buses(std::vector & bus_status) const; + virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); + + virtual void nb_line_end(std::vector & res) const; + virtual void get_graph(std::vector > & res) const; virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); virtual void fillYbus(std::vector > & res, diff --git a/src/GridModel.cpp b/src/GridModel.cpp index ca07f9aa..e8fbc755 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -8,6 +8,7 @@ #include "GridModel.h" #include "ChooseSolver.h" // to avoid circular references +#include GridModel::GridModel(const GridModel & other) @@ -558,7 +559,7 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st // init the Ybus matrix std::vector > tripletList; tripletList.reserve(bus_vn_kv_.size() + 4*powerlines_.nb() + 4*trafos_.nb() + shunts_.nb()); - powerlines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); + powerlines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); // TODO have a function to dispatch that to all type of elements shunts_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); trafos_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); loads_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); @@ -573,7 +574,7 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id_me_to_solver) { // init the Sbus vector - powerlines_.fillSbus(Sbus, id_me_to_solver, ac); + powerlines_.fillSbus(Sbus, id_me_to_solver, ac); // TODO have a function to dispatch that to all type of elements trafos_.fillSbus(Sbus, id_me_to_solver, ac); shunts_.fillSbus(Sbus, id_me_to_solver, ac); loads_.fillSbus(Sbus, id_me_to_solver, ac); @@ -601,7 +602,7 @@ void GridModel::fillpv_pq(const std::vector& id_me_to_solver, bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); - powerlines_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); + powerlines_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); // TODO have a function to dispatch that to all type of elements shunts_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); trafos_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); loads_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); @@ -627,7 +628,7 @@ void GridModel::compute_results(bool ac){ const std::vector & id_me_to_solver = ac ? id_me_to_ac_solver_ : id_me_to_dc_solver_; // for powerlines - powerlines_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac); + powerlines_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac); // TODO have a function to dispatch that to all type of elements // for trafo trafos_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac); // for loads @@ -672,7 +673,7 @@ void GridModel::compute_results(bool ac){ } void GridModel::reset_results(){ - powerlines_.reset_results(); + powerlines_.reset_results(); // TODO have a function to dispatch that to all type of elements shunts_.reset_results(); trafos_.reset_results(); loads_.reset_results(); @@ -899,7 +900,7 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp, tripletList_Bp.reserve(bus_vn_kv_.size() + 4 * powerlines_.nb() + 4 * trafos_.nb() + shunts_.nb()); tripletList_Bpp.reserve(bus_vn_kv_.size() + 4 * powerlines_.nb() + 4 * trafos_.nb() + shunts_.nb()); // run through the grid and get the parameters to fill them - powerlines_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); + powerlines_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); // TODO have a function to dispatch that to all type of elements shunts_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); trafos_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); loads_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); @@ -913,3 +914,113 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp, Bpp.setFromTriplets(tripletList_Bpp.begin(), tripletList_Bpp.end()); Bpp.makeCompressed(); } + +// returns only the gen_id with the highest p that is connected to this bus ! +// returns bus_id, gen_bus_id +std::tuple GridModel::assign_slack_to_most_connected(){ + auto res = std::tuple(-1, -1); + int res_bus_id = -1; + int res_gen_id = -1; + int max_line = -1; + const auto nb_busbars = bus_status_.size(); + std::vector gen_p_per_bus(nb_busbars, 0.); + std::vector nb_line_end_per_bus(nb_busbars, 0); + + // computes the total amount of power produce at each nodes + powerlines_.gen_p_per_bus(gen_p_per_bus); // TODO have a function to dispatch that to all type of elements + shunts_.gen_p_per_bus(gen_p_per_bus); + trafos_.gen_p_per_bus(gen_p_per_bus); + loads_.gen_p_per_bus(gen_p_per_bus); + sgens_.gen_p_per_bus(gen_p_per_bus); + storages_.gen_p_per_bus(gen_p_per_bus); + generators_.gen_p_per_bus(gen_p_per_bus); + dc_lines_.gen_p_per_bus(gen_p_per_bus); + + // computes the total number of "neighbors" (extremity of connected powerlines and trafo, not real neighbors) + powerlines_.nb_line_end(nb_line_end_per_bus); // TODO have a function to dispatch that to all type of elements + shunts_.nb_line_end(nb_line_end_per_bus); + trafos_.nb_line_end(nb_line_end_per_bus); + loads_.nb_line_end(nb_line_end_per_bus); + sgens_.nb_line_end(nb_line_end_per_bus); + storages_.nb_line_end(nb_line_end_per_bus); + generators_.nb_line_end(nb_line_end_per_bus); + dc_lines_.nb_line_end(nb_line_end_per_bus); + + // now find the most connected buses + for(unsigned int bus_id = 0; bus_id < nb_busbars; ++bus_id) + { + const auto & nb_lines_this = nb_line_end_per_bus[bus_id]; + if((nb_lines_this > max_line) && (gen_p_per_bus[bus_id] > 0.)){ + res_bus_id = bus_id; + max_line = nb_lines_this; + } + } + // TODO DEBUG MODE + if(res_bus_id == -1) throw std::runtime_error("GridModel::assign_slack_to_most_connected: impossible to find anything connected to a node."); + std::get<0>(res) = res_bus_id; + + // and reset the slack bus + generators_.remove_all_slackbus(); + res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus); + std::get<1>(res) = res_gen_id; + slack_bus_id_ = std::vector(); + slack_weights_ = RealVect(); + return res; +} + +// TODO DC LINE: one side might be in the connected comp and not the other ! +void GridModel::consider_only_main_component(){ + const auto & slack_buses_id = generators_.get_slack_bus_id(); + + // TODO DEBUG MODE + if(slack_buses_id.size() == 0) throw std::runtime_error("GridModel::consider_only_main_component: no slack is defined on your grid. This function cannot be used."); + + // build the graph + const auto nb_busbars = bus_status_.size(); + std::vector > tripletList; + tripletList.reserve(2 * powerlines_.nb() + 2 * trafos_.nb()); + powerlines_.get_graph(tripletList); // TODO have a function to dispatch that to all type of elements + shunts_.get_graph(tripletList); + trafos_.get_graph(tripletList); + loads_.get_graph(tripletList); + sgens_.get_graph(tripletList); + storages_.get_graph(tripletList); + generators_.get_graph(tripletList); + dc_lines_.get_graph(tripletList); + Eigen::SparseMatrix graph = Eigen::SparseMatrix(nb_busbars, nb_busbars); + graph.setFromTriplets(tripletList.begin(), tripletList.end()); + graph.makeCompressed(); + + // find the connected buses + // TODO copy paste from SecurityAnalysis + std::queue neighborhood; + for(const auto & el : slack_buses_id) neighborhood.push(el); + std::vector visited(nb_busbars, false); + while (true) + { + const Eigen::Index col_id = neighborhood.front(); + visited[col_id] = true; + for (Eigen::SparseMatrix::InnerIterator it(graph, col_id); it; ++it) + { + // add in the queue all my neighbor (if the coefficient is big enough) + if(!visited[it.row()] ){ // && abs(it.value()) > 1e-8 + neighborhood.push(it.row()); + } + } + if(neighborhood.empty()) break; + neighborhood.pop(); + } + + // disconnected elements not in main component + powerlines_.disconnect_if_not_in_main_component(visited); + shunts_.disconnect_if_not_in_main_component(visited); + trafos_.disconnect_if_not_in_main_component(visited); + loads_.disconnect_if_not_in_main_component(visited); + sgens_.disconnect_if_not_in_main_component(visited); + storages_.disconnect_if_not_in_main_component(visited); + generators_.disconnect_if_not_in_main_component(visited); + dc_lines_.disconnect_if_not_in_main_component(visited); + + // and finally deal with the buses + init_bus_status(); +} \ No newline at end of file diff --git a/src/GridModel.h b/src/GridModel.h index 0c129bc4..1b450735 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -108,6 +108,8 @@ class GridModel : public DataGeneric const DataTrafo & get_trafos_as_data() const {return trafos_;} const DataDCLine & get_dclines_as_data() const {return dc_lines_;} Eigen::Ref get_bus_vn_kv() const {return bus_vn_kv_;} + std::tuple assign_slack_to_most_connected(); + void consider_only_main_component(); // solver "control" void change_solver(const SolverType & type){ @@ -716,7 +718,6 @@ class GridModel : public DataGeneric DataDCLine dc_lines_; // 8. slack bus - // TODO multiple slack bus std::vector slack_bus_id_; Eigen::VectorXi slack_bus_id_ac_solver_; Eigen::VectorXi slack_bus_id_dc_solver_; diff --git a/src/main.cpp b/src/main.cpp index 44325792..d0918d08 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -659,8 +659,9 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("turnedoff_pv", &GridModel::turnedoff_pv, "Turned off (or generators with p = 0) generators will be pv buses, they will maintain voltage (default)") .def("get_turnedoff_gen_pv", &GridModel::get_turnedoff_gen_pv, "TODO") .def("update_slack_weights", &GridModel::update_slack_weights, "TODO") + .def("assign_slack_to_most_connected", &GridModel::assign_slack_to_most_connected, "TODO") + .def("consider_only_main_component", &GridModel::consider_only_main_component, "TODO and TODO DC LINE: one side might be in the connected comp and not the other !") - .def("deactivate_bus", &GridModel::deactivate_bus, DocGridModel::_internal_do_not_use.c_str()) .def("deactivate_bus", &GridModel::deactivate_bus, DocGridModel::_internal_do_not_use.c_str()) .def("reactivate_bus", &GridModel::reactivate_bus, DocGridModel::_internal_do_not_use.c_str()) From 6c73b5cc38cb98f2b181af915501a663d584307b Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 9 Nov 2023 17:05:21 +0100 Subject: [PATCH 21/66] adding names in the gridmodel --- CHANGELOG.rst | 6 ++--- lightsim2grid/gridmodel/from_pypowsybl.py | 20 +++++++++++---- src/DataDCLine.cpp | 14 ++++++----- src/DataDCLine.h | 6 +++++ src/DataGen.cpp | 24 +++++++++--------- src/DataGen.h | 6 +++++ src/DataGeneric.h | 8 +++++- src/DataLine.cpp | 18 +++++++------- src/DataLine.h | 6 +++++ src/DataLoad.cpp | 12 ++++----- src/DataLoad.h | 6 +++++ src/DataSGen.cpp | 21 ++++++++-------- src/DataSGen.h | 6 +++++ src/DataShunt.cpp | 12 ++++----- src/DataShunt.h | 6 +++++ src/DataTrafo.cpp | 21 ++++++++-------- src/DataTrafo.h | 6 +++++ src/GridModel.h | 25 +++++++++++++++++++ src/help_fun_msg.cpp | 30 ++++++++++++++++++++++- src/help_fun_msg.h | 1 + src/main.cpp | 17 +++++++++++++ 21 files changed, 202 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b812f78f..f3cda352 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Change Log -------- - [refacto] have a structure in cpp for the buses - [refacto] have the id_grid_to_solver and id_solver_to_grid etc. directly in the solver and NOT in the gridmodel. +- [refacto] put some method in the DataGeneric as well as some attribute (_status for example) - support 3w trafo (as modeled in pandapower) - improve speed by not performing internal checks (keep check for boundaries and all for python API instead) [see `TODO DEBUG MODE` in c++ code] @@ -13,8 +14,6 @@ Change Log - a mode to do both `Computer` and `SecurityAnalysisCPP` - use the "multi slack hack" (see issue #50) for SecurityAnalysis or Computer for example - code `helm` powerflow method -- possibility to read CGMES files -- possibility to read XIIDM files - interface with gridpack (to enforce q limits for example) - maybe have a look at suitesparse "sliplu" tools ? - easier building (get rid of the "make" part) @@ -30,8 +29,9 @@ Change Log these cases. - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. +- [ADDED] possibility to set / retrieve the names of each elements of the grid. - [IMPROVED] now performing the new grid2op `create_test_suite` -- [IMPROVED] now lightsim2grid properly throw BackendError +- [IMPROVED] now lightsim2grid properly throw `BackendError` [0.7.5] 2023-10-05 -------------------- diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 3e1b7033..39d2a2df 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -26,8 +26,8 @@ def _aux_get_bus(bus_df, df, conn_key="connected", bus_key="bus_id"): tmp_bus_id[mask_disco] = df.iloc[first_el_co][bus_key] # assign a "random" bus to disco element bus_id = bus_df.loc[tmp_bus_id.values]["bus_id"].values # deactivate the element not on the main component - wrong_component = bus_df.loc[tmp_bus_id.values]["connected_component"].values != 0 - mask_disco[wrong_component] = True + # wrong_component = bus_df.loc[tmp_bus_id.values]["connected_component"].values != 0 + # mask_disco[wrong_component] = True # assign bus -1 to disconnected elements bus_id[mask_disco] = -1 return bus_id , mask_disco.values @@ -38,8 +38,11 @@ def init(net : pypo.network, slack_bus_id: int = None, sn_mva = 100., sort_index=True, - f_hz = 50.): + f_hz = 50., + only_main_component=True): model = GridModel() + # model.set_f_hz(f_hz) + # for substation # network.get_voltage_levels()["substation_id"] # network.get_substations() @@ -84,6 +87,7 @@ def init(net : pypo.network, for gen_id, is_disco in enumerate(gen_disco): if is_disco: model.deactivate_gen(gen_id) + model.set_gen_names(df_gen.index) # for loads if sort_index: @@ -98,6 +102,7 @@ def init(net : pypo.network, for load_id, is_disco in enumerate(load_disco): if is_disco: model.deactivate_load(load_id) + model.set_load_names(df_load.index) # for lines if sort_index: @@ -141,6 +146,7 @@ def init(net : pypo.network, for line_id, (is_or_disc, is_ex_disc) in enumerate(zip(lor_disco, lex_disco)): if is_or_disc or is_ex_disc: model.deactivate_powerline(line_id) + model.set_line_names(df_line.index) # for trafo if sort_index: @@ -176,6 +182,7 @@ def init(net : pypo.network, for t_id, (is_or_disc, is_ex_disc) in enumerate(zip(tor_disco, tex_disco)): if is_or_disc or is_ex_disc: model.deactivate_trafo(t_id) + model.set_trafo_names(df_trafo.index) # for shunt if sort_index: @@ -192,6 +199,7 @@ def init(net : pypo.network, for shunt_id, disco in enumerate(sh_disco): if disco: model.deactivate_shunt(shunt_id) + model.set_shunt_names(df_trafo.index) # for hvdc (TODO not tested yet) df_dc = net.get_hvdc_lines().sort_index() @@ -218,6 +226,7 @@ def init(net : pypo.network, for hvdc_id, (is_or_disc, is_ex_disc) in enumerate(zip(hvdc_from_disco, hvdc_to_disco)): if is_or_disc or is_ex_disc: model.deactivate_hvdc(hvdc_id) + model.set_dcline_names(df_sations.index) # storage units (TODO not tested yet) if sort_index: @@ -232,6 +241,7 @@ def init(net : pypo.network, for batt_id, disco in enumerate(batt_disco): if disco: model.deactivate_storage(batt_id) + model.set_storage_names(df_batt.index) # TODO dist slack if gen_slack_id is None and slack_bus_id is None: @@ -262,6 +272,6 @@ def init(net : pypo.network, # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") # and now deactivate all elements and nodes not in the main component - # TODO DC LINE: one side might be in the connected comp and not the other ! - model.consider_only_main_component() + if only_main_component: + model.consider_only_main_component() return model diff --git a/src/DataDCLine.cpp b/src/DataDCLine.cpp index 45694567..977f9613 100644 --- a/src/DataDCLine.cpp +++ b/src/DataDCLine.cpp @@ -15,7 +15,8 @@ DataDCLine::StateRes DataDCLine::get_state() const std::vector loss_percent(loss_percent_.begin(), loss_percent_.end()); std::vector loss_mw(loss_mw_.begin(), loss_mw_.end()); std::vector status = status_; - DataDCLine::StateRes res(from_gen_.get_state(), + DataDCLine::StateRes res(names_, + from_gen_.get_state(), to_gen_.get_state(), loss_percent, loss_mw, @@ -25,11 +26,12 @@ DataDCLine::StateRes DataDCLine::get_state() const void DataDCLine::set_state(DataDCLine::StateRes & my_state){ reset_results(); - from_gen_.set_state(std::get<0>(my_state)); - to_gen_.set_state(std::get<1>(my_state)); - std::vector & loss_percent = std::get<2>(my_state); - std::vector & loss_mw = std::get<3>(my_state); - std::vector & status = std::get<4>(my_state); + names_ = std::get<0>(my_state); + from_gen_.set_state(std::get<1>(my_state)); + to_gen_.set_state(std::get<2>(my_state)); + std::vector & loss_percent = std::get<3>(my_state); + std::vector & loss_mw = std::get<4>(my_state); + std::vector & status = std::get<5>(my_state); status_ = status; loss_percent_ = RealVect::Map(&loss_percent[0], loss_percent.size()); loss_mw_ = RealVect::Map(&loss_mw[0], loss_percent.size()); diff --git a/src/DataDCLine.h b/src/DataDCLine.h index 042791ce..fbcba63e 100644 --- a/src/DataDCLine.h +++ b/src/DataDCLine.h @@ -30,6 +30,7 @@ class DataDCLine : public DataGeneric public: // members int id; // id of the dcline + std::string name; bool connected; int bus_or_id; int bus_ex_id; @@ -53,6 +54,7 @@ class DataDCLine : public DataGeneric DCLineInfo(const DataDCLine & r_data_dcline, int my_id): id(-1), + name(""), connected(false), bus_or_id(-1), bus_ex_id(-1), @@ -76,6 +78,9 @@ class DataDCLine : public DataGeneric if((my_id >= 0) & (my_id < r_data_dcline.nb())) { id = my_id; + if(r_data_dcline.names_.size()){ + name = r_data_dcline.names_[my_id]; + } loss_pct = r_data_dcline.loss_percent_(my_id); loss_mw = r_data_dcline.loss_mw_(my_id); @@ -108,6 +113,7 @@ class DataDCLine : public DataGeneric public: typedef std::tuple< + std::vector, DataGen::StateRes, DataGen::StateRes, std::vector, // loss_percent diff --git a/src/DataGen.cpp b/src/DataGen.cpp index 3d064459..b110ff5c 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -58,25 +58,25 @@ DataGen::StateRes DataGen::get_state() const std::vector status = status_; std::vector slack_bus = gen_slackbus_; std::vector slack_weight = gen_slack_weight_; - DataGen::StateRes res(turnedoff_gen_pv_, p_mw, vm_pu, min_q, max_q, bus_id, status, slack_bus, slack_weight); + DataGen::StateRes res(names_, turnedoff_gen_pv_, p_mw, vm_pu, min_q, max_q, bus_id, status, slack_bus, slack_weight); return res; } -void DataGen::set_state(DataGen::StateRes & my_state ) +void DataGen::set_state(DataGen::StateRes & my_state) { reset_results(); - - turnedoff_gen_pv_ = std::get<0>(my_state); + names_ = std::get<0>(my_state); + turnedoff_gen_pv_ = std::get<1>(my_state); // the generators themelves - std::vector & p_mw = std::get<1>(my_state); - std::vector & vm_pu = std::get<2>(my_state); - std::vector & min_q = std::get<3>(my_state); - std::vector & max_q = std::get<4>(my_state); - std::vector & bus_id = std::get<5>(my_state); - std::vector & status = std::get<6>(my_state); - std::vector & slack_bus = std::get<7>(my_state); - std::vector & slack_weight = std::get<8>(my_state); + std::vector & p_mw = std::get<2>(my_state); + std::vector & vm_pu = std::get<3>(my_state); + std::vector & min_q = std::get<4>(my_state); + std::vector & max_q = std::get<5>(my_state); + std::vector & bus_id = std::get<6>(my_state); + std::vector & status = std::get<7>(my_state); + std::vector & slack_bus = std::get<8>(my_state); + std::vector & slack_weight = std::get<9>(my_state); // TODO check sizes // input data diff --git a/src/DataGen.h b/src/DataGen.h index a4c41cd0..d125bf2e 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -39,6 +39,7 @@ class DataGen: public DataGeneric // members // TODO add some const here (value should not be changed !) !!! int id; // id of the generator + std::string name; bool connected; int bus_id; bool is_slack; @@ -56,6 +57,7 @@ class DataGen: public DataGeneric GenInfo(const DataGen & r_data_gen, int my_id): id(-1), + name(""), connected(false), bus_id(-1), is_slack(false), @@ -73,6 +75,9 @@ class DataGen: public DataGeneric if((my_id >= 0) & (my_id < r_data_gen.nb())) { id = my_id; + if(r_data_gen.names_.size()){ + name = r_data_gen.names_[my_id]; + } connected = r_data_gen.status_[my_id]; bus_id = r_data_gen.bus_id_[my_id]; is_slack = r_data_gen.gen_slackbus_[my_id]; @@ -101,6 +106,7 @@ class DataGen: public DataGeneric public: typedef std::tuple< + std::vector, bool, std::vector, // p_mw std::vector, // vm_pu_ diff --git a/src/DataGeneric.h b/src/DataGeneric.h index 85e8e1e2..e3ccd6bd 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -98,9 +98,15 @@ class DataGeneric : public BaseConstants virtual void get_graph(std::vector > & res) const {}; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) {}; + void set_names(const std::vector & names){ + names_ = names; + } + /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/ virtual ~DataGeneric() {}; - + protected: + std::vector names_; + protected: /** activation / deactivation of elements diff --git a/src/DataLine.cpp b/src/DataLine.cpp index 541df7d4..2f63c5b8 100644 --- a/src/DataLine.cpp +++ b/src/DataLine.cpp @@ -77,20 +77,20 @@ DataLine::StateRes DataLine::get_state() const std::vector branch_from_id(bus_or_id_.begin(), bus_or_id_.end()); std::vector branch_to_id(bus_ex_id_.begin(), bus_ex_id_.end()); std::vector status = status_; - DataLine::StateRes res(branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); + DataLine::StateRes res(names_, branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); return res; } void DataLine::set_state(DataLine::StateRes & my_state) { reset_results(); - - std::vector & branch_r = std::get<0>(my_state); - std::vector & branch_x = std::get<1>(my_state); - std::vector & branch_h_or = std::get<2>(my_state); - std::vector & branch_h_ex = std::get<3>(my_state); - std::vector & branch_from_id = std::get<4>(my_state); - std::vector & branch_to_id = std::get<5>(my_state); - std::vector & status = std::get<6>(my_state); + names_ = std::get<0>(my_state); + std::vector & branch_r = std::get<1>(my_state); + std::vector & branch_x = std::get<2>(my_state); + std::vector & branch_h_or = std::get<3>(my_state); + std::vector & branch_h_ex = std::get<4>(my_state); + std::vector & branch_from_id = std::get<5>(my_state); + std::vector & branch_to_id = std::get<6>(my_state); + std::vector & status = std::get<7>(my_state); // TODO check sizes // now assign the values diff --git a/src/DataLine.h b/src/DataLine.h index 85ecd4fb..08dd20d7 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -38,6 +38,7 @@ class DataLine : public DataGeneric public: // members int id; // id of the line + std::string name; bool connected; int bus_or_id; int bus_ex_id; @@ -61,6 +62,7 @@ class DataLine : public DataGeneric LineInfo(const DataLine & r_data_line, int my_id): id(my_id), + name(""), connected(false), bus_or_id(-1), bus_ex_id(-1), @@ -84,6 +86,9 @@ class DataLine : public DataGeneric if((my_id >= 0) & (my_id < r_data_line.nb())) { id = my_id; + if(r_data_line.names_.size()){ + name = r_data_line.names_[my_id]; + } connected = r_data_line.status_[my_id]; bus_or_id = r_data_line.bus_or_id_.coeff(my_id); bus_ex_id = r_data_line.bus_ex_id_.coeff(my_id); @@ -117,6 +122,7 @@ class DataLine : public DataGeneric public: typedef std::tuple< + std::vector, std::vector, // branch_r std::vector, // branch_x std::vector, // branch_h diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp index 041d53e2..e29249bc 100644 --- a/src/DataLoad.cpp +++ b/src/DataLoad.cpp @@ -26,17 +26,17 @@ DataLoad::StateRes DataLoad::get_state() const std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataLoad::StateRes res(p_mw, q_mvar, bus_id, status); + DataLoad::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } void DataLoad::set_state(DataLoad::StateRes & my_state ) { reset_results(); - - std::vector & p_mw = std::get<0>(my_state); - std::vector & q_mvar = std::get<1>(my_state); - std::vector & bus_id = std::get<2>(my_state); - std::vector & status = std::get<3>(my_state); + names_ = std::get<0>(my_state); + std::vector & p_mw = std::get<1>(my_state); + std::vector & q_mvar = std::get<2>(my_state); + std::vector & bus_id = std::get<3>(my_state); + std::vector & status = std::get<4>(my_state); // TODO check sizes // input data diff --git a/src/DataLoad.h b/src/DataLoad.h index aebd6b54..f4d76896 100644 --- a/src/DataLoad.h +++ b/src/DataLoad.h @@ -44,6 +44,7 @@ class DataLoad : public DataGeneric // members // TODO add some const here (value should not be changed !) !!! int id; // id of the generator + std::string name; bool connected; int bus_id; @@ -57,6 +58,7 @@ class DataLoad : public DataGeneric LoadInfo(const DataLoad & r_data_load, int my_id): id(-1), + name(""), connected(false), bus_id(-1), target_p_mw(0.), @@ -70,6 +72,9 @@ class DataLoad : public DataGeneric if((my_id >= 0) & (my_id < r_data_load.nb())) { id = my_id; + if(r_data_load.names_.size()){ + name = r_data_load.names_[my_id]; + } connected = r_data_load.status_[my_id]; bus_id = r_data_load.bus_id_[my_id]; @@ -112,6 +117,7 @@ class DataLoad : public DataGeneric // regular implementation public: typedef std::tuple< + std::vector, std::vector, // p_mw std::vector, // q_mvar std::vector, // bus_id diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp index dc9a2b82..3b460382 100644 --- a/src/DataSGen.cpp +++ b/src/DataSGen.cpp @@ -44,22 +44,23 @@ DataSGen::StateRes DataSGen::get_state() const std::vector q_max(q_max_mvar_.begin(), q_max_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataSGen::StateRes res(p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); + DataSGen::StateRes res(names_, p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); return res; } void DataSGen::set_state(DataSGen::StateRes & my_state ) { reset_results(); - - std::vector & p_mw = std::get<0>(my_state); - std::vector & q_mvar = std::get<1>(my_state); - std::vector & p_min = std::get<2>(my_state); - std::vector & p_max = std::get<3>(my_state); - std::vector & q_min = std::get<4>(my_state); - std::vector & q_max = std::get<5>(my_state); - std::vector & bus_id = std::get<6>(my_state); - std::vector & status = std::get<7>(my_state); + + names_ = std::get<0>(my_state); + std::vector & p_mw = std::get<1>(my_state); + std::vector & q_mvar = std::get<2>(my_state); + std::vector & p_min = std::get<3>(my_state); + std::vector & p_max = std::get<4>(my_state); + std::vector & q_min = std::get<5>(my_state); + std::vector & q_max = std::get<6>(my_state); + std::vector & bus_id = std::get<7>(my_state); + std::vector & status = std::get<8>(my_state); auto size = p_mw.size(); DataGeneric::check_size(p_mw, size, "p_mw"); DataGeneric::check_size(q_mvar, size, "q_mvar"); diff --git a/src/DataSGen.h b/src/DataSGen.h index c2a5c7e7..3d45b932 100644 --- a/src/DataSGen.h +++ b/src/DataSGen.h @@ -42,6 +42,7 @@ class DataSGen: public DataGeneric // members // TODO add some const here (value should not be changed !) !!! int id; // id of the generator + std::string name; bool connected; int bus_id; @@ -61,6 +62,7 @@ class DataSGen: public DataGeneric SGenInfo(const DataSGen & r_data_sgen, int my_id): id(-1), + name(""), connected(false), bus_id(-1), min_q_mvar(0.), @@ -78,6 +80,9 @@ class DataSGen: public DataGeneric if((my_id >= 0) & (my_id < r_data_sgen.nb())) { id = my_id; + if(r_data_sgen.names_.size()){ + name = r_data_sgen.names_[my_id]; + } connected = r_data_sgen.status_[my_id]; bus_id = r_data_sgen.bus_id_[my_id]; @@ -124,6 +129,7 @@ class DataSGen: public DataGeneric public: typedef std::tuple< + std::vector, std::vector, // p_mw std::vector, // q_mvar std::vector, // p_min diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp index 42e057f5..42a03f8a 100644 --- a/src/DataShunt.cpp +++ b/src/DataShunt.cpp @@ -25,18 +25,18 @@ DataShunt::StateRes DataShunt::get_state() const std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataShunt::StateRes res(p_mw, q_mvar, bus_id, status); + DataShunt::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } void DataShunt::set_state(DataShunt::StateRes & my_state ) { reset_results(); - - std::vector & p_mw = std::get<0>(my_state); - std::vector & q_mvar = std::get<1>(my_state); - std::vector & bus_id = std::get<2>(my_state); - std::vector & status = std::get<3>(my_state); + names_ = std::get<0>(my_state); + std::vector & p_mw = std::get<1>(my_state); + std::vector & q_mvar = std::get<2>(my_state); + std::vector & bus_id = std::get<3>(my_state); + std::vector & status = std::get<4>(my_state); // TODO check sizes // input data diff --git a/src/DataShunt.h b/src/DataShunt.h index 31e030c3..1096a574 100644 --- a/src/DataShunt.h +++ b/src/DataShunt.h @@ -38,6 +38,7 @@ class DataShunt : public DataGeneric // members // TODO add some const here (value should not be changed !) !!! int id; // id of the generator + std::string name; bool connected; int bus_id; @@ -51,6 +52,7 @@ class DataShunt : public DataGeneric ShuntInfo(const DataShunt & r_data_shunt, int my_id): id(-1), + name(""), connected(false), bus_id(-1), target_p_mw(0.), @@ -64,6 +66,9 @@ class DataShunt : public DataGeneric if((my_id >= 0) & (my_id < r_data_shunt.nb())) { id = my_id; + if(r_data_shunt.names_.size()){ + name = r_data_shunt.names_[my_id]; + } connected = r_data_shunt.status_[my_id]; bus_id = r_data_shunt.bus_id_[my_id]; @@ -105,6 +110,7 @@ class DataShunt : public DataGeneric public: typedef std::tuple< + std::vector, std::vector, // p_mw std::vector, // q_mvar std::vector, // bus_id diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp index 568f0e9f..22cdce10 100644 --- a/src/DataTrafo.cpp +++ b/src/DataTrafo.cpp @@ -65,22 +65,23 @@ DataTrafo::StateRes DataTrafo::get_state() const std::vector ratio(ratio_.begin(), ratio_.end()); std::vector shift(shift_.begin(), shift_.end()); std::vector is_tap_hv_side = is_tap_hv_side_; - DataTrafo::StateRes res(branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); + DataTrafo::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); return res; } void DataTrafo::set_state(DataTrafo::StateRes & my_state) { reset_results(); - std::vector & branch_r = std::get<0>(my_state); - std::vector & branch_x = std::get<1>(my_state); - std::vector & branch_h = std::get<2>(my_state); - std::vector & bus_hv_id = std::get<3>(my_state); - std::vector & bus_lv_id = std::get<4>(my_state); - std::vector & status = std::get<5>(my_state); - std::vector & ratio = std::get<6>(my_state); - std::vector & is_tap_hv_side = std::get<7>(my_state); - std::vector & shift = std::get<8>(my_state); + names_ = std::get<0>(my_state); + std::vector & branch_r = std::get<1>(my_state); + std::vector & branch_x = std::get<2>(my_state); + std::vector & branch_h = std::get<3>(my_state); + std::vector & bus_hv_id = std::get<4>(my_state); + std::vector & bus_lv_id = std::get<5>(my_state); + std::vector & status = std::get<6>(my_state); + std::vector & ratio = std::get<7>(my_state); + std::vector & is_tap_hv_side = std::get<8>(my_state); + std::vector & shift = std::get<9>(my_state); auto size = branch_r.size(); DataGeneric::check_size(branch_r, size, "branch_r"); diff --git a/src/DataTrafo.h b/src/DataTrafo.h index f162f60e..5afc2400 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -38,6 +38,7 @@ class DataTrafo : public DataGeneric public: // members int id; // id of the generator + std::string name; bool connected; int bus_hv_id; int bus_lv_id; @@ -62,6 +63,7 @@ class DataTrafo : public DataGeneric TrafoInfo(const DataTrafo & r_data_trafo, int my_id): id(-1), + name(""), connected(false), bus_hv_id(-1), bus_lv_id(-1), @@ -86,6 +88,9 @@ class DataTrafo : public DataGeneric if((my_id >= 0) & (my_id < r_data_trafo.nb())) { id = my_id; + if(r_data_trafo.names_.size()){ + name = r_data_trafo.names_[my_id]; + } connected = r_data_trafo.status_[my_id]; bus_hv_id = r_data_trafo.bus_hv_id_.coeff(my_id); bus_lv_id = r_data_trafo.bus_lv_id_.coeff(my_id); @@ -120,6 +125,7 @@ class DataTrafo : public DataGeneric public: typedef std::tuple< + std::vector, std::vector, // branch_r std::vector, // branch_x std::vector, // branch_h diff --git a/src/GridModel.h b/src/GridModel.h index 1b450735..28740fda 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -286,6 +286,31 @@ class GridModel : public DataGeneric const DataShunt & get_shunts() const {return shunts_;} const std::vector & get_bus_status() const {return bus_status_;} + void set_line_names(const std::vector & names){ + powerlines_.set_names(names); + } + void set_dcline_names(const std::vector & names){ + dc_lines_.set_names(names); + } + void set_trafo_names(const std::vector & names){ + trafos_.set_names(names); + } + void set_gen_names(const std::vector & names){ + generators_.set_names(names); + } + void set_load_names(const std::vector & names){ + loads_.set_names(names); + } + void set_storage_names(const std::vector & names){ + storages_.set_names(names); + } + void set_sgen_names(const std::vector & names){ + sgens_.set_names(names); + } + void set_shunt_names(const std::vector & names){ + shunts_.set_names(names); + } + //deactivate a powerline (disconnect it) void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); } void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, topo_changed_); } diff --git a/src/help_fun_msg.cpp b/src/help_fun_msg.cpp index 6a3fe403..a33c1b67 100644 --- a/src/help_fun_msg.cpp +++ b/src/help_fun_msg.cpp @@ -633,7 +633,7 @@ const std::string DocSolver::get_computation_time = R"mydelimiter( )mydelimiter"; const std::string DocIterator::id = R"mydelimiter( - Get the ideas of the element. Ids are integer from 0 to n-1 (if `n` denotes the number of such elements on the grid.) + Get the id of the element. Ids are integer from 0 to n-1 (if `n` denotes the number of such elements on the grid.) Examples -------- @@ -657,6 +657,34 @@ const std::string DocIterator::id = R"mydelimiter( )mydelimiter"; +const std::string DocIterator::name = R"mydelimiter( + Get the name of the element. Names are string that should be unique. But if you really want things unique, use the `id` + + .. warning:: + Names are optional and might not be set when reading the grid. + + Examples + -------- + We give the example only for generators, but it works similarly for every other types of objects + in a :class:`lightsim2grid.gridmodel.GridModel`. + + This gives something like: + + .. code-block:: python + + import grid2op + from lightsim2grid import LightSimBackend + + env_name = ... # eg. "l2rpn_case14_test" + env = grid2op.make(env_name, backend=LightSimBackend()) + + grid_model = env.backend._grid + + first_gen = grid_model.get_generators()[0] # or get_loads for loads, etc. + first_gen.name + +)mydelimiter"; + const std::string DocIterator::connected = R"mydelimiter( Get the status (True = connected, False = disconnected) of each element of a :class:`lightsim2grid.gridmodel.GridModel` diff --git a/src/help_fun_msg.h b/src/help_fun_msg.h index 17fa7193..3f503b13 100644 --- a/src/help_fun_msg.h +++ b/src/help_fun_msg.h @@ -69,6 +69,7 @@ struct DocIterator // generic functions static const std::string only_avail_res; static const std::string id; + static const std::string name; static const std::string connected; static const std::string bus_id; static const std::string target_p_mw; diff --git a/src/main.cpp b/src/main.cpp index d0918d08..88221a88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -405,6 +405,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "GenInfo", DocIterator::GenInfo.c_str()) .def_readonly("id", &DataGen::GenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataGen::GenInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataGen::GenInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_id", &DataGen::GenInfo::bus_id, DocIterator::bus_id.c_str()) .def_readonly("is_slack", &DataGen::GenInfo::is_slack, DocIterator::is_slack.c_str()) @@ -429,6 +430,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "SGenInfo", DocIterator::SGenInfo.c_str()) .def_readonly("id", &DataSGen::SGenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataSGen::SGenInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataSGen::SGenInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_id", &DataSGen::SGenInfo::bus_id, DocIterator::bus_id.c_str()) .def_readonly("min_q_mvar", &DataSGen::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) @@ -453,6 +455,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "LoadInfo", DocIterator::LoadInfo.c_str()) .def_readonly("id", &DataLoad::LoadInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataLoad::LoadInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataLoad::LoadInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_id", &DataLoad::LoadInfo::bus_id, DocIterator::bus_id.c_str()) .def_readonly("target_p_mw", &DataLoad::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str()) @@ -473,6 +476,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "ShuntInfo", DocIterator::ShuntInfo.c_str()) .def_readonly("id", &DataShunt::ShuntInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataShunt::ShuntInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataShunt::ShuntInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_id", &DataShunt::ShuntInfo::bus_id, DocIterator::bus_id.c_str()) .def_readonly("target_p_mw", &DataShunt::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str()) @@ -493,6 +497,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "TrafoInfo", DocIterator::TrafoInfo.c_str()) .def_readonly("id", &DataTrafo::TrafoInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataTrafo::TrafoInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataTrafo::TrafoInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_hv_id", &DataTrafo::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str()) .def_readonly("bus_lv_id", &DataTrafo::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str()) @@ -524,6 +529,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "LineInfo", DocIterator::LineInfo.c_str()) .def_readonly("id", &DataLine::LineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataLine::LineInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataLine::LineInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_or_id", &DataLine::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) .def_readonly("bus_ex_id", &DataLine::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) @@ -554,6 +560,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) py::class_(m, "DCLineInfo", DocIterator::DCLineInfo.c_str()) .def_readonly("id", &DataDCLine::DCLineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DataDCLine::DCLineInfo::name, DocIterator::name.c_str()) .def_readonly("connected", &DataDCLine::DCLineInfo::connected, DocIterator::connected.c_str()) .def_readonly("bus_or_id", &DataDCLine::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) .def_readonly("bus_ex_id", &DataDCLine::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) @@ -662,6 +669,16 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("assign_slack_to_most_connected", &GridModel::assign_slack_to_most_connected, "TODO") .def("consider_only_main_component", &GridModel::consider_only_main_component, "TODO and TODO DC LINE: one side might be in the connected comp and not the other !") + // names + .def("set_line_names", &GridModel::set_line_names, "TODO") + .def("set_dcline_names", &GridModel::set_dcline_names, "TODO") + .def("set_trafo_names", &GridModel::set_trafo_names, "TODO") + .def("set_gen_names", &GridModel::set_gen_names, "TODO") + .def("set_load_names", &GridModel::set_load_names, "TODO") + .def("set_storage_names", &GridModel::set_storage_names, "TODO") + .def("set_sgen_names", &GridModel::set_sgen_names, "TODO") + .def("set_shunt_names", &GridModel::set_shunt_names, "TODO") + .def("deactivate_bus", &GridModel::deactivate_bus, DocGridModel::_internal_do_not_use.c_str()) .def("reactivate_bus", &GridModel::reactivate_bus, DocGridModel::_internal_do_not_use.c_str()) From 800573b78e6a46b80b5c0851e6303144e2ee0941 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 9 Nov 2023 17:49:59 +0100 Subject: [PATCH 22/66] adding possibility have non pv generators --- CHANGELOG.rst | 1 + lightsim2grid/gridmodel/from_pypowsybl.py | 14 ++-- src/DataGen.cpp | 78 ++++++++++++++++++----- src/DataGen.h | 20 ++++++ src/GridModel.h | 10 +++ src/main.cpp | 3 + 6 files changed, 105 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3cda352..4629d1db 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,6 +30,7 @@ Change Log - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. +- [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side) - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 39d2a2df..375fa9dc 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -78,12 +78,14 @@ def init(net : pypo.network, min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 0.5 + 1. max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 0.5 - 1. gen_bus, gen_disco = _aux_get_bus(bus_df, df_gen) - model.init_generators(df_gen["target_p"].values, - df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values, - min_q, - max_q, - gen_bus - ) + model.init_generators_full(df_gen["target_p"].values, + df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values, + df_gen["target_q"].values, + df_gen["voltage_regulator_on"].values, + min_q, + max_q, + gen_bus + ) for gen_id, is_disco in enumerate(gen_disco): if is_disco: model.deactivate_gen(gen_id) diff --git a/src/DataGen.cpp b/src/DataGen.cpp index b110ff5c..db874729 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -45,6 +45,22 @@ void DataGen::init(const RealVect & generators_p, gen_slackbus_ = std::vector(generators_p.size(), false); gen_slack_weight_ = std::vector(generators_p.size(), 0.); turnedoff_gen_pv_ = true; + voltage_regulator_on_ = std::vector(generators_p.size(), true); + q_mvar_ = RealVect::Zero(generators_p.size()); +} + +void DataGen::init_full(const RealVect & generators_p, + const RealVect & generators_v, + const RealVect & generators_q, + const std::vector & voltage_regulator_on, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id + ) +{ + init(generators_p, generators_v, generators_min_q, generators_max_q, generators_bus_id); + voltage_regulator_on_ = voltage_regulator_on; + q_mvar_ = generators_q; } @@ -52,13 +68,17 @@ DataGen::StateRes DataGen::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector vm_pu(vm_pu_.begin(), vm_pu_.end()); + std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); std::vector min_q(min_q_.begin(), min_q_.end()); std::vector max_q(max_q_.begin(), max_q_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; std::vector slack_bus = gen_slackbus_; + std::vector voltage_regulator_on = voltage_regulator_on_; std::vector slack_weight = gen_slack_weight_; - DataGen::StateRes res(names_, turnedoff_gen_pv_, p_mw, vm_pu, min_q, max_q, bus_id, status, slack_bus, slack_weight); + DataGen::StateRes res(names_, turnedoff_gen_pv_, voltage_regulator_on, + p_mw, vm_pu, q_mvar, + min_q, max_q, bus_id, status, slack_bus, slack_weight); return res; } @@ -69,19 +89,23 @@ void DataGen::set_state(DataGen::StateRes & my_state) turnedoff_gen_pv_ = std::get<1>(my_state); // the generators themelves - std::vector & p_mw = std::get<2>(my_state); - std::vector & vm_pu = std::get<3>(my_state); - std::vector & min_q = std::get<4>(my_state); - std::vector & max_q = std::get<5>(my_state); - std::vector & bus_id = std::get<6>(my_state); - std::vector & status = std::get<7>(my_state); - std::vector & slack_bus = std::get<8>(my_state); - std::vector & slack_weight = std::get<9>(my_state); + std::vector & voltage_regulator_on = std::get<2>(my_state); + std::vector & p_mw = std::get<3>(my_state); + std::vector & vm_pu = std::get<4>(my_state); + std::vector & q_mvar = std::get<5>(my_state); + std::vector & min_q = std::get<6>(my_state); + std::vector & max_q = std::get<7>(my_state); + std::vector & bus_id = std::get<8>(my_state); + std::vector & status = std::get<9>(my_state); + std::vector & slack_bus = std::get<10>(my_state); + std::vector & slack_weight = std::get<11>(my_state); // TODO check sizes // input data + voltage_regulator_on_ = voltage_regulator_on; p_mw_ = RealVect::Map(&p_mw[0], p_mw.size()); vm_pu_ = RealVect::Map(&vm_pu[0], vm_pu.size()); + q_mvar_ = RealVect::Map(&q_mvar[0], q_mvar.size()); min_q_ = RealVect::Map(&min_q[0], min_q.size()); max_q_ = RealVect::Map(&max_q[0], max_q.size()); bus_id_ = Eigen::VectorXi::Map(&bus_id[0], bus_id.size()); @@ -118,7 +142,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; - real_type tmp; + cplx_type tmp; for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ // i don't do anything if the load is disconnected if(!status_[gen_id]) continue; @@ -133,7 +157,11 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); } - tmp = p_mw_(gen_id); + tmp = {p_mw_(gen_id), 0.}; + if(!voltage_regulator_on_[gen_id]){ + // gen is pq if voltage regulaton is off + tmp += my_i * q_mvar_(gen_id); + } Sbus.coeffRef(bus_id_solver) += tmp; } } @@ -148,12 +176,11 @@ void DataGen::fillpv(std::vector & bus_pv, for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ // i don't do anything if the generator is disconnected if(!status_[gen_id]) continue; + if (!voltage_regulator_on_[gen_id]) continue; // gen is purposedly not pv + if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv bus_id_me = bus_id_(gen_id); bus_id_solver = id_grid_to_solver[bus_id_me]; - - if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv - if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; @@ -162,7 +189,7 @@ void DataGen::fillpv(std::vector & bus_pv, exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); } - // if(bus_id_solver == slack_bus_id_solver) continue; // slack bus is not PV + if(is_in_vect(bus_id_solver, slack_bus_id_solver)) continue; // slack bus is not PV if(has_bus_been_added[bus_id_solver]) continue; // i already added this bus bus_pv.push_back(bus_id_solver); @@ -199,6 +226,7 @@ void DataGen::get_vm_for_dc(RealVect & Vm){ // i don't do anything if the generator is disconnected if(!status_[gen_id]) continue; + if (!voltage_regulator_on_[gen_id]) continue; // gen is purposedly not pv if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv bus_id_me = bus_id_(gen_id); @@ -232,6 +260,23 @@ void DataGen::change_p(int gen_id, real_type new_p, bool & need_reset) p_mw_(gen_id) = new_p; } +void DataGen::change_q(int gen_id, real_type new_q, bool & need_reset) +{ + bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound + if(!my_status) + { + // TODO DEBUG MODE only this in debug mode + std::ostringstream exc_; + exc_ << "DataGen::change_q: Impossible to change the reactive value of a disconnected generator (check gen. id "; + exc_ << gen_id; + exc_ << ")"; + throw std::runtime_error(exc_.str()); + } + // TODO DEBUG MODE : raise an error if generator is regulating voltage, maybe ? + // this would have not effect + q_mvar_(gen_id) = new_q; +} + void DataGen::change_v(int gen_id, real_type new_v_pu, bool & need_reset) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound @@ -255,6 +300,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c // i don't do anything if the generator is disconnected if(!status_[gen_id]) continue; + if (!voltage_regulator_on_[gen_id]) continue; // gen is purposedly not pv if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv bus_id_me = bus_id_(gen_id); @@ -329,6 +375,7 @@ void DataGen::init_q_vector(int nb_bus, { if(!status_[gen_id]) continue; + if (!voltage_regulator_on_[gen_id]) continue; // gen is purposedly not pv if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv int bus_id = bus_id_(gen_id); @@ -354,6 +401,7 @@ void DataGen::set_q(const RealVect & reactive_mismatch, real_type real_q = 0.; if(!status_[gen_id]) continue; // set at 0 for disconnected generators + if (!voltage_regulator_on_[gen_id]) continue; // gen is purposedly not pv if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue; // in this case turned off generators are not pv int bus_id = bus_id_(gen_id); diff --git a/src/DataGen.h b/src/DataGen.h index d125bf2e..ae72d3d2 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -45,8 +45,10 @@ class DataGen: public DataGeneric bool is_slack; real_type slack_weight; + bool voltage_regulator_on; real_type target_p_mw; real_type target_vm_pu; + real_type target_q_mvar; real_type min_q_mvar; real_type max_q_mvar; bool has_res; @@ -62,8 +64,10 @@ class DataGen: public DataGeneric bus_id(-1), is_slack(false), slack_weight(-1.0), + voltage_regulator_on(false), target_p_mw(0.), target_vm_pu(0.), + target_q_mvar(0.), min_q_mvar(0.), max_q_mvar(0.), has_res(false), @@ -83,8 +87,10 @@ class DataGen: public DataGeneric is_slack = r_data_gen.gen_slackbus_[my_id]; slack_weight = r_data_gen.gen_slack_weight_[my_id]; + voltage_regulator_on = r_data_gen.voltage_regulator_on_[my_id]; target_p_mw = r_data_gen.p_mw_.coeff(my_id); target_vm_pu = r_data_gen.vm_pu_.coeff(my_id); + target_q_mvar = r_data_gen.q_mvar_.coeff(my_id); min_q_mvar = r_data_gen.min_q_.coeff(my_id); max_q_mvar = r_data_gen.max_q_.coeff(my_id); @@ -108,8 +114,10 @@ class DataGen: public DataGeneric typedef std::tuple< std::vector, bool, + std::vector, // voltage_regulator_on std::vector, // p_mw std::vector, // vm_pu_ + std::vector, // q_mvar_ std::vector, // min_q_ std::vector, // max_q_ std::vector, // bus_id @@ -129,6 +137,15 @@ class DataGen: public DataGeneric const Eigen::VectorXi & generators_bus_id ); + void init_full(const RealVect & generators_p, + const RealVect & generators_v, + const RealVect & generators_q, + const std::vector & voltage_regulator_on, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id + ); + int nb() const { return static_cast(p_mw_.size()); } // iterator @@ -220,6 +237,7 @@ class DataGen: public DataGeneric real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} void change_p(int gen_id, real_type new_p, bool & need_reset); + void change_q(int gen_id, real_type new_q, bool & need_reset); void change_v(int gen_id, real_type new_v_pu, bool & need_reset); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; @@ -271,8 +289,10 @@ class DataGen: public DataGeneric // physical properties // input data + std::vector voltage_regulator_on_; RealVect p_mw_; RealVect vm_pu_; + RealVect q_mvar_; RealVect min_q_; RealVect max_q_; Eigen::VectorXi bus_id_; diff --git a/src/GridModel.h b/src/GridModel.h index 28740fda..a4d31b80 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -181,6 +181,16 @@ class GridModel : public DataGeneric const Eigen::VectorXi & generators_bus_id){ generators_.init(generators_p, generators_v, generators_min_q, generators_max_q, generators_bus_id); } + void init_generators_full(const RealVect & generators_p, + const RealVect & generators_v, + const RealVect & generators_q, + const std::vector & voltage_regulator_on, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id){ + generators_.init_full(generators_p, generators_v, generators_q, voltage_regulator_on, + generators_min_q, generators_max_q, generators_bus_id); + } void init_loads(const RealVect & loads_p, const RealVect & loads_q, const Eigen::VectorXi & loads_bus_id){ diff --git a/src/main.cpp b/src/main.cpp index 88221a88..a8f29e29 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -410,8 +410,10 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def_readonly("bus_id", &DataGen::GenInfo::bus_id, DocIterator::bus_id.c_str()) .def_readonly("is_slack", &DataGen::GenInfo::is_slack, DocIterator::is_slack.c_str()) .def_readonly("slack_weight", &DataGen::GenInfo::slack_weight, DocIterator::slack_weight.c_str()) + .def_readonly("voltage_regulator_on", &DataGen::GenInfo::voltage_regulator_on, "TODO") .def_readonly("target_p_mw", &DataGen::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) .def_readonly("target_vm_pu", &DataGen::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str()) + .def_readonly("target_q_mvar", &DataGen::GenInfo::target_q_mvar, "TODO") .def_readonly("min_q_mvar", &DataGen::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) .def_readonly("max_q_mvar", &DataGen::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) .def_readonly("has_res", &DataGen::GenInfo::has_res, DocIterator::has_res.c_str()) @@ -642,6 +644,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("init_shunt", &GridModel::init_shunt, DocGridModel::_internal_do_not_use.c_str()) // same .def("init_trafo", &GridModel::init_trafo, DocGridModel::_internal_do_not_use.c_str()) // same .def("init_generators", &GridModel::init_generators, DocGridModel::_internal_do_not_use.c_str()) // same + .def("init_generators_full", &GridModel::init_generators_full, DocGridModel::_internal_do_not_use.c_str()) // same .def("init_loads", &GridModel::init_loads, DocGridModel::_internal_do_not_use.c_str()) // same .def("init_storages", &GridModel::init_storages, DocGridModel::_internal_do_not_use.c_str()) // same .def("init_sgens", &GridModel::init_sgens, DocGridModel::_internal_do_not_use.c_str()) // same From 582b90cfc3c66cf7a0f9339e4f3f614874db127c Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 1 Dec 2023 17:52:21 +0100 Subject: [PATCH 23/66] preparing for ptdf --- lightsim2grid/gridmodel/from_pypowsybl.py | 2 +- lightsim2grid/securityAnalysis.py | 2 -- src/BaseSolver.h | 10 ++++++++++ src/ChooseSolver.h | 13 +++++++++++++ src/DCSolver.h | 6 ++++++ src/DCSolver.tpp | 22 ++++++++++++++++++++++ src/GridModel.cpp | 7 +++++++ src/GridModel.h | 1 + 8 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index 375fa9dc..b82a2695 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -30,7 +30,7 @@ def _aux_get_bus(bus_df, df, conn_key="connected", bus_key="bus_id"): # mask_disco[wrong_component] = True # assign bus -1 to disconnected elements bus_id[mask_disco] = -1 - return bus_id , mask_disco.values + return bus_id, mask_disco.values def init(net : pypo.network, diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py index aa555ab8..a0cc7242 100644 --- a/lightsim2grid/securityAnalysis.py +++ b/lightsim2grid/securityAnalysis.py @@ -8,8 +8,6 @@ __all__ = ["SecurityAnalysisCPP", "SecurityAnalysis"] -import os -import warnings import copy import numpy as np from collections.abc import Iterable diff --git a/src/BaseSolver.h b/src/BaseSolver.h index 54d87107..8c1eabad 100644 --- a/src/BaseSolver.h +++ b/src/BaseSolver.h @@ -98,6 +98,16 @@ class BaseSolver : public BaseConstants real_type tol ) = 0 ; + virtual Eigen::SparseMatrix get_ptdf(){ + throw std::runtime_error("Impossible to get the PTDF matrix with this solver type."); + } + virtual Eigen::SparseMatrix get_lodf(){ // TODO interface is likely to change + throw std::runtime_error("Impossible to get the LODF matrix with this solver type."); + } + virtual Eigen::SparseMatrix get_bsdf(){ // TODO interface is likely to change + throw std::runtime_error("Impossible to get the BSDF matrix with this solver type."); + } + virtual void reset(); diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h index 57d5e696..ce751882 100644 --- a/src/ChooseSolver.h +++ b/src/ChooseSolver.h @@ -218,6 +218,7 @@ class ChooseSolver auto p_solver = get_prt_solver("get_Vm", true); return p_solver -> get_Vm(); } + Eigen::Ref > get_J() const { check_right_solver("get_J"); @@ -262,6 +263,18 @@ class ChooseSolver else throw std::runtime_error("Unknown solver type encountered (get_J)"); } + Eigen::SparseMatrix get_ptdf(){ + if(_solver_type != SolverType::DC && + _solver_type != SolverType::KLUDC && + _solver_type != SolverType::NICSLUDC && + _solver_type != SolverType::CKTSODC){ + throw std::runtime_error("ChooseSolver::get_ptdf: cannot get ptdf for a solver that is not DC."); + } + auto p_solver = get_prt_solver("get_ptdf", true); + const auto & res = p_solver -> get_ptdf(); + return res; + } + /** apparently i cannot pass a const ref for a sparse matrix in python**/ Eigen::SparseMatrix get_J_python() const{ Eigen::SparseMatrix res = get_J(); diff --git a/src/DCSolver.h b/src/DCSolver.h index 54e16579..e8ee780a 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -34,6 +34,10 @@ class BaseDCSolver: public BaseSolver real_type tol ); + virtual Eigen::SparseMatrix get_ptdf(); // TODO + virtual Eigen::SparseMatrix get_lodf(); // TODO + virtual Eigen::SparseMatrix get_bsdf(); // TODO + private: // no copy allowed BaseDCSolver( const BaseSolver & ) =delete ; @@ -42,6 +46,8 @@ class BaseDCSolver: public BaseSolver protected: LinearSolver _linear_solver; bool need_factorize_; + RealVect Sbus_noslack_; + Eigen::SparseMatrix Ybus_noslack_; }; diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index 36611ee0..114f76c5 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -169,3 +169,25 @@ void BaseDCSolver::reset(){ _linear_solver.reset(); need_factorize_ = true; } + +template +Eigen::SparseMatrix BaseDCSolver::get_ptdf(){ + // TODO + // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) + // and -1. / x / tap for (1..nb_branch, to_bus) + return Ybus_noslack_; +} + +template +Eigen::SparseMatrix BaseDCSolver::get_lodf(){ + // TODO + return Ybus_noslack_; + +} + +template +Eigen::SparseMatrix BaseDCSolver::get_bsdf(){ + // TODO + return Ybus_noslack_; + +} diff --git a/src/GridModel.cpp b/src/GridModel.cpp index e8fbc755..0c13b709 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -720,6 +720,13 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, return res; } +Eigen::SparseMatrix GridModel::get_ptdf(){ + if(Ybus_dc_.size() == 0){ + throw std::runtime_error("Cannot get the ptdf without having first computed a dc powerflow."); + } + return _dc_solver.get_ptdf(); +} + /** Retrieve the number of connected buses **/ diff --git a/src/GridModel.h b/src/GridModel.h index a4d31b80..4df9ee17 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -267,6 +267,7 @@ class GridModel : public DataGeneric int max_iter, // not used for DC real_type tol // not used for DC ); + Eigen::SparseMatrix get_ptdf(); // ac powerflow CplxVect ac_pf(const CplxVect & Vinit, From 54422aedf8c8df6b9173e72e6f05257ba259c005 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 4 Dec 2023 11:24:32 +0100 Subject: [PATCH 24/66] refacto slightly dc solver and add a member to know if solver can solve A.X = B with B being a matrix --- src/BaseSolver.cpp | 13 ++--- src/CKTSOSolver.cpp | 2 + src/CKTSOSolver.h | 3 ++ src/DCSolver.h | 18 ++++++- src/DCSolver.tpp | 112 ++++++++++++++++++++++++----------------- src/KLUSolver.cpp | 2 + src/KLUSolver.h | 3 ++ src/NICSLUSolver.cpp | 2 + src/NICSLUSolver.h | 3 ++ src/SparseLUSolver.cpp | 2 + src/SparseLUSolver.h | 2 + 11 files changed, 105 insertions(+), 57 deletions(-) diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 05c837ed..2ba46cb3 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -140,12 +140,11 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, // pv: list of index of pv nodes // pq: list of index of pq nodes // nb_bus: total number of bus in the grid - // returns: res: the id of the unique slack bus (throw an error if no slack bus is found) - // /!\ does not support multiple slack bus!!! + // returns: res: the ids of all the slack buses (by def: not PV and not PQ) Eigen::VectorXi res(nb_bus - pv.size() - pq.size()); Eigen::Index i_res = 0; - bool found=false; + // run through both pv and pq nodes and declare they are not slack bus std::vector tmp(nb_bus, true); for(unsigned int k=0; k < pv.size(); ++k) @@ -163,16 +162,10 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, { res[i_res] = k; ++i_res; - // if(!found){ - // res = k; - // found = true; - // }else{ - // throw std::runtime_error("BaseSolver::extract_slack_bus_id: multiple slack bus found on your grid !"); - // } } } - // if(res == -1){ if(res.size() != i_res){ + // TODO DEBUG MODE throw std::runtime_error("BaseSolver::extract_slack_bus_id: No slack bus is found in your grid"); } return res; diff --git a/src/CKTSOSolver.cpp b/src/CKTSOSolver.cpp index 0b673f78..8d5b7349 100644 --- a/src/CKTSOSolver.cpp +++ b/src/CKTSOSolver.cpp @@ -11,6 +11,8 @@ #include "CKTSOSolver.h" #include +const bool CKTSOLinearSolver::CAN_SOLVE_MAT = false; + ErrorType CKTSOLinearSolver::reset(){ // free everything diff --git a/src/CKTSOSolver.h b/src/CKTSOSolver.h index 101cb620..2ed7e34a 100644 --- a/src/CKTSOSolver.h +++ b/src/CKTSOSolver.h @@ -64,6 +64,9 @@ class CKTSOLinearSolver ErrorType initialize(Eigen::SparseMatrix & J); ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + // can this linear solver solve problem where RHS is a matrix + static const bool CAN_SOLVE_MAT; + // prevent copy and assignment CKTSOLinearSolver(const CKTSOLinearSolver & other) = delete; CKTSOLinearSolver & operator=( const CKTSOLinearSolver & ) = delete; diff --git a/src/DCSolver.h b/src/DCSolver.h index e8ee780a..7b20b044 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -43,11 +43,25 @@ class BaseDCSolver: public BaseSolver BaseDCSolver( const BaseSolver & ) =delete ; BaseDCSolver & operator=( const BaseSolver & ) =delete; + protected: + void fill_mat_bus_id(int nb_bus_solver); + void fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat); + + // remove_slack_buses: res_mat is initialized and make_compressed in this function + template // ref_mat_type should be `real_type` or `cplx_type` + void remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat); + protected: LinearSolver _linear_solver; bool need_factorize_; - RealVect Sbus_noslack_; - Eigen::SparseMatrix Ybus_noslack_; + + // save this not to recompute them when not needed + RealVect dcSbus_noslack_; + Eigen::SparseMatrix dcYbus_noslack_; + Eigen::VectorXi my_pv_; + Eigen::VectorXi slack_buses_ids_solver_; + // -1 if bus is slack , else the id of the row / column used in the linear solver representing this bus + Eigen::VectorXi mat_bus_id_; // formerly `ybus_to_me` }; diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index 114f76c5..e0b1ad14 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -33,47 +33,24 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix #ifdef __COUT_TIMES auto timer_preproc = CustTimer(); #endif // __COUT_TIMES - Eigen::SparseMatrix dcYbus = Eigen::SparseMatrix(nb_bus_solver - 1, nb_bus_solver - 1); const CplxVect & Sbus_tmp = Sbus; // TODO SLACK (for now i put all slacks as PV, except the first one) // this should be handled in Sbus, because we know the amount of power absorbed by the slack // so we can compute it correctly ! - const Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv); + my_pv_ = retrieve_pv_with_slack(slack_ids, pv); // const Eigen::VectorXi & my_pv = pv; // find the slack buses - Eigen::VectorXi slack_bus_ids_solver = extract_slack_bus_id(my_pv, pq, nb_bus_solver); + slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, nb_bus_solver); + // corresp bus -> solverbus - Eigen::VectorXi ybus_to_me = Eigen::VectorXi::Constant(nb_bus_solver, -1); - // Eigen::VectorXi me_to_ybus = Eigen::VectorXi::Constant(nb_bus_solver - slack_bus_ids_solver.size(), -1); - int solver_id = 0; - for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){ - if(isin(ybus_id, slack_bus_ids_solver)) continue; // I don't add anything to the slack bus - ybus_to_me(ybus_id) = solver_id; - // me_to_ybus(solver_id) = ybus_id; - ++solver_id; - } + fill_mat_bus_id(nb_bus_solver); + // remove the slack bus from Ybus // and extract only real part - // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29 - std::vector > tripletList; - tripletList.reserve(Ybus.nonZeros()); - for (int k=0; k < nb_bus_solver; ++k){ - if(ybus_to_me(k) == -1) continue; // I don't add anything to the slack bus - for (Eigen::SparseMatrix::InnerIterator it(Ybus, k); it; ++it) - { - int row_res = static_cast(it.row()); // TODO Eigen::Index here ? - if(ybus_to_me(row_res) == -1) continue; - row_res = ybus_to_me(row_res); - int col_res = static_cast(it.col()); // should be k // TODO Eigen::Index here ? - col_res = ybus_to_me(col_res); - tripletList.push_back(Eigen::Triplet (row_res, col_res, std::real(it.value()))); - } - } - dcYbus.setFromTriplets(tripletList.begin(), tripletList.end()); - dcYbus.makeCompressed(); + fill_dcYbus_noslack(nb_bus_solver, Ybus); #ifdef __COUT_TIMES std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl; @@ -85,7 +62,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix #endif // __COUT_TIMES bool just_factorize = false; if(need_factorize_){ - ErrorType status_init = _linear_solver.initialize(dcYbus); + ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; return false; @@ -95,17 +72,16 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } // remove the slack bus from Sbus - RealVect dcSbus = RealVect::Constant(nb_bus_solver - slack_bus_ids_solver.size(), my_zero_); + dcSbus_noslack_ = RealVect::Constant(nb_bus_solver - slack_buses_ids_solver_.size(), my_zero_); for (int k=0; k < nb_bus_solver; ++k){ - if(ybus_to_me(k) == -1) continue; // I don't add anything to the slack bus - const int col_res = ybus_to_me(k); - dcSbus(col_res) = std::real(Sbus_tmp(k)); + if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus + const int col_res = mat_bus_id_(k); + dcSbus_noslack_(col_res) = std::real(Sbus_tmp(k)); } - // solve for theta: Sbus = dcY . theta - RealVect Va_dc_without_slack = dcSbus; - - ErrorType error = _linear_solver.solve(dcYbus, Va_dc_without_slack, just_factorize); + // solve for theta: Sbus = dcY . theta (make a copy to keep dcSbus_noslack_) + RealVect Va_dc_without_slack = dcSbus_noslack_; + ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, just_factorize); if(error != ErrorType::NoError){ err_ = error; timer_total_nr_ += timer.duration(); @@ -135,18 +111,18 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix RealVect Va_dc = RealVect::Constant(nb_bus_solver, my_zero_); // fill Va from dc approx for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){ - if(ybus_to_me(ybus_id) == -1) continue; // slack bus is handled elsewhere - const int bus_me = ybus_to_me(ybus_id); + if(mat_bus_id_(ybus_id) == -1) continue; // slack bus is handled elsewhere + const int bus_me = mat_bus_id_(ybus_id); Va_dc(ybus_id) = Va_dc_without_slack(bus_me); } - Va_dc.array() += std::arg(V(slack_bus_ids_solver(0))); + Va_dc.array() += std::arg(V(slack_buses_ids_solver_(0))); // save the results Va_ = Va_dc; // add the Voltage setpoints of the generator Vm_ = V.array().abs(); - Vm_(slack_bus_ids_solver) = V(slack_bus_ids_solver).array().abs(); + Vm_(slack_buses_ids_solver_) = V(slack_buses_ids_solver_).array().abs(); // now compute the resulting complex voltage V_ = (Va_.array().cos().template cast() + my_i * Va_.array().sin().template cast()); @@ -163,11 +139,57 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix return true; } +template +void BaseDCSolver::fill_mat_bus_id(int nb_bus_solver){ + mat_bus_id_ = Eigen::VectorXi::Constant(nb_bus_solver, -1); + // Eigen::VectorXi me_to_ybus = Eigen::VectorXi::Constant(nb_bus_solver - slack_bus_ids_solver.size(), -1); + int solver_id = 0; + for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){ + if(isin(ybus_id, slack_buses_ids_solver_)) continue; // I don't add anything to the slack bus + mat_bus_id_(ybus_id) = solver_id; + // me_to_ybus(solver_id) = ybus_id; + ++solver_id; + } +} + +template +void BaseDCSolver::fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat){ + // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29 + remove_slack_buses(nb_bus_solver, ref_mat, dcYbus_noslack_); +} + +template +template // ref_mat_type should be `real_type` or `cplx_type` +void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ + res_mat = Eigen::SparseMatrix(nb_bus_solver - 1, nb_bus_solver - 1); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? + std::vector > tripletList; + tripletList.reserve(ref_mat.nonZeros()); + for (int k=0; k < nb_bus_solver; ++k){ + if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus + for (typename Eigen::SparseMatrix::InnerIterator it(ref_mat, k); it; ++it) + { + int row_res = static_cast(it.row()); // TODO Eigen::Index here ? + if(mat_bus_id_(row_res) == -1) continue; + row_res = mat_bus_id_(row_res); + int col_res = static_cast(it.col()); // should be k // TODO Eigen::Index here ? + col_res = mat_bus_id_(col_res); + tripletList.push_back(Eigen::Triplet (row_res, col_res, std::real(it.value()))); + } + } + res_mat.setFromTriplets(tripletList.begin(), tripletList.end()); + res_mat.makeCompressed(); +} + template void BaseDCSolver::reset(){ BaseSolver::reset(); _linear_solver.reset(); need_factorize_ = true; + dcSbus_noslack_ = RealVect(); + dcYbus_noslack_ = Eigen::SparseMatrix(); + my_pv_ = Eigen::VectorXi(); + slack_buses_ids_solver_ = Eigen::VectorXi(); + mat_bus_id_ = Eigen::VectorXi(); } template @@ -175,19 +197,19 @@ Eigen::SparseMatrix BaseDCSolver::get_ptdf(){ // TODO // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) // and -1. / x / tap for (1..nb_branch, to_bus) - return Ybus_noslack_; + return dcYbus_noslack_; } template Eigen::SparseMatrix BaseDCSolver::get_lodf(){ // TODO - return Ybus_noslack_; + return dcYbus_noslack_; } template Eigen::SparseMatrix BaseDCSolver::get_bsdf(){ // TODO - return Ybus_noslack_; + return dcYbus_noslack_; } diff --git a/src/KLUSolver.cpp b/src/KLUSolver.cpp index 316712a8..fe8efc4f 100644 --- a/src/KLUSolver.cpp +++ b/src/KLUSolver.cpp @@ -10,6 +10,8 @@ #include +const bool KLULinearSolver::CAN_SOLVE_MAT = false; + ErrorType KLULinearSolver::reset(){ klu_free_symbolic(&symbolic_, &common_); klu_free_numeric(&numeric_, &common_); diff --git a/src/KLUSolver.h b/src/KLUSolver.h index c7aff02a..2eefe06e 100644 --- a/src/KLUSolver.h +++ b/src/KLUSolver.h @@ -48,6 +48,9 @@ class KLULinearSolver ErrorType initialize(Eigen::SparseMatrix& J); ErrorType solve(Eigen::SparseMatrix& J, RealVect & b, bool has_just_been_inialized); + // can this linear solver solve problem where RHS is a matrix + static const bool CAN_SOLVE_MAT; + private: // solver initialization klu_symbolic* symbolic_; diff --git a/src/NICSLUSolver.cpp b/src/NICSLUSolver.cpp index 18fed9e6..357a6726 100644 --- a/src/NICSLUSolver.cpp +++ b/src/NICSLUSolver.cpp @@ -9,6 +9,8 @@ #include "NICSLUSolver.h" #include +const bool NICSLULinearSolver::CAN_SOLVE_MAT = false; + ErrorType NICSLULinearSolver::reset(){ // free everything solver_.Free(); diff --git a/src/NICSLUSolver.h b/src/NICSLUSolver.h index 825bc988..481b4a09 100644 --- a/src/NICSLUSolver.h +++ b/src/NICSLUSolver.h @@ -56,6 +56,9 @@ class NICSLULinearSolver ErrorType initialize(Eigen::SparseMatrix & J); ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + // can this linear solver solve problem where RHS is a matrix + static const bool CAN_SOLVE_MAT; + // prevent copy and assignment NICSLULinearSolver(const NICSLULinearSolver & other) = delete; NICSLULinearSolver & operator=( const NICSLULinearSolver & ) = delete; diff --git a/src/SparseLUSolver.cpp b/src/SparseLUSolver.cpp index 640a8795..98c31491 100644 --- a/src/SparseLUSolver.cpp +++ b/src/SparseLUSolver.cpp @@ -10,6 +10,8 @@ #include +const bool SparseLULinearSolver::CAN_SOLVE_MAT = true; + ErrorType SparseLULinearSolver::initialize(const Eigen::SparseMatrix & J){ // default Eigen representation: column major, which is good for klu ! // J is const here diff --git a/src/SparseLUSolver.h b/src/SparseLUSolver.h index 34849a5a..768fd67f 100644 --- a/src/SparseLUSolver.h +++ b/src/SparseLUSolver.h @@ -37,6 +37,8 @@ class SparseLULinearSolver ErrorType solve(const Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); ErrorType reset(){ return ErrorType::NoError; } + // can this linear solver solve problem where RHS is a matrix + static const bool CAN_SOLVE_MAT; private: // solver initialization Eigen::SparseLU, Eigen::COLAMDOrdering > solver_; From d0840af6788b710cda1887ad5de1bdf80b5003fe Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 4 Dec 2023 15:49:45 +0100 Subject: [PATCH 25/66] fix a bug in the detection of the main component --- lightsim2grid/gridmodel/from_pypowsybl.py | 22 +++++++++---------- lightsim2grid/lightSimBackend.py | 1 - lightsim2grid/tests/test_backend_pypowsybl.py | 3 ++- src/GridModel.cpp | 10 ++++++--- src/SecurityAnalysis.cpp | 4 +++- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index b82a2695..ef7a856a 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -38,7 +38,7 @@ def init(net : pypo.network, slack_bus_id: int = None, sn_mva = 100., sort_index=True, - f_hz = 50., + f_hz = 50., # unused only_main_component=True): model = GridModel() # model.set_f_hz(f_hz) @@ -89,8 +89,8 @@ def init(net : pypo.network, for gen_id, is_disco in enumerate(gen_disco): if is_disco: model.deactivate_gen(gen_id) - model.set_gen_names(df_gen.index) - + model.set_gen_names(df_gen.index) + # for loads if sort_index: df_load = net.get_loads().sort_index() @@ -104,7 +104,7 @@ def init(net : pypo.network, for load_id, is_disco in enumerate(load_disco): if is_disco: model.deactivate_load(load_id) - model.set_load_names(df_load.index) + model.set_load_names(df_load.index) # for lines if sort_index: @@ -148,7 +148,7 @@ def init(net : pypo.network, for line_id, (is_or_disc, is_ex_disc) in enumerate(zip(lor_disco, lex_disco)): if is_or_disc or is_ex_disc: model.deactivate_powerline(line_id) - model.set_line_names(df_line.index) + model.set_line_names(df_line.index) # for trafo if sort_index: @@ -184,7 +184,7 @@ def init(net : pypo.network, for t_id, (is_or_disc, is_ex_disc) in enumerate(zip(tor_disco, tex_disco)): if is_or_disc or is_ex_disc: model.deactivate_trafo(t_id) - model.set_trafo_names(df_trafo.index) + model.set_trafo_names(df_trafo.index) # for shunt if sort_index: @@ -201,7 +201,7 @@ def init(net : pypo.network, for shunt_id, disco in enumerate(sh_disco): if disco: model.deactivate_shunt(shunt_id) - model.set_shunt_names(df_trafo.index) + model.set_shunt_names(df_trafo.index) # for hvdc (TODO not tested yet) df_dc = net.get_hvdc_lines().sort_index() @@ -228,7 +228,7 @@ def init(net : pypo.network, for hvdc_id, (is_or_disc, is_ex_disc) in enumerate(zip(hvdc_from_disco, hvdc_to_disco)): if is_or_disc or is_ex_disc: model.deactivate_hvdc(hvdc_id) - model.set_dcline_names(df_sations.index) + model.set_dcline_names(df_sations.index) # storage units (TODO not tested yet) if sort_index: @@ -243,7 +243,7 @@ def init(net : pypo.network, for batt_id, disco in enumerate(batt_disco): if disco: model.deactivate_storage(batt_id) - model.set_storage_names(df_batt.index) + model.set_storage_names(df_batt.index) # TODO dist slack if gen_slack_id is None and slack_bus_id is None: @@ -262,8 +262,8 @@ def init(net : pypo.network, raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack") for gen_id, is_slack in enumerate(gen_is_conn_slack): if is_slack: - model.add_gen_slackbus(gen_id, 1. / nb_conn) - + model.add_gen_slackbus(gen_id, 1. / nb_conn) + # TODO # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 6b068e4c..cf814596 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -446,7 +446,6 @@ def make_complete_path(path, filename): raise Grid2OpException('There is no powergrid at "{}"'.format(full_path)) return full_path full_path = make_complete_path(path, filename) - grid_tmp = pypow_net.load(full_path) gen_slack_id = None if "gen_slack_id" in loader_kwargs: diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py index bce90c25..1390698d 100644 --- a/lightsim2grid/tests/test_backend_pypowsybl.py +++ b/lightsim2grid/tests/test_backend_pypowsybl.py @@ -92,7 +92,8 @@ def test_init(self): grid.ac_pf(np.ones(14, dtype=np.complex128), 10, 1e-6) def test_runpf(self): - backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=_aux_get_loader_kwargs()) + backend = LightSimBackend(loader_method="pypowsybl", + loader_kwargs=_aux_get_loader_kwargs()) self._aux_prep_backend(backend) # AC powerflow conv, exc_ = backend.runpf() diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 0c13b709..edce0ec2 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -1002,20 +1002,25 @@ void GridModel::consider_only_main_component(){ // TODO copy paste from SecurityAnalysis std::queue neighborhood; for(const auto & el : slack_buses_id) neighborhood.push(el); + if(neighborhood.empty()){ + throw std::runtime_error("There is no slack buses defined in your grid. You cannot isolate the main component."); + } std::vector visited(nb_busbars, false); + std::vector already_added(nb_busbars, false); while (true) { const Eigen::Index col_id = neighborhood.front(); + neighborhood.pop(); visited[col_id] = true; for (Eigen::SparseMatrix::InnerIterator it(graph, col_id); it; ++it) { // add in the queue all my neighbor (if the coefficient is big enough) - if(!visited[it.row()] ){ // && abs(it.value()) > 1e-8 + if(!visited[it.row()] && !already_added[it.row()]){ // && abs(it.value()) > 1e-8 neighborhood.push(it.row()); + already_added[it.row()] = true; } } if(neighborhood.empty()) break; - neighborhood.pop(); } // disconnected elements not in main component @@ -1027,7 +1032,6 @@ void GridModel::consider_only_main_component(){ storages_.disconnect_if_not_in_main_component(visited); generators_.disconnect_if_not_in_main_component(visited); dc_lines_.disconnect_if_not_in_main_component(visited); - // and finally deal with the buses init_bus_status(); } \ No newline at end of file diff --git a/src/SecurityAnalysis.cpp b/src/SecurityAnalysis.cpp index 0b9fd57a..f4d5c9a1 100644 --- a/src/SecurityAnalysis.cpp +++ b/src/SecurityAnalysis.cpp @@ -12,6 +12,7 @@ bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Ybus) const{ std::vector visited(Ybus.cols(), false); + std::vector already_added(Ybus.cols(), false); std::queue neighborhood; Eigen::Index col_id = 0; // start by node 0, why not while (true) @@ -20,8 +21,9 @@ bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Y for (Eigen::SparseMatrix::InnerIterator it(Ybus, col_id); it; ++it) { // add in the queue all my neighbor (if the coefficient is big enough) - if(!visited[it.row()] && abs(it.value()) > 1e-8){ + if(!visited[it.row()] && !already_added[it.row()] && abs(it.value()) > 1e-8){ neighborhood.push(it.row()); + already_added[it.row()] = true; } } if(neighborhood.empty()) break; From 8c5791ef44c2822e756af38fb407839ab3402a9c Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 4 Dec 2023 17:46:43 +0100 Subject: [PATCH 26/66] start real work on ptdf [skip ci] --- src/BaseSolver.cpp | 6 ++++++ src/BaseSolver.h | 2 ++ src/DCSolver.h | 3 ++- src/DCSolver.tpp | 25 +++++++++++------------ src/DataGeneric.h | 5 +++++ src/DataLine.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ src/DataLine.h | 5 +++++ src/DataTrafo.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++++ src/DataTrafo.h | 4 ++++ src/GridModel.cpp | 23 ++++++++++++++++++++++ src/GridModel.h | 2 ++ 11 files changed, 155 insertions(+), 13 deletions(-) diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 2ba46cb3..6bec0853 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -170,3 +170,9 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, } return res; } + + +void BaseSolver::get_Bf(Eigen::SparseMatrix & Bf) const { + if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); + _gridmodel->fillBf_for_PTDF(Bf); +} diff --git a/src/BaseSolver.h b/src/BaseSolver.h index 8c1eabad..ea6fd366 100644 --- a/src/BaseSolver.h +++ b/src/BaseSolver.h @@ -200,6 +200,8 @@ class BaseSolver : public BaseConstants return false; } + void get_Bf(Eigen::SparseMatrix & Bf) const; + protected: // solver initialization int n_; diff --git a/src/DCSolver.h b/src/DCSolver.h index 7b20b044..cc07778a 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -15,7 +15,7 @@ template class BaseDCSolver: public BaseSolver { public: - BaseDCSolver():BaseSolver(false), _linear_solver(), need_factorize_(true){}; + BaseDCSolver():BaseSolver(false), _linear_solver(), need_factorize_(true), nb_dcbus_solver_(0){}; ~BaseDCSolver(){} @@ -56,6 +56,7 @@ class BaseDCSolver: public BaseSolver bool need_factorize_; // save this not to recompute them when not needed + int nb_dcbus_solver_; RealVect dcSbus_noslack_; Eigen::SparseMatrix dcYbus_noslack_; Eigen::VectorXi my_pv_; diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index e0b1ad14..ca344bb9 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -28,7 +28,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix auto timer = CustTimer(); BaseSolver::reset_timer(); - const int nb_bus_solver = static_cast(Ybus.rows()); + nb_dcbus_solver_ = static_cast(Ybus.rows()); #ifdef __COUT_TIMES auto timer_preproc = CustTimer(); @@ -43,14 +43,14 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // const Eigen::VectorXi & my_pv = pv; // find the slack buses - slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, nb_bus_solver); + slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, nb_dcbus_solver_); // corresp bus -> solverbus - fill_mat_bus_id(nb_bus_solver); + fill_mat_bus_id(nb_dcbus_solver_); // remove the slack bus from Ybus // and extract only real part - fill_dcYbus_noslack(nb_bus_solver, Ybus); + fill_dcYbus_noslack(nb_dcbus_solver_, Ybus); #ifdef __COUT_TIMES std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl; @@ -72,8 +72,8 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } // remove the slack bus from Sbus - dcSbus_noslack_ = RealVect::Constant(nb_bus_solver - slack_buses_ids_solver_.size(), my_zero_); - for (int k=0; k < nb_bus_solver; ++k){ + dcSbus_noslack_ = RealVect::Constant(nb_dcbus_solver_ - slack_buses_ids_solver_.size(), my_zero_); + for (int k=0; k < nb_dcbus_solver_; ++k){ if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus const int col_res = mat_bus_id_(k); dcSbus_noslack_(col_res) = std::real(Sbus_tmp(k)); @@ -108,9 +108,9 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // retrieve back the results in the proper shape (add back the slack bus) // TODO have a better way for this, for example using `.segment(0,npv)` // see the BaseSolver.cpp: _evaluate_Fx - RealVect Va_dc = RealVect::Constant(nb_bus_solver, my_zero_); + RealVect Va_dc = RealVect::Constant(nb_dcbus_solver_, my_zero_); // fill Va from dc approx - for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){ + for (int ybus_id=0; ybus_id < nb_dcbus_solver_; ++ybus_id){ if(mat_bus_id_(ybus_id) == -1) continue; // slack bus is handled elsewhere const int bus_me = mat_bus_id_(ybus_id); Va_dc(ybus_id) = Va_dc_without_slack(bus_me); @@ -185,6 +185,7 @@ void BaseDCSolver::reset(){ BaseSolver::reset(); _linear_solver.reset(); need_factorize_ = true; + nb_dcbus_solver_ = 0; dcSbus_noslack_ = RealVect(); dcYbus_noslack_ = Eigen::SparseMatrix(); my_pv_ = Eigen::VectorXi(); @@ -194,10 +195,10 @@ void BaseDCSolver::reset(){ template Eigen::SparseMatrix BaseDCSolver::get_ptdf(){ - // TODO - // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) - // and -1. / x / tap for (1..nb_branch, to_bus) - return dcYbus_noslack_; + Eigen::SparseMatrix Bf_with_slack; + get_Bf(Bf_with_slack); + + return Bf_with_slack; } template diff --git a/src/DataGeneric.h b/src/DataGeneric.h index e3ccd6bd..9ccdc59d 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -77,6 +77,11 @@ class DataGeneric : public BaseConstants const std::vector & id_grid_to_solver, real_type sn_mva, FDPFMethod xb_or_bx) const {}; + + virtual void fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_line) const {}; virtual void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) {}; virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {}; diff --git a/src/DataLine.cpp b/src/DataLine.cpp index 2f63c5b8..ee833cfa 100644 --- a/src/DataLine.cpp +++ b/src/DataLine.cpp @@ -288,6 +288,50 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, } } + +void DataLine::fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_line) const +{ + const Eigen::Index nb_line = static_cast(powerlines_r_.size()); + + for(Eigen::Index line_id=0; line_id < nb_line; ++line_id){ + // i only add this if the powerline is connected + if(!status_[line_id]) continue; + + // get the from / to bus id + int bus_or_id_me = bus_or_id_(line_id); + int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; + if(bus_or_solver_id == _deactivated_bus_id){ + std::ostringstream exc_; + exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << line_id; + exc_ << " is connected (or side) to a disconnected bus while being connected"; + throw std::runtime_error(exc_.str()); + } + int bus_ex_id_me = bus_ex_id_(line_id); + int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; + if(bus_ex_solver_id == _deactivated_bus_id){ + std::ostringstream exc_; + exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << line_id; + exc_ << " is connected (ex side) to a disconnected bus while being connected"; + throw std::runtime_error(exc_.str()); + } + real_type x = powerlines_x_(line_id); + + // TODO + // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) + // and -1. / x / tap for (1..nb_branch, to_bus) + + Bf.push_back(Eigen::Triplet (line_id, bus_or_id_me, 1. / x)); + Bf.push_back(Eigen::Triplet (line_id, bus_ex_solver_id, -1. / x)); + } + +} + + void DataLine::reset_results() { res_powerline_por_ = RealVect(); // in MW diff --git a/src/DataLine.h b/src/DataLine.h index 08dd20d7..348d5701 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -204,6 +204,11 @@ class DataLine : public DataGeneric const std::vector & id_grid_to_solver, real_type sn_mva, FDPFMethod xb_or_bx) const; + virtual void fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_line) const; + virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); void compute_results(const Eigen::Ref & Va, diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp index 22cdce10..5c80d948 100644 --- a/src/DataTrafo.cpp +++ b/src/DataTrafo.cpp @@ -54,6 +54,7 @@ void DataTrafo::init(const RealVect & trafo_r, } + DataTrafo::StateRes DataTrafo::get_state() const { std::vector branch_r(r_.begin(), r_.end()); @@ -68,6 +69,8 @@ DataTrafo::StateRes DataTrafo::get_state() const DataTrafo::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); return res; } + + void DataTrafo::set_state(DataTrafo::StateRes & my_state) { reset_results(); @@ -109,6 +112,7 @@ void DataTrafo::set_state(DataTrafo::StateRes & my_state) _update_model_coeffs(); } + void DataTrafo::_update_model_coeffs() { const Eigen::Index my_size = r_.size(); @@ -353,6 +357,7 @@ void DataTrafo::reset_results(){ res_a_lv_ = RealVect(); // in kA } + void DataTrafo::fillBp_Bpp(std::vector > & Bp, std::vector > & Bpp, const std::vector & id_grid_to_solver, @@ -453,6 +458,50 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, } } + +void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_line) const +{ + const Eigen::Index nb_line = static_cast(r_.size()); + + for(Eigen::Index tr_id=0; tr_id < nb_line; ++tr_id){ + // i only add this if the powerline is connected + if(!status_[tr_id]) continue; + + // get the from / to bus id + int bus_or_id_me = bus_hv_id_(tr_id); + int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; + if(bus_or_solver_id == _deactivated_bus_id){ + std::ostringstream exc_; + exc_ << "DataTrafo::fillBf_for_PTDF: the line with id "; + exc_ << tr_id; + exc_ << " is connected (hv side) to a disconnected bus while being connected"; + throw std::runtime_error(exc_.str()); + } + int bus_ex_id_me = bus_lv_id_(tr_id); + int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; + if(bus_ex_solver_id == _deactivated_bus_id){ + std::ostringstream exc_; + exc_ << "tr_id::fillBf_for_PTDF: the line with id "; + exc_ << tr_id; + exc_ << " is connected (lv side) to a disconnected bus while being connected"; + throw std::runtime_error(exc_.str()); + } + real_type x = x_(tr_id); + real_type _1_tau = is_tap_hv_side_[tr_id] ? 1. / ratio_(tr_id) : ratio_(tr_id); // 1. / tau + + // TODO + // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) + // and -1. / x / tap for (1..nb_branch, to_bus) + Bf.push_back(Eigen::Triplet (tr_id + nb_line, bus_or_id_me, 1. / x * _1_tau)); + Bf.push_back(Eigen::Triplet (tr_id + nb_line, bus_ex_solver_id, -1. / x * _1_tau)); + } + +} + + void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ const Eigen::Index nb_trafo = nb(); diff --git a/src/DataTrafo.h b/src/DataTrafo.h index 5afc2400..ea168389 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -196,6 +196,10 @@ class DataTrafo : public DataGeneric const std::vector & id_grid_to_solver, real_type sn_mva, FDPFMethod xb_or_bx) const; + virtual void fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_line) const; virtual void hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver); // needed for dc mode void compute_results(const Eigen::Ref & Va, diff --git a/src/GridModel.cpp b/src/GridModel.cpp index edce0ec2..3cedd6e6 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -922,6 +922,29 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp, Bpp.makeCompressed(); } + +void GridModel::fillBf_for_PTDF(Eigen::SparseMatrix & Bf) const +{ + const int nb_bus_solver = static_cast(id_ac_solver_to_me_.size()); + // TODO PTDF: nb_line, nb_bus or nb_branch, nb_bus ??? + // TODO PTDF: if we don't have nb_branch we need a converter line_id => branch id + Bf = Eigen::SparseMatrix(powerlines_.nb() + trafos_.nb(), nb_bus_solver); + std::vector > tripletList; + tripletList.reserve(bus_vn_kv_.size() + 2 * powerlines_.nb() + 2 * trafos_.nb()); + + powerlines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); // TODO have a function to dispatch that to all type of elements + shunts_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + trafos_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + loads_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + sgens_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + storages_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + generators_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + dc_lines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + + Bf.setFromTriplets(tripletList.begin(), tripletList.end()); + Bf.makeCompressed(); +} + // returns only the gen_id with the highest p that is connected to this bus ! // returns bus_id, gen_bus_id std::tuple GridModel::assign_slack_to_most_connected(){ diff --git a/src/GridModel.h b/src/GridModel.h index 4df9ee17..a7bb1004 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -568,6 +568,8 @@ class GridModel : public DataGeneric Eigen::SparseMatrix & Bpp, FDPFMethod xb_or_bx) const; + void fillBf_for_PTDF(Eigen::SparseMatrix & Bf) const; + Eigen::SparseMatrix debug_get_Bp_python(FDPFMethod xb_or_bx){ Eigen::SparseMatrix Bp; Eigen::SparseMatrix Bpp; From 4602b15c3002888ba136f88f00f78beed90c221d Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 5 Dec 2023 18:01:52 +0100 Subject: [PATCH 27/66] making progress on ptdf [skip ci] --- docs/conf.py | 2 +- lightsim2grid/__init__.py | 2 +- setup.py | 2 +- src/BaseSolver.cpp | 6 ++++ src/BaseSolver.h | 6 ++-- src/ChooseSolver.h | 10 +++--- src/DCSolver.h | 6 ++-- src/DCSolver.tpp | 75 ++++++++++++++++++++++++++++++++++++--- src/DataDCLine.cpp | 12 +++---- src/DataGeneric.h | 3 +- src/DataLine.cpp | 19 ++++++---- src/DataLine.h | 3 +- src/DataTrafo.cpp | 20 +++++++---- src/DataTrafo.h | 3 +- src/GridModel.cpp | 56 +++++++++++++++++++---------- src/GridModel.h | 9 +++-- src/Utils.h | 3 ++ src/main.cpp | 3 ++ 18 files changed, 177 insertions(+), 63 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b57b808c..f89887a6 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.7.6.dev0" +release = "0.7.6.dev1" version = '0.7' # -- General configuration --------------------------------------------------- diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py index 577629d8..929780f7 100644 --- a/lightsim2grid/__init__.py +++ b/lightsim2grid/__init__.py @@ -5,7 +5,7 @@ # you can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -__version__ = "0.7.6.dev0" +__version__ = "0.7.6.dev1" __all__ = ["newtonpf", "SolverType", "ErrorType", "solver"] diff --git a/setup.py b/setup.py index 50c1c9b1..99193f8a 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext -__version__ = "0.7.6.dev0" +__version__ = "0.7.6.dev1" KLU_SOLVER_AVAILABLE = False # Try to link against SuiteSparse (if available) diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 6bec0853..1dea2448 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -7,6 +7,7 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. #include "BaseSolver.h" +#include "GridModel.h" // needs to be included here because of the forward declaration void BaseSolver::reset(){ // reset timers @@ -176,3 +177,8 @@ void BaseSolver::get_Bf(Eigen::SparseMatrix & Bf) const { if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); _gridmodel->fillBf_for_PTDF(Bf); } + +void BaseSolver::get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const { + if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); + _gridmodel->fillBf_for_PTDF(Bf_T, true); +} diff --git a/src/BaseSolver.h b/src/BaseSolver.h index ea6fd366..97c7ca4f 100644 --- a/src/BaseSolver.h +++ b/src/BaseSolver.h @@ -98,7 +98,7 @@ class BaseSolver : public BaseConstants real_type tol ) = 0 ; - virtual Eigen::SparseMatrix get_ptdf(){ + virtual RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus){ throw std::runtime_error("Impossible to get the PTDF matrix with this solver type."); } virtual Eigen::SparseMatrix get_lodf(){ // TODO interface is likely to change @@ -108,8 +108,7 @@ class BaseSolver : public BaseConstants throw std::runtime_error("Impossible to get the BSDF matrix with this solver type."); } - virtual - void reset(); + virtual void reset(); protected: virtual void reset_timer(){ @@ -201,6 +200,7 @@ class BaseSolver : public BaseConstants } void get_Bf(Eigen::SparseMatrix & Bf) const; + void get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const; protected: // solver initialization diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h index ce751882..4c713b69 100644 --- a/src/ChooseSolver.h +++ b/src/ChooseSolver.h @@ -263,15 +263,15 @@ class ChooseSolver else throw std::runtime_error("Unknown solver type encountered (get_J)"); } - Eigen::SparseMatrix get_ptdf(){ + RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus){ if(_solver_type != SolverType::DC && - _solver_type != SolverType::KLUDC && - _solver_type != SolverType::NICSLUDC && - _solver_type != SolverType::CKTSODC){ + _solver_type != SolverType::KLUDC && + _solver_type != SolverType::NICSLUDC && + _solver_type != SolverType::CKTSODC){ throw std::runtime_error("ChooseSolver::get_ptdf: cannot get ptdf for a solver that is not DC."); } auto p_solver = get_prt_solver("get_ptdf", true); - const auto & res = p_solver -> get_ptdf(); + const auto & res = p_solver -> get_ptdf(dcYbus); return res; } diff --git a/src/DCSolver.h b/src/DCSolver.h index cc07778a..38c110de 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -34,9 +34,9 @@ class BaseDCSolver: public BaseSolver real_type tol ); - virtual Eigen::SparseMatrix get_ptdf(); // TODO - virtual Eigen::SparseMatrix get_lodf(); // TODO - virtual Eigen::SparseMatrix get_bsdf(); // TODO + virtual RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus); // TODO PTDF + virtual Eigen::SparseMatrix get_lodf(); // TODO PTDF + virtual Eigen::SparseMatrix get_bsdf(); // TODO PTDF private: // no copy allowed diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index ca344bb9..1e65858a 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -161,7 +161,7 @@ void BaseDCSolver::fill_dcYbus_noslack(int nb_bus_solver, const Ei template template // ref_mat_type should be `real_type` or `cplx_type` void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ - res_mat = Eigen::SparseMatrix(nb_bus_solver - 1, nb_bus_solver - 1); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? + res_mat = Eigen::SparseMatrix(nb_bus_solver - slack_buses_ids_solver_.size(), nb_bus_solver - slack_buses_ids_solver_.size()); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? std::vector > tripletList; tripletList.reserve(ref_mat.nonZeros()); for (int k=0; k < nb_bus_solver; ++k){ @@ -194,11 +194,76 @@ void BaseDCSolver::reset(){ } template -Eigen::SparseMatrix BaseDCSolver::get_ptdf(){ - Eigen::SparseMatrix Bf_with_slack; - get_Bf(Bf_with_slack); +RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix & dcYbus){ + Eigen::SparseMatrix Bf_T_with_slack; + RealMat PTDF; + RealVect rhs; + // TODO PTDF: sparse matrix ? + // TODO PTDF: distributed slack + // TODO PTDF: check that the solver has converged + + + //extract the Bf matrix + BaseSolver::get_Bf_transpose(Bf_T_with_slack); // Bf_T_with_slack : [bus_id, line_or_trafo_id] + const int nb_bus = Bf_T_with_slack.rows(); + const int nb_pow_tr = Bf_T_with_slack.cols(); // cols and not rows because Bf_T_with_slack is transposed - return Bf_with_slack; + // get the index of buses without slacks + std::vector ind_no_slack_; + ind_no_slack_.reserve(nb_bus); + for(int bus_id = 0; bus_id < nb_bus; ++bus_id){ + if(mat_bus_id_(bus_id) == -1) continue; + ind_no_slack_.push_back(bus_id); + } + const Eigen::VectorXi ind_no_slack = Eigen::VectorXi::Map(&ind_no_slack_[0], ind_no_slack_.size()); + + // solve iteratively the linear systems (one per powerline) + PTDF = RealMat(Bf_T_with_slack.cols(), Bf_T_with_slack.rows()); // rows and cols are "inverted" because the matrix Bf is transposed + rhs = RealVect::Zero(Bf_T_with_slack.cols() - slack_buses_ids_solver_.size()); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? + for (int line_id=0; line_id < nb_pow_tr; ++line_id){ + // build the rhs vector + for (typename Eigen::SparseMatrix::InnerIterator it(Bf_T_with_slack, line_id); it; ++it) + { + const auto bus_id = it.row(); + if(mat_bus_id_(bus_id) == -1) continue; // I don't add anything if it's the slack + const auto col_res = mat_bus_id_(bus_id); + rhs[col_res] = it.value(); + } + if (line_id == 16){ + std::cout << "line 16\n"; + for(auto i = 0; i < nb_bus; ++i) std::cout << rhs[i] << ", "; + std::cout << std::endl; + } + if (line_id == 17){ + std::cout << "line 17\n"; + for(auto i = 0; i < nb_bus; ++i) std::cout << rhs[i] << ", "; + std::cout << std::endl; + } + if (line_id == 18){ + std::cout << "line 18\n"; + for(auto i = 0; i < nb_bus; ++i) std::cout << rhs[i] << ", "; + std::cout << std::endl; + } + + // solve the linear system + _linear_solver.solve(dcYbus_noslack_, rhs, true); // I don't need to refactorize the matrix (hence the `true`) + + if (line_id == 18){ + std::cout << "res for 18\n"; + RealVect res = dcYbus_noslack_ * rhs; + for(auto i = 0; i < nb_bus; ++i) std::cout << res[i] << ", "; + std::cout << std::endl; + } + + // assign results to the PTDF matrix + PTDF(line_id, ind_no_slack) = rhs; + + // reset the rhs vector to 0. + rhs.array() = 0.; + // rhs = RealVect::Zero(Bf_T_with_slack.cols() - slack_buses_ids_solver_.size()); + } + // TODO PTDF: if the solver can solve the directly, do that instead + return PTDF; } template diff --git a/src/DataDCLine.cpp b/src/DataDCLine.cpp index 977f9613..574f7cd9 100644 --- a/src/DataDCLine.cpp +++ b/src/DataDCLine.cpp @@ -54,8 +54,8 @@ void DataDCLine::init(const Eigen::VectorXi & branch_from_id, from_gen_.init(p_mw, vm_or_pu, min_q_or, max_q_or, branch_from_id); RealVect p_ex = p_mw; - unsigned int size_ = p_mw.size(); - for(unsigned int i = 0; i < size_; ++i){ + Eigen::Index size_ = p_mw.size(); + for(Eigen::Index i = 0; i < size_; ++i){ p_ex(i) = get_to_mw(i, p_ex(i)); } to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id); @@ -63,10 +63,10 @@ void DataDCLine::init(const Eigen::VectorXi & branch_from_id, void DataDCLine::nb_line_end(std::vector & res) const { - auto nb = from_gen_.nb(); + const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); const auto & bus_ex_id = get_bus_id_ex(); - for(unsigned int i = 0; i < nb; ++i){ + for(Eigen::Index i = 0; i < nb; ++i){ if(!status_[i]) continue; auto bus_or = bus_or_id(i); auto bus_ex = bus_ex_id(i); @@ -78,10 +78,10 @@ void DataDCLine::nb_line_end(std::vector & res) const // TODO DC LINE: one side might be in the connected comp and not the other ! void DataDCLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { - auto nb = from_gen_.nb(); + const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); const auto & bus_ex_id = get_bus_id_ex(); - for(unsigned int i = 0; i < nb; ++i){ + for(Eigen::Index i = 0; i < nb; ++i){ if(!status_[i]) continue; auto bus_or = bus_or_id(i); auto bus_ex = bus_ex_id(i); diff --git a/src/DataGeneric.h b/src/DataGeneric.h index 9ccdc59d..18d1a807 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -81,7 +81,8 @@ class DataGeneric : public BaseConstants virtual void fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, - int nb_line) const {}; + int nb_line, + bool transpose) const {}; virtual void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) {}; virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {}; diff --git a/src/DataLine.cpp b/src/DataLine.cpp index ee833cfa..acccf460 100644 --- a/src/DataLine.cpp +++ b/src/DataLine.cpp @@ -292,9 +292,10 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, void DataLine::fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, - int nb_line) const + int nb_powerline, + bool transpose) const { - const Eigen::Index nb_line = static_cast(powerlines_r_.size()); + const Eigen::Index nb_line = powerlines_r_.size(); for(Eigen::Index line_id=0; line_id < nb_line; ++line_id){ // i only add this if the powerline is connected @@ -324,9 +325,13 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, // TODO // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) // and -1. / x / tap for (1..nb_branch, to_bus) - - Bf.push_back(Eigen::Triplet (line_id, bus_or_id_me, 1. / x)); - Bf.push_back(Eigen::Triplet (line_id, bus_ex_solver_id, -1. / x)); + if(transpose){ + Bf.push_back(Eigen::Triplet (bus_or_solver_id, line_id, 1. / x)); + Bf.push_back(Eigen::Triplet (bus_ex_solver_id, line_id, -1. / x)); + }else{ + Bf.push_back(Eigen::Triplet (line_id, bus_or_solver_id, 1. / x)); + Bf.push_back(Eigen::Triplet (line_id, bus_ex_solver_id, -1. / x)); + } } } @@ -493,8 +498,8 @@ void DataLine::get_graph(std::vector > & res) const void DataLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { - auto nb_line = nb(); - for(unsigned int i = 0; i < nb_line; ++i){ + const Eigen::Index nb_line = nb(); + for(Eigen::Index i = 0; i < nb_line; ++i){ if(!status_[i]) continue; auto bus_or = bus_or_id_(i); auto bus_ex = bus_ex_id_(i); diff --git a/src/DataLine.h b/src/DataLine.h index 348d5701..a046dd0b 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -207,7 +207,8 @@ class DataLine : public DataGeneric virtual void fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, - int nb_line) const; + int nb_powerline, + bool transpose) const; virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp index 5c80d948..a526eaee 100644 --- a/src/DataTrafo.cpp +++ b/src/DataTrafo.cpp @@ -462,11 +462,12 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, - int nb_line) const + int nb_powerline, + bool transpose) const { - const Eigen::Index nb_line = static_cast(r_.size()); + const Eigen::Index nb_trafo = r_.size(); - for(Eigen::Index tr_id=0; tr_id < nb_line; ++tr_id){ + for(Eigen::Index tr_id=0; tr_id < nb_trafo; ++tr_id){ // i only add this if the powerline is connected if(!status_[tr_id]) continue; @@ -495,8 +496,13 @@ void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, // TODO // Bf (nb_branch, nb_bus) : en dc un truc du genre 1 / x / tap for (1..nb_branch, from_bus) // and -1. / x / tap for (1..nb_branch, to_bus) - Bf.push_back(Eigen::Triplet (tr_id + nb_line, bus_or_id_me, 1. / x * _1_tau)); - Bf.push_back(Eigen::Triplet (tr_id + nb_line, bus_ex_solver_id, -1. / x * _1_tau)); + if(transpose){ + Bf.push_back(Eigen::Triplet (bus_or_solver_id, tr_id + nb_powerline, 1. / x * _1_tau)); + Bf.push_back(Eigen::Triplet (bus_ex_solver_id, tr_id + nb_powerline, -1. / x * _1_tau)); + }else{ + Bf.push_back(Eigen::Triplet (tr_id + nb_powerline, bus_or_solver_id, 1. / x * _1_tau)); + Bf.push_back(Eigen::Triplet (tr_id + nb_powerline, bus_ex_solver_id, -1. / x * _1_tau)); + } } } @@ -561,8 +567,8 @@ void DataTrafo::get_graph(std::vector > & res) const void DataTrafo::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { - auto nb_line = nb(); - for(unsigned int i = 0; i < nb_line; ++i){ + const Eigen::Index nb_line = nb(); + for(Eigen::Index i = 0; i < nb_line; ++i){ if(!status_[i]) continue; auto bus_or = bus_hv_id_(i); auto bus_ex = bus_lv_id_(i); diff --git a/src/DataTrafo.h b/src/DataTrafo.h index ea168389..62279793 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -199,7 +199,8 @@ class DataTrafo : public DataGeneric virtual void fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, - int nb_line) const; + int nb_powerline, + bool transpose) const; virtual void hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver); // needed for dc mode void compute_results(const Eigen::Ref & Va, diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 3cedd6e6..1152da97 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -262,6 +262,7 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) } Sbus_ = CplxVect(); + dcSbus_ = CplxVect(); bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); slack_weights_ = RealVect(); @@ -436,7 +437,8 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); fillYbus(Ybus, is_ac, id_me_to_solver); } - init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); + auto & this_Sbus = is_ac ? Sbus_ : dcSbus_; + init_Sbus(this_Sbus, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver); // TODO what if pv and pq changed ? :O int nb_bus_total = static_cast(bus_vn_kv_.size()); @@ -445,7 +447,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0); generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - fillSbus_me(Sbus_, is_ac, id_me_to_solver); + fillSbus_me(this_Sbus, is_ac, id_me_to_solver); const int nb_bus_solver = static_cast(id_solver_to_me.size()); CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_); @@ -660,7 +662,7 @@ void GridModel::compute_results(bool ac){ // to split among the contributing generators) so it's possible to "mess with" Sbus // for such purpose const auto id_slack = slack_bus_id_dc_solver_(0); - active_mismatch(id_slack) = -Sbus_.real().sum() * sn_mva_; + active_mismatch(id_slack) = -dcSbus_.real().sum() * sn_mva_; } generators_.set_p_slack(active_mismatch, id_me_to_solver); @@ -713,18 +715,27 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, // start the solver slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); - conv = _dc_solver.compute_pf(Ybus_dc_, V, Sbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); + conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); // store results (fase -> because I am in dc mode) process_results(conv, res, Vinit, false, id_me_to_dc_solver_); return res; } -Eigen::SparseMatrix GridModel::get_ptdf(){ +RealMat GridModel::get_ptdf(){ if(Ybus_dc_.size() == 0){ - throw std::runtime_error("Cannot get the ptdf without having first computed a dc powerflow."); + throw std::runtime_error("GridModel::get_ptdf: Cannot get the ptdf without having first computed a DC powerflow."); } - return _dc_solver.get_ptdf(); + return _dc_solver.get_ptdf(Ybus_dc_); +} + +Eigen::SparseMatrix GridModel::get_Bf(){ + if(Ybus_dc_.size() == 0){ + throw std::runtime_error("GridModel::get_Bf: Cannot get the Bf matrix without having first computed a DC powerflow."); + } + Eigen::SparseMatrix Bf; + fillBf_for_PTDF(Bf); + return Bf; } /** @@ -923,23 +934,30 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp, } -void GridModel::fillBf_for_PTDF(Eigen::SparseMatrix & Bf) const +void GridModel::fillBf_for_PTDF(Eigen::SparseMatrix & Bf, bool transpose) const { - const int nb_bus_solver = static_cast(id_ac_solver_to_me_.size()); + const int nb_bus_solver = static_cast(id_me_to_dc_solver_.size()); + // TODO DEBUG MODE + if(nb_bus_solver == 0) throw std::runtime_error("GridModel::fillBf_for_PTDF: it appears no DC powerflow has run on your grid."); + // TODO PTDF: nb_line, nb_bus or nb_branch, nb_bus ??? - // TODO PTDF: if we don't have nb_branch we need a converter line_id => branch id - Bf = Eigen::SparseMatrix(powerlines_.nb() + trafos_.nb(), nb_bus_solver); + // TODO PTDF: if we don't have nb_branch we need a converter line_id => branch + if(transpose){ + Bf = Eigen::SparseMatrix(nb_bus_solver, powerlines_.nb() + trafos_.nb()); + }else{ + Bf = Eigen::SparseMatrix(powerlines_.nb() + trafos_.nb(), nb_bus_solver); + } std::vector > tripletList; tripletList.reserve(bus_vn_kv_.size() + 2 * powerlines_.nb() + 2 * trafos_.nb()); - powerlines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); // TODO have a function to dispatch that to all type of elements - shunts_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - trafos_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - loads_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - sgens_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - storages_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - generators_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); - dc_lines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb()); + powerlines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); // TODO have a function to dispatch that to all type of elements + shunts_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + trafos_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + loads_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + sgens_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + storages_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + generators_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); + dc_lines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); Bf.setFromTriplets(tripletList.begin(), tripletList.end()); Bf.makeCompressed(); diff --git a/src/GridModel.h b/src/GridModel.h index a7bb1004..02e1940e 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -267,7 +267,8 @@ class GridModel : public DataGeneric int max_iter, // not used for DC real_type tol // not used for DC ); - Eigen::SparseMatrix get_ptdf(); + RealMat get_ptdf(); + Eigen::SparseMatrix get_Bf(); // ac powerflow CplxVect ac_pf(const CplxVect & Vinit, @@ -444,6 +445,9 @@ class GridModel : public DataGeneric Eigen::Ref get_Sbus() const{ return Sbus_; } + Eigen::Ref get_dcSbus() const{ + return dcSbus_; + } Eigen::Ref get_pv() const{ return bus_pv_; } @@ -568,7 +572,7 @@ class GridModel : public DataGeneric Eigen::SparseMatrix & Bpp, FDPFMethod xb_or_bx) const; - void fillBf_for_PTDF(Eigen::SparseMatrix & Bf) const; + void fillBf_for_PTDF(Eigen::SparseMatrix & Bf, bool transpose=false) const; Eigen::SparseMatrix debug_get_Bp_python(FDPFMethod xb_or_bx){ Eigen::SparseMatrix Bp; @@ -765,6 +769,7 @@ class GridModel : public DataGeneric Eigen::SparseMatrix Ybus_ac_; Eigen::SparseMatrix Ybus_dc_; CplxVect Sbus_; + CplxVect dcSbus_; Eigen::VectorXi bus_pv_; // id are the solver internal id and NOT the initial id Eigen::VectorXi bus_pq_; // id are the solver internal id and NOT the initial id diff --git a/src/Utils.h b/src/Utils.h index f8a6d021..49184ce3 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -33,6 +33,9 @@ typedef Eigen::Matrix IntVect; typedef Eigen::Matrix RealVect; typedef Eigen::Matrix CplxVect; +typedef Eigen::Matrix RealMat; +typedef Eigen::Matrix CplxMat; + // type of error in the different solvers enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError}; diff --git a/src/main.cpp b/src/main.cpp index a8f29e29..2dea615f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -762,6 +762,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_Ybus", &GridModel::get_Ybus, DocGridModel::get_Ybus.c_str()) .def("get_dcYbus", &GridModel::get_dcYbus, DocGridModel::get_dcYbus.c_str()) .def("get_Sbus", &GridModel::get_Sbus, DocGridModel::get_Sbus.c_str()) + .def("get_dcSbus", &GridModel::get_dcSbus, DocGridModel::_internal_do_not_use.c_str()) .def("check_solution", &GridModel::check_solution, DocGridModel::check_solution.c_str()) @@ -801,6 +802,8 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("unset_topo_changed", &GridModel::unset_topo_changed, DocGridModel::_internal_do_not_use.c_str()) .def("tell_topo_changed", &GridModel::tell_topo_changed, DocGridModel::_internal_do_not_use.c_str()) .def("compute_newton", &GridModel::ac_pf, DocGridModel::ac_pf.c_str()) + .def("get_ptdf", &GridModel::get_ptdf, DocGridModel::_internal_do_not_use.c_str()) // TODO PTDF + .def("get_Bf", &GridModel::get_Bf, DocGridModel::_internal_do_not_use.c_str()) // TODO PTDF // apply action faster (optimized for grid2op representation) // it is not recommended to use it outside of grid2Op. From 14d62bdbdc7234020a3bcc4036b66581b8d8fe5d Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 7 Dec 2023 11:38:04 +0100 Subject: [PATCH 28/66] implement and test computation of ptdf --- lightsim2grid/tests/test_ptdf.py | 106 +++++++++++++++++++++++++++++++ src/DCSolver.h | 10 ++- src/DCSolver.tpp | 52 +++++---------- 3 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 lightsim2grid/tests/test_ptdf.py diff --git a/lightsim2grid/tests/test_ptdf.py b/lightsim2grid/tests/test_ptdf.py new file mode 100644 index 00000000..92ad48cb --- /dev/null +++ b/lightsim2grid/tests/test_ptdf.py @@ -0,0 +1,106 @@ +# Copyright (c) 2023, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +import unittest +import pandapower as pp +import pandapower.networks as pn +import numpy as np +import warnings +from scipy.sparse.linalg import spsolve + +from lightsim2grid.gridmodel import init +from lightsim2grid.solver import SolverType + +import pdb + +class TestCase14SLU(unittest.TestCase): + def make_grid(self): + case14 = pn.case14() + return case14 + + def get_solver_type(self): + return SolverType.DC + + def setUp(self) -> None: + self.case = self.make_grid() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.gridmodel = init(self.case) + self.V_init = 1. * self.gridmodel.get_bus_vn_kv() + self.gridmodel.change_solver(self.get_solver_type()) + self.gridmodel.dc_pf(self.V_init, 1, 1e-8) + self.dcYbus = 1.0 * self.gridmodel.get_dcYbus() + self.dcSbus = 1.0 * self.gridmodel.get_dcSbus().real + self.Bbus = 1.0 * self.dcYbus.real + self.res_powerflow = 1.0 * np.concatenate((self.gridmodel.get_lineor_res()[0], self.gridmodel.get_trafohv_res()[0])) + self.nb = self.case.bus.shape[0] + self.nbr = self.case.line.shape[0] + self.case.trafo.shape[0] + self.slack_bus = self.case.ext_grid.iloc[0]["bus"] + self.noref = np.arange(1, self.nb) ## use bus 1 for voltage angle reference + self.noslack = np.flatnonzero(np.arange(self.nb) != self.slack_bus) + self.tol = 1e-6 + return super().setUp() + + def test_from_pp(self): + # test the right computation of Bf matrix (PTDF derived from python, might be slower) + Bf = 1.0 * self.gridmodel.get_Bf() + PTDF = np.zeros((self.nbr, self.nb)) + PTDF[:, self.noslack] = spsolve(self.Bbus[np.ix_(self.noslack, self.noref)].T, Bf[:, self.noref].toarray().T).T + # test the solver works correctly + tmp_mat = self.Bbus[np.ix_(self.noslack, self.noref)].T.todense() + for line_id in range(self.nbr): + solve_error = np.abs(np.dot(tmp_mat, PTDF[line_id, self.noslack]).T - Bf[line_id, self.noref].toarray().T).max() + assert solve_error <= self.tol, f"error for line {line_id}: {solve_error:.2e}MW" + # test powerflow are correct + res_ptdf = np.dot(PTDF, self.dcSbus * self.gridmodel.get_sn_mva()) + assert np.abs(res_ptdf - self.res_powerflow).max() <= self.tol, f"max error for powerflow: {np.abs(res_ptdf - self.res_powerflow).max():.2e}MW" + + def test_ptdf_from_ls(self): + # now test the right computation of the PTDF + Bf = 1.0 * self.gridmodel.get_Bf() + PTDF2 = 1.0 * self.gridmodel.get_ptdf() + # test the solver works correctly + tmp_mat = self.Bbus[np.ix_(self.noslack, self.noref)].T.todense() + for line_id in range(self.nbr): + solve_error = np.abs(np.dot(tmp_mat, PTDF2[line_id, self.noslack]).T - Bf[line_id, self.noref].toarray().T).max() + assert solve_error <= self.tol, f"error for line {line_id}: {solve_error:.2e}MW" + # test powerflow are correct + res_ptdf2 = np.dot(PTDF2, self.dcSbus * self.gridmodel.get_sn_mva()) + np.where(np.abs(res_ptdf2 - self.res_powerflow) >= 1e3) + assert np.abs(res_ptdf2 - self.res_powerflow).max() <= self.tol, f"max error for powerflow: {np.abs(res_ptdf2 - self.res_powerflow).max():.2e}MW" + + +class TestCase30SLU(TestCase14SLU): + def make_grid(self): + res = pn.case30() + return res + + +class TestCase118SLU(TestCase14SLU): + def make_grid(self): + res = pn.case118() + return res + + +class TestCase14KLU(TestCase14SLU): + def get_solver_type(self): + return SolverType.KLUDC + + +class TestCase30KLU(TestCase30SLU): + def get_solver_type(self): + return SolverType.KLUDC + + +class TestCase118KLU(TestCase118SLU): + def get_solver_type(self): + return SolverType.KLUDC + + +if __name__ == "__main__": + unittest.main() diff --git a/src/DCSolver.h b/src/DCSolver.h index 38c110de..eeb14a24 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -15,7 +15,12 @@ template class BaseDCSolver: public BaseSolver { public: - BaseDCSolver():BaseSolver(false), _linear_solver(), need_factorize_(true), nb_dcbus_solver_(0){}; + BaseDCSolver(): + BaseSolver(false), + _linear_solver(), + need_factorize_(true), + sizeYbus_with_slack_(0), + sizeYbus_without_slack_(0){}; ~BaseDCSolver(){} @@ -56,7 +61,8 @@ class BaseDCSolver: public BaseSolver bool need_factorize_; // save this not to recompute them when not needed - int nb_dcbus_solver_; + int sizeYbus_with_slack_; + int sizeYbus_without_slack_; RealVect dcSbus_noslack_; Eigen::SparseMatrix dcYbus_noslack_; Eigen::VectorXi my_pv_; diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index 1e65858a..fee67b2f 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -28,7 +28,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix auto timer = CustTimer(); BaseSolver::reset_timer(); - nb_dcbus_solver_ = static_cast(Ybus.rows()); + sizeYbus_with_slack_ = static_cast(Ybus.rows()); #ifdef __COUT_TIMES auto timer_preproc = CustTimer(); @@ -43,14 +43,15 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // const Eigen::VectorXi & my_pv = pv; // find the slack buses - slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, nb_dcbus_solver_); + slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); + sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); // corresp bus -> solverbus - fill_mat_bus_id(nb_dcbus_solver_); + fill_mat_bus_id(sizeYbus_with_slack_); // remove the slack bus from Ybus // and extract only real part - fill_dcYbus_noslack(nb_dcbus_solver_, Ybus); + fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); #ifdef __COUT_TIMES std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl; @@ -72,8 +73,8 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } // remove the slack bus from Sbus - dcSbus_noslack_ = RealVect::Constant(nb_dcbus_solver_ - slack_buses_ids_solver_.size(), my_zero_); - for (int k=0; k < nb_dcbus_solver_; ++k){ + dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); + for (int k=0; k < sizeYbus_with_slack_; ++k){ if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus const int col_res = mat_bus_id_(k); dcSbus_noslack_(col_res) = std::real(Sbus_tmp(k)); @@ -108,9 +109,9 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // retrieve back the results in the proper shape (add back the slack bus) // TODO have a better way for this, for example using `.segment(0,npv)` // see the BaseSolver.cpp: _evaluate_Fx - RealVect Va_dc = RealVect::Constant(nb_dcbus_solver_, my_zero_); + RealVect Va_dc = RealVect::Constant(sizeYbus_with_slack_, my_zero_); // fill Va from dc approx - for (int ybus_id=0; ybus_id < nb_dcbus_solver_; ++ybus_id){ + for (int ybus_id=0; ybus_id < sizeYbus_with_slack_; ++ybus_id){ if(mat_bus_id_(ybus_id) == -1) continue; // slack bus is handled elsewhere const int bus_me = mat_bus_id_(ybus_id); Va_dc(ybus_id) = Va_dc_without_slack(bus_me); @@ -161,7 +162,7 @@ void BaseDCSolver::fill_dcYbus_noslack(int nb_bus_solver, const Ei template template // ref_mat_type should be `real_type` or `cplx_type` void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ - res_mat = Eigen::SparseMatrix(nb_bus_solver - slack_buses_ids_solver_.size(), nb_bus_solver - slack_buses_ids_solver_.size()); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? + res_mat = Eigen::SparseMatrix(sizeYbus_without_slack_, sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? std::vector > tripletList; tripletList.reserve(ref_mat.nonZeros()); for (int k=0; k < nb_bus_solver; ++k){ @@ -185,7 +186,8 @@ void BaseDCSolver::reset(){ BaseSolver::reset(); _linear_solver.reset(); need_factorize_ = true; - nb_dcbus_solver_ = 0; + sizeYbus_with_slack_ = 0; + sizeYbus_without_slack_ = 0; dcSbus_noslack_ = RealVect(); dcYbus_noslack_ = Eigen::SparseMatrix(); my_pv_ = Eigen::VectorXi(); @@ -197,7 +199,7 @@ template RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix & dcYbus){ Eigen::SparseMatrix Bf_T_with_slack; RealMat PTDF; - RealVect rhs; + RealVect rhs = RealVect::Zero(sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? // TODO PTDF: sparse matrix ? // TODO PTDF: distributed slack // TODO PTDF: check that the solver has converged @@ -218,8 +220,7 @@ RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix::InnerIterator it(Bf_T_with_slack, line_id); it; ++it) @@ -229,38 +230,15 @@ RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix Date: Thu, 7 Dec 2023 11:38:42 +0100 Subject: [PATCH 29/66] accelerate computation of FDPF by not refactoring uselessly the Bp and Bpp matrices each time --- src/BaseFDPFSolver.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/BaseFDPFSolver.h b/src/BaseFDPFSolver.h index 4fa95e20..f670eeac 100644 --- a/src/BaseFDPFSolver.h +++ b/src/BaseFDPFSolver.h @@ -121,7 +121,8 @@ class BaseFDPFSolver : public BaseSolver RealVect & b, bool has_just_been_inialized){ auto timer = CustTimer(); - const ErrorType solve_status = linear_solver.solve(mat, b, has_just_been_inialized); + // const ErrorType solve_status = linear_solver.solve(mat, b, has_just_been_inialized); + const ErrorType solve_status = linear_solver.solve(mat, b, true); // true because i don't need to refactorize the matrix if(solve_status != ErrorType::NoError){ // std::cout << "solve error: " << solve_status << std::endl; err_ = solve_status; From 2ad2c9d7442991e15a039e14f40b3c41fb3e5dc6 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 7 Dec 2023 15:13:46 +0100 Subject: [PATCH 30/66] fixing broken tests --- .gitignore | 3 +++ CHANGELOG.rst | 4 ++++ lightsim2grid/tests/test_SameResPP.py | 4 ++-- lightsim2grid/tests/test_init_from_pypowsybl.py | 8 ++++---- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 4558f40f..e4e37371 100644 --- a/.gitignore +++ b/.gitignore @@ -275,3 +275,6 @@ test.txt test_output.txt test_pypower_fdpf.py lightsim2grid/tests/_grid2op_for_test/ +bug_sparselu +bug_sparselu_eigen.cpp +test_segfault.sh diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4629d1db..56c4a883 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,9 @@ Change Log [0.7.6] 2023-xx-yy -------------------- +- [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`). + If you previously used `gridmodel.get_Subus()` to retrieve the Sbus used for DC powerflow, please use + `gridmodel.get_dcSbus()` instead. - [FIXED] now voltage is properly set to 0. when shunts are disconnected - [FIXED] now voltage is properly set to 0. when storage units are disconnected - [FIXED] a bug where non connected grid were not spotted in DC @@ -31,6 +34,7 @@ Change Log one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. - [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side) +- [ADDED] computation of PTPF (Power Transfer Distribution Factor) is now possible - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` diff --git a/lightsim2grid/tests/test_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py index 43d746c5..9e7e1f78 100644 --- a/lightsim2grid/tests/test_SameResPP.py +++ b/lightsim2grid/tests/test_SameResPP.py @@ -219,8 +219,8 @@ def _aux_test(self, pn_net): Vdc = backend._grid.dc_pf(Vinit, max_iter, tol_this) backend._grid.reactivate_result_computation() backend._grid.tell_topo_changed() - Ydc_me = copy.deepcopy(backend._grid.get_Ybus()) - Sdc_me = copy.deepcopy(backend._grid.get_Sbus()) + Ydc_me = copy.deepcopy(backend._grid.get_dcYbus()) + Sdc_me = copy.deepcopy(backend._grid.get_dcSbus()) assert np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])) <= 100.*self.tol,\ f"\t Error for the DC approximation: resulting voltages are different " \ f"{np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])):.5f}pu" diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index db64eace..7e008be7 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -125,12 +125,12 @@ def test_compare_pp(self): mat_pp = self.ref_samecase.get_dcYbus().todense() assert max_ <= self.tol_eq, f"error for dcYbus: {max_:.2e}" # check Sbus without slack - Sbus_ordered = self.gridmodel.get_Sbus()[reorder].reshape(-1) + Sbus_ordered = self.gridmodel.get_dcSbus()[reorder].reshape(-1) if slack_id > 0: - max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() + max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_dcSbus()[:slack_id]).max() assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}" - if slack_id != self.gridmodel.get_Sbus().shape[0] - 1: - max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() + if slack_id != self.gridmodel.get_dcSbus().shape[0] - 1: + max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_dcSbus()[(slack_id+1):]).max() assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}" # same in AC From 835df5a934ed2cb8628a0dbc0b7ba29a24475217 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Dec 2023 08:59:03 +0100 Subject: [PATCH 31/66] fix broken tests: KLU not compiled in the windows CI --- lightsim2grid/tests/test_ptdf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lightsim2grid/tests/test_ptdf.py b/lightsim2grid/tests/test_ptdf.py index 92ad48cb..8bfb6da0 100644 --- a/lightsim2grid/tests/test_ptdf.py +++ b/lightsim2grid/tests/test_ptdf.py @@ -32,7 +32,10 @@ def setUp(self) -> None: warnings.filterwarnings("ignore") self.gridmodel = init(self.case) self.V_init = 1. * self.gridmodel.get_bus_vn_kv() - self.gridmodel.change_solver(self.get_solver_type()) + solver_type = self.get_solver_type() + if solver_type not in self.gridmodel.available_solvers(): + self.skipTest("Solver type not supported on this platform") + self.gridmodel.change_solver(solver_type) self.gridmodel.dc_pf(self.V_init, 1, 1e-8) self.dcYbus = 1.0 * self.gridmodel.get_dcYbus() self.dcSbus = 1.0 * self.gridmodel.get_dcSbus().real From c5c85bb15aebe6710e9c29edd984344d319679c1 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Dec 2023 10:38:58 +0100 Subject: [PATCH 32/66] now it compiles, need to check tests, and implement logic solver side [skip ci] --- src/BaseSolver.cpp | 6 +++++- src/BaseSolver.h | 14 ++++++-------- src/ChooseSolver.h | 9 ++++++++- src/DataGen.cpp | 6 +++--- src/DataGen.h | 31 +++++++++++++++++++++---------- src/GridModel.cpp | 29 +++++++++++++++-------------- src/GridModel.h | 6 ++++-- src/Utils.h | 11 +++++++++-- src/main.cpp | 2 +- 9 files changed, 72 insertions(+), 42 deletions(-) diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index e5c0bc73..c2df65e0 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -9,7 +9,8 @@ #include "BaseSolver.h" #include "GridModel.h" // needs to be included here because of the forward declaration -void BaseSolver::reset(const SolverControl & solver_control){ + +void BaseSolver::reset(){ // reset timers reset_timer(); @@ -20,8 +21,11 @@ void BaseSolver::reset(const SolverControl & solver_control){ V_ = RealVect(); // voltage angle // TODO solver control: see if I could reuse some of these nr_iter_ = 0; // number of iteration performs by the algorithm err_ = ErrorType::NotInitError; //error message: + + _solver_control = SolverControl(); } + RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const CplxVect & Sbus, diff --git a/src/BaseSolver.h b/src/BaseSolver.h index 8b8006d3..deeaaa11 100644 --- a/src/BaseSolver.h +++ b/src/BaseSolver.h @@ -98,10 +98,10 @@ class BaseSolver : public BaseConstants real_type tol ) = 0 ; -<<<<<<< HEAD - virtual - void reset(const SolverControl & solver_control); -======= + void tell_solver_control(const SolverControl & solver_control){ + _solver_control = solver_control; + } + virtual void reset(); virtual RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus){ throw std::runtime_error("Impossible to get the PTDF matrix with this solver type."); } @@ -111,9 +111,6 @@ class BaseSolver : public BaseConstants virtual Eigen::SparseMatrix get_bsdf(){ // TODO interface is likely to change throw std::runtime_error("Impossible to get the BSDF matrix with this solver type."); } - - virtual void reset(); ->>>>>>> bd-dev protected: virtual void reset_timer(){ @@ -232,7 +229,8 @@ class BaseSolver : public BaseConstants double timer_total_nr_; const GridModel * _gridmodel; // does not have ownership so that's fine (pointer to the base gridmodel, can be used for some powerflow) - + SolverControl _solver_control; + private: // no copy allowed BaseSolver( const BaseSolver & ) ; diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h index 4c713b69..08fb0cd1 100644 --- a/src/ChooseSolver.h +++ b/src/ChooseSolver.h @@ -173,10 +173,12 @@ class ChooseSolver #endif // now switch the union (see https://en.cppreference.com/w/cpp/language/union) + // reset the old solver reset(); - // and assign the right solver _solver_type = type; + // and now reset the new one + reset(); } void reset() @@ -275,6 +277,11 @@ class ChooseSolver return res; } + void tell_solver_control(const SolverControl & solver_control){ + auto p_solver = get_prt_solver("get_ptdf", true); + p_solver -> tell_solver_control(solver_control); + } + /** apparently i cannot pass a const ref for a sparse matrix in python**/ Eigen::SparseMatrix get_J_python() const{ Eigen::SparseMatrix res = get_J(); diff --git a/src/DataGen.cpp b/src/DataGen.cpp index 699620be..bc60fd2e 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -438,16 +438,16 @@ void DataGen::update_slack_weights(Eigen::Ref 0.){ // gen is properly connected if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was not in the slack before, so I need to reset the solver - add_slackbus(gen_id, p_mw_(gen_id)); + add_slackbus(gen_id, p_mw_(gen_id), solver_control); }else{ // gen is now "turned off" if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was in the slack before, so I need to reset the solver - remove_slackbus(gen_id); + remove_slackbus(gen_id, solver_control); } }else{ if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was in the slack before, I need to reset the solver - remove_slackbus(gen_id); + remove_slackbus(gen_id, solver_control); } } } diff --git a/src/DataGen.h b/src/DataGen.h index 3b90e2a8..e2fb9cbe 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -174,25 +174,29 @@ class DataGen: public DataGeneric we suppose that the data are correct (ie gen_id in the proper range, and weight > 0.) This is checked in GridModel, and not at this stage **/ - void add_slackbus(int gen_id, real_type weight){ - gen_slackbus_[gen_id] = true; - gen_slack_weight_[gen_id] = weight; + void add_slackbus(int gen_id, real_type weight, SolverControl & solver_control){ // TODO DEBUG MODE if(weight <= 0.) throw std::runtime_error("DataGen::add_slackbus Cannot assign a negative weight to the slack bus."); + if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); + gen_slackbus_[gen_id] = true; + if(gen_slack_weight_[gen_id] != weight) solver_control.tell_slack_weight_changed(); + gen_slack_weight_[gen_id] = weight; } - void remove_slackbus(int gen_id){ + void remove_slackbus(int gen_id, SolverControl & solver_control){ + if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); gen_slackbus_[gen_id] = false; gen_slack_weight_[gen_id] = 0.; } void remove_all_slackbus(){ const int nb_gen = nb(); + SolverControl unused_solver_control; for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { - remove_slackbus(gen_id); + remove_slackbus(gen_id, unused_solver_control); } } // returns only the gen_id with the highest p that is connected to this bus ! - int assign_slack_bus(int slack_bus_id, const std::vector & gen_p_per_bus){ + int assign_slack_bus(int slack_bus_id, const std::vector & gen_p_per_bus, SolverControl & solver_control){ const int nb_gen = nb(); int res_gen_id = -1; real_type max_p = -1.; @@ -201,7 +205,7 @@ class DataGen: public DataGeneric if(!status_[gen_id]) continue; if(bus_id_(gen_id) != slack_bus_id) continue; const real_type p_mw = p_mw_(gen_id); - add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id]); + add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id], solver_control); if((p_mw > max_p) || (res_gen_id == -1) ){ res_gen_id = gen_id; max_p = p_mw; @@ -224,14 +228,18 @@ class DataGen: public DataGeneric void turnedoff_no_pv(){turnedoff_gen_pv_=false;} // turned off generators are not pv void turnedoff_pv(){turnedoff_gen_pv_=true;} // turned off generators are pv bool get_turnedoff_gen_pv() const {return turnedoff_gen_pv_;} - void update_slack_weights(Eigen::Ref > could_be_slack, SolverControl & solver_control); + void update_slack_weights(Eigen::Ref > could_be_slack, + SolverControl & solver_control); void deactivate(int gen_id, SolverControl & solver_control) { if (status_[gen_id]){ solver_control.tell_recompute_sbus(); if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); - if(gen_slack_weight_[gen_id]) solver_control.tell_slack_participate_changed(); + if(gen_slack_weight_[gen_id]){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + } } _deactivate(gen_id, status_); } @@ -240,7 +248,10 @@ class DataGen: public DataGeneric solver_control.tell_recompute_sbus(); if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); - if(gen_slack_weight_[gen_id]) solver_control.tell_slack_participate_changed(); + if(gen_slack_weight_[gen_id]){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + } } _reactivate(gen_id, status_); } diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 58ef427d..2aee652e 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -274,7 +274,6 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) _solver.set_gridmodel(this); _dc_solver.set_gridmodel(this); } - // std::cout << "GridModel::reset called" << std::endl; } CplxVect GridModel::ac_pf(const CplxVect & Vinit, @@ -415,15 +414,16 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, const SolverControl & solver_control) { // TODO get rid of the "is_ac" argument: this info is available in the _solver already - if(is_ac) _solver.reset(solver_control); - else _dc_solver.reset(solver_control); - slack_bus_id_ = generators_.get_slack_bus_id(); + if(is_ac) _solver.tell_solver_control(solver_control); + else _dc_solver.tell_solver_control(solver_control); + + if (solver_control.has_slack_participate_changed()) slack_bus_id_ = generators_.get_slack_bus_id(); if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed()) init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed() || solver_control.need_recompute_ybus()) fillYbus(Ybus, is_ac, id_me_to_solver); if (solver_control.has_dimension_changed()) init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); - fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); + if (solver_control.has_slack_participate_changed() || solver_control.has_pv_changed() || solver_control.has_pq_changed()) fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); - if (solver_control.need_recompute_sbus()){ + if (solver_control.has_dimension_changed() || solver_control.need_recompute_sbus() && is_ac){ int nb_bus_total = static_cast(bus_vn_kv_.size()); total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); @@ -477,7 +477,7 @@ void GridModel::process_results(bool conv, // compute the results of the flows, P,Q,V of loads etc. compute_results(ac); } - need_reset_ = false; + solver_control_.tell_none_changed(); const CplxVect & res_tmp = ac ? _solver.get_V(): _dc_solver.get_V() ; // convert back the results to "big" vector @@ -487,7 +487,7 @@ void GridModel::process_results(bool conv, } else { //powerflow diverge reset_results(); - need_reset_ = true; // in this case, the powerflow diverge, so i need to recompute Ybus next time + // TODO solver control ??? something to do here ? } } @@ -698,13 +698,14 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, // pre process the data to define a proper jacobian matrix, the proper voltage vector etc. bool is_ac = false; - bool reset_solver = topo_changed_; // I reset the solver only if the topology change CplxVect V = pre_process_solver(Vinit, Ybus_dc_, id_me_to_dc_solver_, id_dc_solver_to_me_, slack_bus_id_dc_solver_, - is_ac, reset_solver); + is_ac, solver_control_); // start the solver - slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); + if(solver_control_.has_slack_participate_changed() || + solver_control_.has_pv_changed() || + solver_control_.has_slack_weight_changed()) slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); // store results (fase -> because I am in dc mode) @@ -761,7 +762,7 @@ void GridModel::add_gen_slackbus(int gen_id, real_type weight){ exc_ << "GridModel::add_gen_slackbus: please enter a valid weight for the slack bus (> 0.)"; throw std::runtime_error(exc_.str()); } - generators_.add_slackbus(gen_id, weight); + generators_.add_slackbus(gen_id, weight, solver_control_); } void GridModel::remove_gen_slackbus(int gen_id){ @@ -781,7 +782,7 @@ void GridModel::remove_gen_slackbus(int gen_id){ exc_ << "Generator with id " << gen_id << " does not exist and can't be the slack bus"; throw std::runtime_error(exc_.str()); } - generators_.remove_slackbus(gen_id); + generators_.remove_slackbus(gen_id, solver_control_); } /** GRID2OP SPECIFIC REPRESENTATION **/ @@ -999,7 +1000,7 @@ std::tuple GridModel::assign_slack_to_most_connected(){ // and reset the slack bus generators_.remove_all_slackbus(); - res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus); + res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus, solver_control_); std::get<1>(res) = res_gen_id; slack_bus_id_ = std::vector(); slack_weights_ = RealVect(); diff --git a/src/GridModel.h b/src/GridModel.h index 271f9b13..82955344 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -704,7 +704,8 @@ class GridModel : public DataGeneric { (this->*fun_react)(el_id); // eg reactivate_load(load_id); (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); - topo_changed_ = true; + // topo_changed_ = true; + solver_control_.tell_dimension_changed(); } } else{ if(has_changed(el_pos)) @@ -714,7 +715,8 @@ class GridModel : public DataGeneric // bus_status_ is set to "false" in GridModel.update_topo // and a bus is activated if (and only if) one element is connected to it. // I must not set `bus_status_[new_bus_backend] = false;` in this case ! - topo_changed_ = true; + // topo_changed_ = true; + solver_control_.tell_dimension_changed(); } } } diff --git a/src/Utils.h b/src/Utils.h index 42a36172..0d56bc0d 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -64,6 +64,7 @@ class SolverControl need_recompute_sbus_(true), need_recompute_ybus_(true), v_changed_(true), + slack_weight_changed_(true), ybus_change_sparsity_pattern_(true) {}; @@ -76,6 +77,7 @@ class SolverControl need_recompute_sbus_ = true; need_recompute_ybus_ = true; v_changed_ = true; + slack_weight_changed_ = true; ybus_change_sparsity_pattern_ = true; } @@ -88,6 +90,7 @@ class SolverControl need_recompute_sbus_ = false; need_recompute_ybus_ = false; v_changed_ = false; + slack_weight_changed_ = false; ybus_change_sparsity_pattern_ = false; } @@ -109,16 +112,19 @@ class SolverControl void tell_ybus_change_sparsity_pattern(){ybus_change_sparsity_pattern_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. // tell at least one generator changed its v setpoint void tell_v_changed(){v_changed_ = true;} + // at least one generator has changed its slack participation + void tell_slack_weight_changed(){slack_weight_changed_ = true;} bool has_dimension_changed() const {return change_dimension_;} bool has_pv_changed() const {return pv_changed_;} bool has_pq_changed() const {return pq_changed_;} - bool has_tell_slack_participate_changed() const {return slack_participate_changed_;} + bool has_slack_participate_changed() const {return slack_participate_changed_;} bool need_reset_solver() const {return need_reset_solver_;} bool need_recompute_sbus() const {return need_recompute_sbus_;} bool need_recompute_ybus() const {return need_recompute_ybus_;} bool ybus_change_sparsity_pattern() const {return ybus_change_sparsity_pattern_;} - bool v_changed() const {return v_changed_;} + bool has_slack_weight_changed() const {return slack_weight_changed_;} + bool has_v_changed() const {return v_changed_;} protected: bool change_dimension_; @@ -129,6 +135,7 @@ class SolverControl bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern bool v_changed_; + bool slack_weight_changed_; bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff), or ybus change of dimension }; diff --git a/src/main.cpp b/src/main.cpp index 319d1e22..a743bbae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -598,7 +598,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("has_dimension_changed", &SolverControl::has_dimension_changed, "TODO") .def("has_pv_changed", &SolverControl::has_pv_changed, "TODO") .def("has_pq_changed", &SolverControl::has_pq_changed, "TODO") - .def("has_tell_slack_participate_changed", &SolverControl::has_tell_slack_participate_changed, "TODO") + .def("has_slack_participate_changed", &SolverControl::has_slack_participate_changed, "TODO") .def("need_reset_solver", &SolverControl::need_reset_solver, "TODO") .def("need_recompute_sbus", &SolverControl::need_recompute_sbus, "TODO") .def("need_recompute_ybus", &SolverControl::need_recompute_ybus, "TODO") From 8334ef7c61f700cc9c0469386f1ea69cbe7a5ad4 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Dec 2023 16:54:17 +0100 Subject: [PATCH 33/66] python calls updated, now need to solve the segfault [skip ci] --- lightsim2grid/lightSimBackend.py | 11 +++-- src/BaseSolver.cpp | 1 + src/ChooseSolver.cpp | 77 ++++++++++++++++++++++++++++++++ src/ChooseSolver.h | 16 ++++++- src/DataDCLine.cpp | 2 - src/GridModel.cpp | 69 +++++++++++++++++++++++----- src/GridModel.h | 4 +- 7 files changed, 159 insertions(+), 21 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index cf814596..a8c66c5d 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -735,7 +735,7 @@ def _aux_finish_setup_after_reading(self): self.storage_theta = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1) self._count_object_per_bus() - self._grid.tell_topo_changed() + self._grid.tell_solver_need_reset() self.__me_at_init = self._grid.copy() self.__init_topo_vect = np.ones(cls.dim_topo, dtype=dt_int) self.__init_topo_vect[:] = self.topo_vect @@ -894,7 +894,9 @@ def runpf(self, is_dc=False): self._grid.deactivate_result_computation() # if I init with dc values, it should depends on previous state self.V[:] = self._grid.get_init_vm_pu() # see issue 30 + print("before dc pf") Vdc = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it, self.tol) + print("after dc pf") self._grid.reactivate_result_computation() if Vdc.shape[0] == 0: raise BackendError(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}") @@ -903,6 +905,7 @@ def runpf(self, is_dc=False): V_init = copy.deepcopy(self.V) tick = time.perf_counter() self._timer_preproc += tick - beg_preproc + print("before ac pf") V = self._grid.ac_pf(V_init, self.max_it, self.tol) self._timer_solver += time.perf_counter() - tick if V.shape[0] == 0: @@ -968,11 +971,11 @@ def runpf(self, is_dc=False): if (self.line_or_theta >= 1e6).any() or (self.line_ex_theta >= 1e6).any(): raise BackendError(f"Some theta are above 1e6 which should not be happening !") res = True - self._grid.unset_topo_changed() + self._grid.unset_changes() self._timer_postproc += time.perf_counter() - beg_postroc except Exception as exc_: # of the powerflow has not converged, results are Nan - self._grid.tell_topo_changed() + self._grid.unset_changes() self._fill_nans() res = False my_exc_ = exc_ @@ -1190,7 +1193,7 @@ def get_current_solver_type(self): def reset(self, grid_path, grid_filename=None): self._fill_nans() self._grid = self.__me_at_init.copy() - self._grid.tell_topo_changed() + self._grid.unset_changes() self._grid.change_solver(self.__current_solver_type) self._handle_turnedoff_pv() self.topo_vect[:] = self.__init_topo_vect diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index c2df65e0..61b9d491 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -23,6 +23,7 @@ void BaseSolver::reset(){ err_ = ErrorType::NotInitError; //error message: _solver_control = SolverControl(); + _solver_control.tell_all_changed(); } diff --git a/src/ChooseSolver.cpp b/src/ChooseSolver.cpp index 165f7871..bfd87dd0 100644 --- a/src/ChooseSolver.cpp +++ b/src/ChooseSolver.cpp @@ -7,3 +7,80 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. #include "ChooseSolver.h" + +std::ostream& operator<<(std::ostream& out, const SolverType& solver_type) +{ + switch (solver_type) + { + case SolverType::SparseLU: + out << "SparseLU"; + break; + case SolverType::KLU: + out << "KLU"; + break; + case SolverType::GaussSeidel: + out << "GaussSeidel"; + break; + case SolverType::DC: + out << "DC"; + break; + case SolverType::GaussSeidelSynch: + out << "GaussSeidelSynch"; + break; + case SolverType::NICSLU: + out << "NICSLU"; + break; + case SolverType::SparseLUSingleSlack: + out << "SparseLUSingleSlack"; + break; + case SolverType::KLUSingleSlack: + out << "KLUSingleSlack"; + break; + case SolverType::NICSLUSingleSlack: + out << "NICSLUSingleSlack"; + break; + case SolverType::KLUDC: + out << "KLUDC"; + break; + case SolverType::NICSLUDC: + out << "NICSLUDC"; + break; + case SolverType::CKTSO: + out << "CKTSO"; + break; + case SolverType::CKTSOSingleSlack: + out << "CKTSOSingleSlack"; + break; + case SolverType::CKTSODC: + out << "CKTSODC"; + break; + case SolverType::FDPF_XB_SparseLU: + out << "FDPF_XB_SparseLU"; + break; + case SolverType::FDPF_BX_SparseLU: + out << "FDPF_BX_SparseLU"; + break; + case SolverType::FDPF_XB_KLU: + out << "FDPF_XB_KLU"; + break; + case SolverType::FDPF_BX_KLU: + out << "FDPF_BX_KLU"; + break; + case SolverType::FDPF_XB_NICSLU: + out << "FDPF_XB_NICSLU"; + break; + case SolverType::FDPF_BX_NICSLU: + out << "FDPF_BX_NICSLU"; + break; + case SolverType::FDPF_XB_CKTSO: + out << "FDPF_XB_CKTSO"; + break; + case SolverType::FDPF_BX_CKTSO: + out << "FDPF_BX_CKTSO"; + break; + default: + out << "(unknown)"; + break; + } + return out; +} diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h index 08fb0cd1..2961d8a6 100644 --- a/src/ChooseSolver.h +++ b/src/ChooseSolver.h @@ -27,6 +27,8 @@ enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU, FDPF_XB_CKTSO, FDPF_BX_CKTSO // from 0.7.5 }; + +std::ostream& operator<<(std::ostream& out, const SolverType& solver_type); // TODO define a template class instead of these weird stuff !!! // TODO export all methods from base class ! @@ -278,7 +280,7 @@ class ChooseSolver } void tell_solver_control(const SolverControl & solver_control){ - auto p_solver = get_prt_solver("get_ptdf", true); + auto p_solver = get_prt_solver("tell_solver_control", false); p_solver -> tell_solver_control(solver_control); } @@ -312,7 +314,17 @@ class ChooseSolver private: void check_right_solver(const std::string & error_msg) const { - if(_solver_type != _type_used_for_nr) throw std::runtime_error("ChooseSolver: Solver mismatch when calling '"+error_msg+"': current solver is not the last solver used to perform a powerflow"); + if(_solver_type != _type_used_for_nr){ + std::ostringstream exc_; + exc_ << "ChooseSolver: Solver mismatch when calling '"; + exc_ << error_msg; + exc_ << ": current solver ("; + exc_ << _solver_type; + exc_ << ") is not the one used to perform a powerflow ("; + exc_ << _type_used_for_nr; + exc_ << ")."; + throw std::runtime_error(exc_.str()); + } #ifndef KLU_SOLVER_AVAILABLE if(_solver_type == SolverType::KLU){ diff --git a/src/DataDCLine.cpp b/src/DataDCLine.cpp index e7c51c43..c74301be 100644 --- a/src/DataDCLine.cpp +++ b/src/DataDCLine.cpp @@ -87,11 +87,9 @@ void DataDCLine::disconnect_if_not_in_main_component(std::vector & busbar_ auto bus_or = bus_or_id(i); auto bus_ex = bus_ex_id(i); if(!busbar_in_main_component[bus_or]) { - bool tmp = false; from_gen_.deactivate(i, unused_solver_control); } if(!busbar_in_main_component[bus_ex]) { - bool tmp = false; to_gen_.deactivate(i, unused_solver_control); } // if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 2aee652e..f4768f2f 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -414,25 +414,67 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, const SolverControl & solver_control) { // TODO get rid of the "is_ac" argument: this info is available in the _solver already - if(is_ac) _solver.tell_solver_control(solver_control); - else _dc_solver.tell_solver_control(solver_control); - - if (solver_control.has_slack_participate_changed()) slack_bus_id_ = generators_.get_slack_bus_id(); - if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed()) init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); - if (solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed() || solver_control.need_recompute_ybus()) fillYbus(Ybus, is_ac, id_me_to_solver); - if (solver_control.has_dimension_changed()) init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); - if (solver_control.has_slack_participate_changed() || solver_control.has_pv_changed() || solver_control.has_pq_changed()) fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); + if(is_ac){ + _solver.tell_solver_control(solver_control); + if(solver_control.need_reset_solver()) _solver.reset(); + } else { + _dc_solver.tell_solver_control(solver_control); + if(solver_control.need_reset_solver()){ + std::cout << "_dc_solver.reset();" << std::endl; + _dc_solver.reset(); + } + } + + if (solver_control.need_reset_solver() || + solver_control.has_slack_participate_changed()){ + std::cout << "get_slack_bus_id;" << std::endl; + slack_bus_id_ = generators_.get_slack_bus_id(); + } + if (solver_control.need_reset_solver() || + solver_control.ybus_change_sparsity_pattern() || + solver_control.has_dimension_changed()){ + init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); + std::cout << "init_Ybus;" << std::endl; + } + if (solver_control.need_reset_solver() || + solver_control.ybus_change_sparsity_pattern() || + solver_control.has_dimension_changed() || + solver_control.need_recompute_ybus()){ + fillYbus(Ybus, is_ac, id_me_to_solver); + std::cout << "fillYbus;" << std::endl; + } + if (solver_control.need_reset_solver() || + solver_control.has_dimension_changed()) { + init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); + std::cout << "init_Sbus;" << std::endl; + } + if (solver_control.need_reset_solver() || + solver_control.has_slack_participate_changed() || + solver_control.has_pv_changed() || + solver_control.has_pq_changed()) { + fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); + std::cout << "fillpv_pq;" << std::endl; + } - if (solver_control.has_dimension_changed() || solver_control.need_recompute_sbus() && is_ac){ + if (is_ac && (solver_control.need_reset_solver() || + solver_control.has_dimension_changed() || + solver_control.need_recompute_sbus())){ int nb_bus_total = static_cast(bus_vn_kv_.size()); total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0); generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - fillSbus_me(Sbus_, is_ac, id_me_to_solver); + std::cout << "total_gen_per_bus_;" << std::endl; } + if (solver_control.need_reset_solver() || + solver_control.has_slack_participate_changed() || + solver_control.has_pq_changed()) { + fillSbus_me(Sbus_, is_ac, id_me_to_solver); + std::cout << "fillSbus_me;" << std::endl; + } + const int nb_bus_solver = static_cast(id_solver_to_me.size()); CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_); for(int bus_solver_id = 0; bus_solver_id < nb_bus_solver; ++bus_solver_id){ @@ -442,6 +484,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, } generators_.set_vm(V, id_me_to_solver); dc_lines_.set_vm(V, id_me_to_solver); + std::cout << nb_bus_solver << std::endl; return V; } @@ -701,15 +744,17 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, CplxVect V = pre_process_solver(Vinit, Ybus_dc_, id_me_to_dc_solver_, id_dc_solver_to_me_, slack_bus_id_dc_solver_, is_ac, solver_control_); - + std::cout << "after pre proces\n"; // start the solver if(solver_control_.has_slack_participate_changed() || solver_control_.has_pv_changed() || solver_control_.has_slack_weight_changed()) slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); + std::cout << "slack_weights\n"; conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); - + std::cout << "after compute_pf\n"; // store results (fase -> because I am in dc mode) process_results(conv, res, Vinit, false, id_me_to_dc_solver_); + std::cout << "after compute_pf\n"; return res; } diff --git a/src/GridModel.h b/src/GridModel.h index 82955344..0030c4d9 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -76,8 +76,11 @@ class GridModel : public DataGeneric solver_control_(), init_vm_pu_(1.04), sn_mva_(1.0){ + _solver.change_solver(SolverType::SparseLU); _dc_solver.change_solver(SolverType::DC); _solver.set_gridmodel(this); + _dc_solver.set_gridmodel(this); + solver_control_.tell_all_changed(); } GridModel(const GridModel & other); GridModel copy() const{ @@ -166,7 +169,6 @@ class GridModel : public DataGeneric const RealVect & trafo_x, const CplxVect & trafo_b, const RealVect & trafo_tap_step_pct, - // const RealVect & trafo_tap_step_degree, const RealVect & trafo_tap_pos, const RealVect & trafo_shift_degree, const std::vector & trafo_tap_hv, // is tap on high voltage (true) or low voltate From cc25e5306546537c1e3f74404507983231efe552 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Wed, 13 Dec 2023 11:17:29 +0100 Subject: [PATCH 34/66] compiles and basic checks work --- lightsim2grid/lightSimBackend.py | 5 +- setup.py | 3 +- src/BaseNRSolver.tpp | 48 +++++--- src/BaseNRSolverSingleSlack.tpp | 75 ++++++++---- src/BaseSolver.cpp | 1 + src/DCSolver.tpp | 11 +- src/DataDCLine.h | 2 +- src/DataGen.cpp | 14 ++- src/DataGen.h | 14 ++- src/DataGeneric.cpp | 4 +- src/DataGeneric.h | 2 +- src/DataLine.h | 1 + src/DataShunt.cpp | 9 +- src/DataTrafo.h | 1 + src/GridModel.cpp | 199 ++++++++++++++++++++++--------- src/GridModel.h | 27 +++-- src/Utils.cpp | 49 ++++++++ src/Utils.h | 21 +++- 18 files changed, 354 insertions(+), 132 deletions(-) create mode 100644 src/Utils.cpp diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index a8c66c5d..81732db0 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -894,9 +894,9 @@ def runpf(self, is_dc=False): self._grid.deactivate_result_computation() # if I init with dc values, it should depends on previous state self.V[:] = self._grid.get_init_vm_pu() # see issue 30 - print("before dc pf") + # print(f"{self.V[:14] = }") Vdc = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it, self.tol) - print("after dc pf") + # print(f"{Vdc[:14] = }") self._grid.reactivate_result_computation() if Vdc.shape[0] == 0: raise BackendError(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}") @@ -905,7 +905,6 @@ def runpf(self, is_dc=False): V_init = copy.deepcopy(self.V) tick = time.perf_counter() self._timer_preproc += tick - beg_preproc - print("before ac pf") V = self._grid.ac_pf(V_init, self.max_it, self.tol) self._timer_solver += time.perf_counter() - tick if V.shape[0] == 0: diff --git a/setup.py b/setup.py index 99193f8a..9a0f4f08 100644 --- a/setup.py +++ b/setup.py @@ -157,7 +157,8 @@ "src/BaseMultiplePowerflow.cpp", "src/Computers.cpp", "src/SecurityAnalysis.cpp", - "src/Solvers.cpp"] + "src/Solvers.cpp", + "src/Utils.cpp"] if KLU_SOLVER_AVAILABLE: src_files.append("src/KLUSolver.cpp") diff --git a/src/BaseNRSolver.tpp b/src/BaseNRSolver.tpp index 9c259257..b0995584 100644 --- a/src/BaseNRSolver.tpp +++ b/src/BaseNRSolver.tpp @@ -13,15 +13,15 @@ template bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -36,19 +36,28 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix // TODO DEBUG MODE std::ostringstream exc_; exc_ << "BaseNRSolver::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; - exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.rows() << ")."; + exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ // TODO DEBUG MODE std::ostringstream exc_; exc_ << "BaseNRSolver::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; - exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.rows() << ")."; + exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } reset_timer(); + // std::cout << "dist slack" << std::endl; + + if(_solver_control.need_reset_solver() || + _solver_control.has_dimension_changed()){ + reset(); + } auto timer = CustTimer(); - if(!is_linear_solver_valid()) return false; + if(!is_linear_solver_valid()) { + // err_ = ErrorType::NotInitError; + return false; + } err_ = ErrorType::NoError; // reset the error if previous error happened @@ -85,6 +94,11 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl; // std::cout << "slack_absorbed " << slack_absorbed << std::endl; + BaseNRSolver::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + BaseNRSolver::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + BaseNRSolver::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed while ((!converged) & (nr_iter_ < max_iter)){ nr_iter_++; fill_jacobian_matrix(Ybus, V_, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); @@ -146,6 +160,7 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix << "\n\t timer_total_nr_: " << timer_total_nr_ << "\n\n"; #endif // __COUT_TIMES + _solver_control.tell_none_changed(); return res; } @@ -165,7 +180,7 @@ void BaseNRSolver::reset(){ template void BaseNRSolver::_dSbus_dV(const Eigen::Ref > & Ybus, - const Eigen::Ref & V){ + const Eigen::Ref & V){ auto timer = CustTimer(); const auto size_dS = V.size(); const CplxVect Vnorm = V.array() / V.array().abs(); @@ -331,6 +346,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); + fill_value_map(slack_bus_id, pq, pvpq); #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; #endif // __COUT_TIMES @@ -339,7 +355,8 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< // properly and faster (approx 3 times faster than the previous one) #ifdef __COUT_TIMES auto timer3 = CustTimer(); - #endif // __COUT_TIMES + #endif // + if (BaseNRSolver::value_map_.size() == 0) fill_value_map(slack_bus_id,pq, pvpq); fill_jacobian_matrix_kown_sparsity_pattern(slack_bus_id, pq, pvpq ); @@ -404,7 +421,7 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can // i can do that because the matrix will ALWAYS have the same non zero coefficients. // in this if, i allocate it in a "large enough" place to avoid copy when first filling it - if(J_.cols() != size_j) J_ = Eigen::SparseMatrix(size_j,size_j); + if(J_.cols() != size_j) J_ = Eigen::SparseMatrix(size_j, size_j); std::vector > coeffs; // HERE FOR PERF OPTIM (3) coeffs.reserve(2*(dS_dVa_.nonZeros()+dS_dVm_.nonZeros()) + slack_weights.size()); // HERE FOR PERF OPTIM (3) @@ -512,7 +529,6 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( J_.setFromTriplets(coeffs.begin(), coeffs.end()); // HERE FOR PERF OPTIM (3) // std::cout << "end fill jacobian unknown " << std::endl; J_.makeCompressed(); - fill_value_map(slack_bus_id, pq, pvpq); // std::cout << "end fill_value_map" << std::endl; } diff --git a/src/BaseNRSolverSingleSlack.tpp b/src/BaseNRSolverSingleSlack.tpp index df0a6e88..fd9e1ac3 100644 --- a/src/BaseNRSolverSingleSlack.tpp +++ b/src/BaseNRSolverSingleSlack.tpp @@ -11,15 +11,15 @@ template bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, // unused here - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, // unused here + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -31,18 +31,26 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){ std::ostringstream exc_; exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; - exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.rows() << ")."; + exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ std::ostringstream exc_; exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; - exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<<", "<::reset_timer(); - - if(!BaseNRSolver::is_linear_solver_valid()) return false; + // std::cout << "singleslack" << std::endl; + + if(BaseNRSolver::_solver_control.need_reset_solver() || + BaseNRSolver::_solver_control.has_dimension_changed()){ + BaseNRSolver::reset(); + } + + if(!BaseNRSolver::is_linear_solver_valid()){ + return false; + } BaseNRSolver::err_ = ErrorType::NoError; // reset the error if previous error happened auto timer = CustTimer(); @@ -73,14 +81,20 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop const cplx_type m_i = BaseNRSolver::my_i; // otherwise it does not compile - + BaseNRSolver::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + BaseNRSolver::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + BaseNRSolver::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed while ((!converged) & (BaseNRSolver::nr_iter_ < max_iter)){ BaseNRSolver::nr_iter_++; + // std::cout << "\tnr_iter_ " << BaseNRSolver::nr_iter_ << std::endl; fill_jacobian_matrix(Ybus, BaseNRSolver::V_, pq, pvpq, pq_inv, pvpq_inv); if(BaseNRSolver::need_factorize_){ BaseNRSolver::initialize(); if(BaseNRSolver::err_ != ErrorType::NoError){ // I got an error during the initialization of the linear system, i need to stop here + // std::cout << BaseNRSolver::err_ << std::endl; res = false; break; } @@ -95,6 +109,7 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix has_just_been_initialized = false; if(BaseNRSolver::err_ != ErrorType::NoError){ // I got an error during the solving of the linear system, i need to stop here + // std::cout << BaseNRSolver::err_ << std::endl; res = false; break; } @@ -117,7 +132,11 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix F = BaseNRSolver::_evaluate_Fx(Ybus, BaseNRSolver::V_, Sbus, my_pv, pq); bool tmp = F.allFinite(); - if(!tmp) break; // divergence due to Nans + if(!tmp){ + BaseNRSolver::err_ = ErrorType::InifiniteValue; + // std::cout << BaseNRSolver::err_ << std::endl; + break; // divergence due to Nans + } converged = BaseNRSolver::_check_for_convergence(F, tol); } if(!converged){ @@ -135,17 +154,18 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix << "\n\t timer_total_nr_: " << BaseNRSolver::timer_total_nr_ << "\n\n"; #endif // __COUT_TIMES + BaseNRSolver::_solver_control.tell_none_changed(); return res; } template void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, - const CplxVect & V, - const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq, - const std::vector & pq_inv, - const std::vector & pvpq_inv - ) + const CplxVect & V, + const Eigen::VectorXi & pq, + const Eigen::VectorXi & pvpq, + const std::vector & pq_inv, + const std::vector & pvpq_inv + ) { /** J has the shape @@ -165,7 +185,6 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp const int n_pvpq = static_cast(pvpq.size()); const int n_pq = static_cast(pq.size()); const int size_j = n_pvpq + n_pq; - // TODO to gain a bit more time below, try to compute directly, in _dSbus_dV(Ybus, V); // TODO the `dS_dVa_[pvpq, pvpq]` // TODO so that it's easier to retrieve in the next few lines ! @@ -177,6 +196,8 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, pq, pvpq, pq_inv, pvpq_inv); + fill_value_map(pq, pvpq); + // std::cout << "\t\tfill_jacobian_matrix_unkown_sparsity_pattern" << std::endl; #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; #endif // __COUT_TIMES @@ -186,7 +207,12 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #ifdef __COUT_TIMES auto timer3 = CustTimer(); #endif // __COUT_TIMES + if (BaseNRSolver::value_map_.size() == 0){ + // std::cout << "\t\tfill_value_map called" << std::endl; + fill_value_map(pq, pvpq); + } fill_jacobian_matrix_kown_sparsity_pattern(pq, pvpq); + // std::cout << "\t\tfill_jacobian_matrix_kown_sparsity_pattern" << std::endl; #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_kown_sparsity_pattern : " << timer3.duration() << std::endl; #endif // __COUT_TIMES @@ -323,7 +349,6 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity } // J_.setFromTriplets(coeffs.begin(), coeffs.end()); // HERE FOR PERF OPTIM (3) BaseNRSolver::J_.makeCompressed(); - fill_value_map(pq, pvpq); } /** @@ -340,9 +365,9 @@ void BaseNRSolverSingleSlack::fill_value_map( const int n_pvpq = static_cast(pvpq.size()); BaseNRSolver::value_map_ = std::vector (BaseNRSolver::J_.nonZeros()); - const int n_row = static_cast(BaseNRSolver::J_.cols()); + const int n_col = static_cast(BaseNRSolver::J_.cols()); unsigned int pos_el = 0; - for (int col_=0; col_ < n_row; ++col_){ + for (int col_=0; col_ < n_col; ++col_){ for (Eigen::SparseMatrix::InnerIterator it(BaseNRSolver::J_, col_); it; ++it) { const int row_id = static_cast(it.row()); diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 61b9d491..624b5236 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -122,6 +122,7 @@ bool BaseSolver::_check_for_convergence(const RealVect & F, { auto timer = CustTimer(); const auto norm_inf = F.lpNorm(); + // std::cout << "\t\tnorm_inf: " << norm_inf << std::endl; bool res = norm_inf < tol; timer_check_ += timer.duration(); return res; diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index fee67b2f..87c2fb3a 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -26,6 +26,15 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // V is used the following way: at pq buses it's completely ignored. For pv bus only the magnitude is used, // and for the slack bus both the magnitude and the angle are used. + if(!is_linear_solver_valid()) { + // err_ = ErrorType::NotInitError; + return false; + } + if(_solver_control.need_reset_solver() || + _solver_control.has_dimension_changed()){ + reset(); + } + auto timer = CustTimer(); BaseSolver::reset_timer(); sizeYbus_with_slack_ = static_cast(Ybus.rows()); @@ -135,7 +144,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix #ifdef __COUT_TIMES std::cout << "\t dc postproc: " << 1000. * timer_postproc.duration() << "ms" << std::endl; #endif // __COUT_TIMES - + _solver_control.tell_none_changed(); timer_total_nr_ += timer.duration(); return true; } diff --git a/src/DataDCLine.h b/src/DataDCLine.h index 8f2576a9..b9363d2c 100644 --- a/src/DataDCLine.h +++ b/src/DataDCLine.h @@ -224,7 +224,7 @@ class DataDCLine : public DataGeneric virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const { from_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver); to_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver); diff --git a/src/DataGen.cpp b/src/DataGen.cpp index bc60fd2e..c504fc43 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -168,7 +168,7 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv void DataGen::fillpv(std::vector & bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const { const int nb_gen = nb(); @@ -216,7 +216,7 @@ void DataGen::reset_results(){ res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV res_theta_ = RealVect(); // in deg - bus_slack_weight_ = RealVect(); + // bus_slack_weight_ = RealVect(); } void DataGen::get_vm_for_dc(RealVect & Vm){ @@ -331,16 +331,18 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c } } -std::vector DataGen::get_slack_bus_id() const{ - std::vector res; +Eigen::VectorXi DataGen::get_slack_bus_id() const{ + std::vector tmp; + Eigen::VectorXi res; const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ if(gen_slackbus_[gen_id]){ const auto my_bus = bus_id_(gen_id); // do not add twice the same "slack bus" - if(!is_in_vect(my_bus, res)) res.push_back(my_bus); + if(!is_in_vect(my_bus, tmp)) tmp.push_back(my_bus); } } + res = Eigen::VectorXi::Map(&tmp[0], tmp.size()); // force the copy of the data apparently return res; } @@ -349,7 +351,7 @@ void DataGen::set_p_slack(const RealVect& node_mismatch, { if(bus_slack_weight_.size() == 0){ // TODO DEBUG MODE: perform this check only in debug mode - throw std::runtime_error("Impossible to set the active value of generators for the slack bus"); + throw std::runtime_error("DataGen::set_p_slack: Impossible to set the active value of generators for the slack bus: no known slack (you should haved called DataGen::get_slack_weights first)"); } const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ diff --git a/src/DataGen.h b/src/DataGen.h index e2fb9cbe..c143017c 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -221,7 +221,7 @@ class DataGen: public DataGeneric **/ RealVect get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver); - std::vector get_slack_bus_id() const; + Eigen::VectorXi get_slack_bus_id() const; void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver); // modification @@ -236,7 +236,7 @@ class DataGen: public DataGeneric solver_control.tell_recompute_sbus(); if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); - if(gen_slack_weight_[gen_id]){ + if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ solver_control.tell_slack_participate_changed(); solver_control.tell_slack_weight_changed(); } @@ -248,14 +248,18 @@ class DataGen: public DataGeneric solver_control.tell_recompute_sbus(); if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); - if(gen_slack_weight_[gen_id]){ + if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ solver_control.tell_slack_participate_changed(); solver_control.tell_slack_weight_changed(); } } _reactivate(gen_id, status_); } - void change_bus(int gen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, solver_control, nb_bus);} + void change_bus(int gen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + if (new_bus_id != bus_id_[gen_id]){ + if (gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]) solver_control.has_slack_participate_changed(); + } + _change_bus(gen_id, new_bus_id, bus_id_, solver_control, nb_bus);} int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); @@ -269,7 +273,7 @@ class DataGen: public DataGeneric virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const; void init_q_vector(int nb_bus, Eigen::VectorXi & total_gen_per_bus, diff --git a/src/DataGeneric.cpp b/src/DataGeneric.cpp index 08d30ed2..3b7c330c 100644 --- a/src/DataGeneric.cpp +++ b/src/DataGeneric.cpp @@ -86,8 +86,8 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el // TODO speed: sparsity pattern might not change if something is already there solver_control.tell_ybus_change_sparsity_pattern(); - solver_control.tell_recompute_sbus(); - solver_control.tell_recompute_ybus(); + solver_control.tell_recompute_sbus(); // if a bus changed for load / generator + solver_control.tell_recompute_ybus(); // if a bus changed for shunts / line / trafo } bus_me_id = new_bus_me_id; } diff --git a/src/DataGeneric.h b/src/DataGeneric.h index d844f4a0..768cc7dd 100644 --- a/src/DataGeneric.h +++ b/src/DataGeneric.h @@ -88,7 +88,7 @@ class DataGeneric : public BaseConstants virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {}; virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const {}; virtual void get_q(std::vector& q_by_bus) {}; diff --git a/src/DataLine.h b/src/DataLine.h index 20261535..10b4be09 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -192,6 +192,7 @@ class DataLine : public DataGeneric if(status_[powerline_id]){ solver_control.tell_recompute_ybus(); // but sparsity pattern do not change here (possibly one more coeff at 0.) + solver_control.tell_ybus_some_coeffs_zero(); } _deactivate(powerline_id, status_); } diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp index 6813a7ac..ba52bbdb 100644 --- a/src/DataShunt.cpp +++ b/src/DataShunt.cpp @@ -170,7 +170,10 @@ void DataShunt::change_p(int shunt_id, real_type new_p, SolverControl & solver_c { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the active value of a disconnected shunt"); - if(p_mw_(shunt_id) != new_p) solver_control.tell_recompute_sbus(); + if(p_mw_(shunt_id) != new_p){ + solver_control.tell_recompute_ybus(); + solver_control.tell_recompute_sbus(); // in dc mode sbus is modified + } p_mw_(shunt_id) = new_p; } @@ -179,7 +182,9 @@ void DataShunt::change_q(int shunt_id, real_type new_q, SolverControl & solver_c { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the reactive value of a disconnected shunt"); - if(q_mvar_(shunt_id) != new_q) solver_control.tell_recompute_sbus(); + if(q_mvar_(shunt_id) != new_q){ + solver_control.tell_recompute_ybus(); + } q_mvar_(shunt_id) = new_q; } diff --git a/src/DataTrafo.h b/src/DataTrafo.h index 90e05e96..033a58aa 100644 --- a/src/DataTrafo.h +++ b/src/DataTrafo.h @@ -178,6 +178,7 @@ class DataTrafo : public DataGeneric if(status_[trafo_id]){ solver_control.tell_recompute_ybus(); // but sparsity pattern do not change here (possibly one more coeff at 0.) + solver_control.tell_ybus_some_coeffs_zero(); } _deactivate(trafo_id, status_); } diff --git a/src/GridModel.cpp b/src/GridModel.cpp index f4768f2f..7bdbea3e 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -260,7 +260,7 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) Ybus_dc_ = Eigen::SparseMatrix(); } - Sbus_ = CplxVect(); + acSbus_ = CplxVect(); dcSbus_ = CplxVect(); bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); @@ -291,9 +291,14 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, bool conv = false; CplxVect res = CplxVect(); + reset_results(); // reset the results + // pre process the data to define a proper jacobian matrix, the proper voltage vector etc. bool is_ac = true; - CplxVect V = pre_process_solver(Vinit, Ybus_ac_, + // std::cout << "before pre process" << std::endl; + CplxVect V = pre_process_solver(Vinit, + acSbus_, + Ybus_ac_, id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_, @@ -301,10 +306,20 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, solver_control_); // start the solver - slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_); - conv = _solver.compute_pf(Ybus_ac_, V, Sbus_, slack_bus_id_ac_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_); + // std::cout << "before get_slack_weights" << std::endl; + if(solver_control_.need_reset_solver() || + solver_control_.has_dimension_changed() || + solver_control_.has_slack_participate_changed() || + solver_control_.has_pv_changed() || + solver_control_.has_slack_weight_changed()){ + // std::cout << "get_slack_weights" << std::endl; + slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_); + } + // std::cout << "before compute_pf" << std::endl; + conv = _solver.compute_pf(Ybus_ac_, V, acSbus_, slack_bus_id_ac_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_); - // store results (in ac mode) + // store results (in ac mode) + // std::cout << "before process_results" << std::endl; process_results(conv, res, Vinit, true, id_me_to_ac_solver_); // return the vector of complex voltage at each bus @@ -377,14 +392,18 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim bool is_ac = true; SolverControl reset_solver; reset_solver.tell_none_changed(); // TODO reset solver - CplxVect V = pre_process_solver(V_proposed, Ybus_ac_, - id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_, + CplxVect V = pre_process_solver(V_proposed, + acSbus_, + Ybus_ac_, + id_me_to_ac_solver_, + id_ac_solver_to_me_, + slack_bus_id_ac_solver_, is_ac, reset_solver); // compute the mismatch CplxVect tmp = Ybus_ac_ * V; // this is a vector tmp = tmp.array().conjugate(); // i take the conjugate - auto mis = V.array() * tmp.array() - Sbus_.array(); + auto mis = V.array() * tmp.array() - acSbus_.array(); // TODO ac or dc here // store results CplxVect res = _get_results_back_to_orig_nodes(mis, @@ -406,6 +425,7 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim }; CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, + CplxVect & Sbus, Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector & id_solver_to_me, @@ -416,44 +436,56 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, // TODO get rid of the "is_ac" argument: this info is available in the _solver already if(is_ac){ _solver.tell_solver_control(solver_control); - if(solver_control.need_reset_solver()) _solver.reset(); + if(solver_control.need_reset_solver()){ + // std::cout << "_ac_solver.reset();" << std::endl; + _solver.reset(); + } } else { _dc_solver.tell_solver_control(solver_control); if(solver_control.need_reset_solver()){ - std::cout << "_dc_solver.reset();" << std::endl; + // std::cout << "_dc_solver.reset();" << std::endl; _dc_solver.reset(); } } if (solver_control.need_reset_solver() || + solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed()){ - std::cout << "get_slack_bus_id;" << std::endl; - slack_bus_id_ = generators_.get_slack_bus_id(); + slack_bus_id_solver = generators_.get_slack_bus_id(); + // this is the slack bus ids with the gridmodel ordering, not the solver ordering. + // conversion to solver ordering is done in init_slack_bus + + // std::cout << "slack_bus_id_solver 1: "; + // for(auto el: slack_bus_id_solver) std::cout << el << ", "; + // std::cout << std::endl; } if (solver_control.need_reset_solver() || solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed()){ init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); - std::cout << "init_Ybus;" << std::endl; + // std::cout << "init_Ybus;" << std::endl; } if (solver_control.need_reset_solver() || solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed() || solver_control.need_recompute_ybus()){ fillYbus(Ybus, is_ac, id_me_to_solver); - std::cout << "fillYbus;" << std::endl; + // std::cout << "fillYbus;" << std::endl; } if (solver_control.need_reset_solver() || solver_control.has_dimension_changed()) { - init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); - std::cout << "init_Sbus;" << std::endl; + // init Sbus + Sbus = CplxVect::Constant(id_solver_to_me.size(), 0.); + // std::cout << "init_Sbus;" << std::endl; } if (solver_control.need_reset_solver() || + solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed() || solver_control.has_pv_changed() || solver_control.has_pq_changed()) { + init_slack_bus(Sbus, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); - std::cout << "fillpv_pq;" << std::endl; + // std::cout << "fillpv_pq;" << std::endl; } if (is_ac && (solver_control.need_reset_solver() || @@ -465,14 +497,15 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0); generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - std::cout << "total_gen_per_bus_;" << std::endl; + // std::cout << "total_gen_per_bus_;" << std::endl; } if (solver_control.need_reset_solver() || + solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed() || solver_control.has_pq_changed()) { - fillSbus_me(Sbus_, is_ac, id_me_to_solver); - std::cout << "fillSbus_me;" << std::endl; + fillSbus_me(Sbus, is_ac, id_me_to_solver); + // std::cout << "fillSbus_me;" << std::endl; } const int nb_bus_solver = static_cast(id_solver_to_me.size()); @@ -484,7 +517,10 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, } generators_.set_vm(V, id_me_to_solver); dc_lines_.set_vm(V, id_me_to_solver); - std::cout << nb_bus_solver << std::endl; + // std::cout << "pre_process_solver: V result: "< & Ybus, id_me_to_solver = std::vector(nb_bus_init, _deactivated_bus_id); // by default, if a bus is disconnected, then it has a -1 there id_solver_to_me = std::vector(); id_solver_to_me.reserve(nb_bus_init); - int bus_id_solver=0; + int bus_id_solver = 0; for(int bus_id_me=0; bus_id_me < nb_bus_init; ++bus_id_me){ if(bus_status_[bus_id_me]){ // bus is connected @@ -552,31 +589,56 @@ void GridModel::init_Ybus(Eigen::SparseMatrix & Ybus, ++bus_id_solver; } } - const int nb_bus = static_cast(id_solver_to_me.size()); + const int nb_bus_solver = static_cast(id_solver_to_me.size()); - Ybus = Eigen::SparseMatrix(nb_bus, nb_bus); - Ybus.reserve(nb_bus + 2*powerlines_.nb() + 2*trafos_.nb()); + Ybus = Eigen::SparseMatrix(nb_bus_solver, nb_bus_solver); + Ybus.reserve(nb_bus_solver + 2*powerlines_.nb() + 2*trafos_.nb()); } -void GridModel::init_Sbus(CplxVect & Sbus, - std::vector& id_me_to_solver, - std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver){ +void GridModel::init_slack_bus(const CplxVect & Sbus, + const std::vector& id_me_to_solver, + const std::vector& id_solver_to_me, + Eigen::VectorXi & slack_bus_id_solver){ - const int nb_bus = static_cast(id_solver_to_me.size()); - Sbus = CplxVect::Constant(nb_bus, 0.); - slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size()); + const int nb_bus = static_cast(id_solver_to_me.size()); + // slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size()); + // slack_bus_id_solver = Eigen::VectorXi::Constant(slack_bus_id_solver.size(), _deactivated_bus_id); size_t i = 0; - for(auto el: slack_bus_id_) { - slack_bus_id_solver(i) = id_me_to_solver[el]; + // std::cout << "slack_bus_id_solver 2: "; + // for(auto el: slack_bus_id_solver) std::cout << el << ", "; + // std::cout << std::endl; + // std::cout << "id_me_to_solver: "; + // for(auto el: id_me_to_solver) std::cout << el << ", "; + // std::cout << std::endl; + // std::cout << "id_solver_to_me: "; + // for(auto el: id_solver_to_me) std::cout << el << ", "; + // std::cout << std::endl; + + // for(auto el: slack_bus_id_) { + for(auto el: slack_bus_id_solver) { + auto tmp = id_me_to_solver[el]; + if(tmp == _deactivated_bus_id){ + std::ostringstream exc_; + exc_ << "GridModel::init_Sbus: One of the slack bus is disconnected."; + exc_ << " You can check element "; + exc_ << el; + exc_ << ": ["; + for(auto el2 : slack_bus_id_solver) exc_ << el2 << ", "; + exc_ << "]."; + throw std::out_of_range(exc_.str()); + } + slack_bus_id_solver(i) = tmp; ++i; } + // std::cout << "slack_bus_id_solver 3: "; + // for(auto el: slack_bus_id_solver) std::cout << el << ", "; + // std::cout << std::endl; if(is_in_vect(_deactivated_bus_id, slack_bus_id_solver)){ // TODO improve error message with the gen_id // TODO DEBUG MODE: only check that in debug mode - throw std::runtime_error("One of the slack bus is disconnected !"); + throw std::runtime_error("GridModel::init_Sbus: One of the slack bus is disconnected !"); } } void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver){ @@ -586,6 +648,7 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st **/ // init the Ybus matrix + res.setZero(); // it should not be needed but might not hurt too much either. std::vector > tripletList; tripletList.reserve(bus_vn_kv_.size() + 4*powerlines_.nb() + 4*trafos_.nb() + shunts_.nb()); powerlines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); // TODO have a function to dispatch that to all type of elements @@ -596,13 +659,14 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st storages_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); generators_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); dc_lines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); - res.setFromTriplets(tripletList.begin(), tripletList.end()); + res.setFromTriplets(tripletList.begin(), tripletList.end()); // works because "The initial contents of *this is destroyed" res.makeCompressed(); } void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id_me_to_solver) { - // init the Sbus vector + // init the Sbus + Sbus.array() = 0.; // reset to 0. powerlines_.fillSbus(Sbus, id_me_to_solver, ac); // TODO have a function to dispatch that to all type of elements trafos_.fillSbus(Sbus, id_me_to_solver, ac); shunts_.fillSbus(Sbus, id_me_to_solver, ac); @@ -617,8 +681,8 @@ void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id } void GridModel::fillpv_pq(const std::vector& id_me_to_solver, - std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver, + const std::vector& id_solver_to_me, + const Eigen::VectorXi & slack_bus_id_solver, const SolverControl & solver_control) { // Nothing to do if neither pv, nor pq nor the dimension of the problem has changed @@ -633,8 +697,6 @@ void GridModel::fillpv_pq(const std::vector& id_me_to_solver, bus_pv.reserve(nb_bus); std::vector has_bus_been_added(nb_bus, false); - // std::cout << "id_me_to_solver.size(): " << id_me_to_solver.size() << std::endl; - bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); powerlines_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); // TODO have a function to dispatch that to all type of elements @@ -652,8 +714,8 @@ void GridModel::fillpv_pq(const std::vector& id_me_to_solver, bus_pq.push_back(bus_id); has_bus_been_added[bus_id] = true; // don't add it a second time } - bus_pv_ = Eigen::Map(bus_pv.data(), bus_pv.size()); - bus_pq_ = Eigen::Map(bus_pq.data(), bus_pq.size()); + bus_pv_ = Eigen::VectorXi::Map(&bus_pv[0], bus_pv.size()); + bus_pq_ = Eigen::VectorXi::Map(&bus_pq[0], bus_pq.size()); } void GridModel::compute_results(bool ac){ // retrieve results from powerflow @@ -681,12 +743,12 @@ void GridModel::compute_results(bool ac){ //handle_slack_bus active power CplxVect mismatch; // power mismatch at each bus (SOLVER BUS !!!) - RealVect ractive_mismatch; // not used in dc mode (DO NOT ATTEMPT TO USE IT THERE) + RealVect reactive_mismatch; // not used in dc mode (DO NOT ATTEMPT TO USE IT THERE) RealVect active_mismatch; if(ac){ // In AC mode i am not forced to run through all the grid auto tmp = (Ybus_ac_ * V).conjugate(); - mismatch = V.array() * tmp.array() - Sbus_.array(); + mismatch = V.array() * tmp.array() - acSbus_.array(); active_mismatch = mismatch.real() * sn_mva_; } else{ active_mismatch = RealVect::Zero(V.size()); @@ -697,13 +759,15 @@ void GridModel::compute_results(bool ac){ const auto id_slack = slack_bus_id_dc_solver_(0); active_mismatch(id_slack) = -dcSbus_.real().sum() * sn_mva_; } + // for(auto el: active_mismatch) std::cout << el << ", "; + // std::cout << std::endl; generators_.set_p_slack(active_mismatch, id_me_to_solver); - if(ac) ractive_mismatch = mismatch.imag() * sn_mva_; + if(ac) reactive_mismatch = mismatch.imag() * sn_mva_; // mainly to initialize the Q value of the generators in dc (just fill it with 0.) - generators_.set_q(ractive_mismatch, id_me_to_solver, ac, + generators_.set_q(reactive_mismatch, id_me_to_solver, ac, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); - dc_lines_.set_q(ractive_mismatch, id_me_to_solver, ac, + dc_lines_.set_q(reactive_mismatch, id_me_to_solver, ac, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_); } @@ -739,22 +803,39 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, bool conv = false; CplxVect res = CplxVect(); + reset_results(); // reset the results + // pre process the data to define a proper jacobian matrix, the proper voltage vector etc. bool is_ac = false; - CplxVect V = pre_process_solver(Vinit, Ybus_dc_, - id_me_to_dc_solver_, id_dc_solver_to_me_, slack_bus_id_dc_solver_, + CplxVect V = pre_process_solver(Vinit, + dcSbus_, + Ybus_dc_, + id_me_to_dc_solver_, + id_dc_solver_to_me_, + slack_bus_id_dc_solver_, is_ac, solver_control_); - std::cout << "after pre proces\n"; + // std::cout << "after pre proces\n"; // start the solver - if(solver_control_.has_slack_participate_changed() || + if(solver_control_.need_reset_solver() || + solver_control_.has_dimension_changed() || + solver_control_.has_slack_participate_changed() || solver_control_.has_pv_changed() || - solver_control_.has_slack_weight_changed()) slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); - std::cout << "slack_weights\n"; + solver_control_.has_slack_weight_changed()){ + // TODO smarter solver: this is done both in ac and in dc ! + // std::cout << "get_slack_weights" << std::endl; + slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); + } + // std::cout << "V (init to dc pf)\n"; + // for(auto el: V) std::cout << el << ", "; + // std::cout << std::endl; + // std::cout << "dcSbus (init to dc pf)\n"; + // for(auto el: dcSbus_) std::cout << el << ", "; + // std::cout << std::endl; conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); - std::cout << "after compute_pf\n"; + // std::cout << "after compute_pf\n"; // store results (fase -> because I am in dc mode) process_results(conv, res, Vinit, false, id_me_to_dc_solver_); - std::cout << "after compute_pf\n"; + // std::cout << "after compute_pf\n"; return res; } @@ -1047,7 +1128,9 @@ std::tuple GridModel::assign_slack_to_most_connected(){ generators_.remove_all_slackbus(); res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus, solver_control_); std::get<1>(res) = res_gen_id; - slack_bus_id_ = std::vector(); + // slack_bus_id_ = std::vector(); + slack_bus_id_ac_solver_ = Eigen::VectorXi(); + slack_bus_id_dc_solver_ = Eigen::VectorXi(); slack_weights_ = RealVect(); return res; } diff --git a/src/GridModel.h b/src/GridModel.h index 0030c4d9..2bf85b74 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -255,7 +255,7 @@ class GridModel : public DataGeneric unsigned int size_th = 6; if (my_state.size() != size_th) { - std::cout << "LightSim::GridModel state size " << my_state.size() << " instead of "<< size_th << std::endl; + // std::cout << "LightSim::GridModel state size " << my_state.size() << " instead of "<< size_th << std::endl; // TODO more explicit error message throw std::runtime_error("Invalid state when loading LightSim::GridModel"); } @@ -471,7 +471,7 @@ class GridModel : public DataGeneric return Ybus_dc_; // This is copied to python } Eigen::Ref get_Sbus() const{ - return Sbus_; + return acSbus_; } Eigen::Ref get_dcSbus() const{ return dcSbus_; @@ -629,23 +629,30 @@ class GridModel : public DataGeneric if you will perform a powerflow after it or not. (usually put ``true`` here). **/ CplxVect pre_process_solver(const CplxVect & Vinit, + CplxVect & Sbus, Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector & id_solver_to_me, Eigen::VectorXi & slack_bus_id_solver, bool is_ac, const SolverControl & solver_control); + + // init the Ybus matrix (its size, it is filled up elsewhere) and also the + // converter from "my bus id" to the "solver bus id" (id_me_to_solver and id_solver_to_me) void init_Ybus(Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector& id_solver_to_me); - void init_Sbus(CplxVect & Sbus, - std::vector & id_me_to_solver, - std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver); + + // converts the slack_bus_id from gridmodel ordering into solver ordering + void init_slack_bus(const CplxVect & Sbus, + const std::vector & id_me_to_solver, + const std::vector& id_solver_to_me, + Eigen::VectorXi & slack_bus_id_solver); void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver); void fillSbus_me(CplxVect & res, bool ac, const std::vector& id_me_to_solver); - void fillpv_pq(const std::vector& id_me_to_solver, std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver, + void fillpv_pq(const std::vector& id_me_to_solver, + const std::vector& id_solver_to_me, + const Eigen::VectorXi & slack_bus_id_solver, const SolverControl & solver_control); // results @@ -794,7 +801,7 @@ class GridModel : public DataGeneric DataDCLine dc_lines_; // 8. slack bus - std::vector slack_bus_id_; + // std::vector slack_bus_id_; Eigen::VectorXi slack_bus_id_ac_solver_; Eigen::VectorXi slack_bus_id_dc_solver_; RealVect slack_weights_; @@ -802,7 +809,7 @@ class GridModel : public DataGeneric // as matrix, for the solver Eigen::SparseMatrix Ybus_ac_; Eigen::SparseMatrix Ybus_dc_; - CplxVect Sbus_; + CplxVect acSbus_; CplxVect dcSbus_; Eigen::VectorXi bus_pv_; // id are the solver internal id and NOT the initial id Eigen::VectorXi bus_pq_; // id are the solver internal id and NOT the initial id diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 00000000..835bbb89 --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2020, RTE (https://www.rte-france.com) +// See AUTHORS.txt +// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 +// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +#include "Utils.h" + +std::ostream& operator<<(std::ostream& out, const ErrorType & error_type){ + switch (error_type) + { + case ErrorType::NoError: + out << "NoError"; + break; + case ErrorType::SingularMatrix: + out << "SingularMatrix"; + break; + case ErrorType::TooManyIterations: + out << "TooManyIterations"; + break; + case ErrorType::InifiniteValue: + out << "InifiniteValue"; + break; + case ErrorType::SolverAnalyze: + out << "SolverAnalyze"; + break; + case ErrorType::SolverFactor: + out << "SolverFactor"; + break; + case ErrorType::SolverReFactor: + out << "SolverReFactor"; + break; + case ErrorType::SolverSolve: + out << "SolverSolve"; + break; + case ErrorType::NotInitError: + out << "NotInitError"; + break; + case ErrorType::LicenseError: + out << "LicenseError"; + break; + default: + out << "unknown error (check utils.cpp)"; + break; + } + return out; +} diff --git a/src/Utils.h b/src/Utils.h index 0d56bc0d..406c125d 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -37,7 +37,17 @@ typedef Eigen::Matrix RealMat; typedef Eigen::Matrix CplxMat; // type of error in the different solvers -enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError}; +enum class ErrorType {NoError, + SingularMatrix, + TooManyIterations, + InifiniteValue, + SolverAnalyze, + SolverFactor, + SolverReFactor, + SolverSolve, + NotInitError, + LicenseError}; +std::ostream& operator<<(std::ostream& out, const ErrorType & error_type); // define some constant for compilation outside of "setup.py" #ifndef VERSION_MAJOR @@ -65,6 +75,7 @@ class SolverControl need_recompute_ybus_(true), v_changed_(true), slack_weight_changed_(true), + ybus_some_coeffs_zero_(true), ybus_change_sparsity_pattern_(true) {}; @@ -78,6 +89,7 @@ class SolverControl need_recompute_ybus_ = true; v_changed_ = true; slack_weight_changed_ = true; + ybus_some_coeffs_zero_ = true; ybus_change_sparsity_pattern_ = true; } @@ -91,6 +103,7 @@ class SolverControl need_recompute_ybus_ = false; v_changed_ = false; slack_weight_changed_ = false; + ybus_some_coeffs_zero_ = false; ybus_change_sparsity_pattern_ = false; } @@ -114,6 +127,10 @@ class SolverControl void tell_v_changed(){v_changed_ = true;} // at least one generator has changed its slack participation void tell_slack_weight_changed(){slack_weight_changed_ = true;} + // tells that some coeff of ybus might have been set to 0. + // (and ybus compressed again, so these coeffs are really completely hidden) + // might need to trigger some recomputation of some solvers (eg NR based ones) + void tell_ybus_some_coeffs_zero(){ybus_some_coeffs_zero_ = true;} bool has_dimension_changed() const {return change_dimension_;} bool has_pv_changed() const {return pv_changed_;} @@ -125,6 +142,7 @@ class SolverControl bool ybus_change_sparsity_pattern() const {return ybus_change_sparsity_pattern_;} bool has_slack_weight_changed() const {return slack_weight_changed_;} bool has_v_changed() const {return v_changed_;} + bool has_ybus_some_coeffs_zero() const {return ybus_some_coeffs_zero_;} protected: bool change_dimension_; @@ -136,6 +154,7 @@ class SolverControl bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern bool v_changed_; bool slack_weight_changed_; + bool ybus_some_coeffs_zero_; // tells that some coeff of ybus might have been set to 0. (and ybus compressed again, so these coeffs are really completely hidden) bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff), or ybus change of dimension }; From 91939fdc4637a2849a847d47bdf096b4e153989f Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 14 Dec 2023 16:00:08 +0100 Subject: [PATCH 35/66] let's see what tests fail now --- CHANGELOG.rst | 2 + benchmarks/benchmark_solvers.py | 2 +- lightsim2grid/tests/test_GridModel.py | 5 +- lightsim2grid/tests/test_SameResPP.py | 4 +- lightsim2grid/tests/test_case118.py | 2 +- lightsim2grid/tests/test_issue_56.py | 2 +- lightsim2grid/tests/test_ptdf.py | 3 +- src/BaseNRSolver.h | 14 +++++- src/BaseNRSolver.tpp | 71 ++++++++++++++++----------- src/BaseNRSolverSingleSlack.h | 3 +- src/BaseNRSolverSingleSlack.tpp | 39 ++++++++------- src/DataGen.cpp | 4 +- src/DataGen.h | 2 + src/DataLine.h | 13 +++-- src/GridModel.cpp | 40 ++++++++++++--- src/GridModel.h | 10 +++- src/Utils.h | 2 +- 17 files changed, 144 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 56c4a883..c880b94f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,6 +30,8 @@ Change Log - [FIXED] a bug in init from pypowsybl when some object were disconnected. It raises an error (because they are not connected to a bus): now this function properly handles these cases. +- [FIXED] a bug leading to not propagate correctly the "compute_results" flag when the + environment was copied (for example) - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. diff --git a/benchmarks/benchmark_solvers.py b/benchmarks/benchmark_solvers.py index 7281e06e..55dfa24a 100644 --- a/benchmarks/benchmark_solvers.py +++ b/benchmarks/benchmark_solvers.py @@ -43,10 +43,10 @@ lightsim2grid.SolverType.SparseLU: "NR (SLU)", lightsim2grid.SolverType.KLU: "NR (KLU)", lightsim2grid.SolverType.NICSLU: "NR (NICSLU *)", + lightsim2grid.SolverType.CKTSO: "NR (CKTSO *)", lightsim2grid.SolverType.SparseLUSingleSlack: "NR single (SLU)", lightsim2grid.SolverType.KLUSingleSlack: "NR single (KLU)", lightsim2grid.SolverType.NICSLUSingleSlack: "NR single (NICSLU *)", - lightsim2grid.SolverType.CKTSO: "NR (CKTSO *)", lightsim2grid.SolverType.CKTSOSingleSlack: "NR single (CKTSO *)", lightsim2grid.SolverType.FDPF_XB_SparseLU: "FDPF XB (SLU)", lightsim2grid.SolverType.FDPF_BX_SparseLU: "FDPF BX (SLU)", diff --git a/lightsim2grid/tests/test_GridModel.py b/lightsim2grid/tests/test_GridModel.py index 0d6329ee..7230fc7f 100644 --- a/lightsim2grid/tests/test_GridModel.py +++ b/lightsim2grid/tests/test_GridModel.py @@ -103,7 +103,8 @@ def make_v0(self, net): return V0 def run_me_pf(self, V0): - return self.model.compute_newton(V0, self.max_it, self.tol) + res = self.model.ac_pf(V0, self.max_it, self.tol) + return res def run_ref_pf(self, net): with warnings.catch_warnings(): @@ -111,7 +112,7 @@ def run_ref_pf(self, net): pp.runpp(net, init="flat", lightsim2grid=False, - numba=True, + numba=False, distributed_slack=False) def do_i_skip(self, func_name): diff --git a/lightsim2grid/tests/test_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py index 9e7e1f78..13ae6a86 100644 --- a/lightsim2grid/tests/test_SameResPP.py +++ b/lightsim2grid/tests/test_SameResPP.py @@ -131,7 +131,7 @@ def _aux_test(self, pn_net): V = backend._grid.ac_pf(v_tmp, 10, 1e-5) assert V.shape[0], "? lightsim diverge when initialized with pp final voltage ?" - backend._grid.tell_topo_changed() + backend._grid.tell_solver_need_reset() Y_pp = backend.init_pp_backend._grid._ppc["internal"]["Ybus"] Sbus = backend.init_pp_backend._grid._ppc["internal"]["Sbus"] @@ -218,7 +218,7 @@ def _aux_test(self, pn_net): backend._grid.deactivate_result_computation() Vdc = backend._grid.dc_pf(Vinit, max_iter, tol_this) backend._grid.reactivate_result_computation() - backend._grid.tell_topo_changed() + backend._grid.tell_solver_need_reset() Ydc_me = copy.deepcopy(backend._grid.get_dcYbus()) Sdc_me = copy.deepcopy(backend._grid.get_dcSbus()) assert np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])) <= 100.*self.tol,\ diff --git a/lightsim2grid/tests/test_case118.py b/lightsim2grid/tests/test_case118.py index c4e48902..95b5d875 100644 --- a/lightsim2grid/tests/test_case118.py +++ b/lightsim2grid/tests/test_case118.py @@ -131,7 +131,7 @@ def test_neurips_track2(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore") ls_grid = init(self.pp_net) - ls_grid.tell_topo_changed() + ls_grid.tell_solver_need_reset() V = np.ones(2 * self.nb_bus_total, dtype=np.complex_) V = ls_grid.ac_pf(V, self.max_it, self.tol) self.check_results(V[:self.nb_bus_total], ls_grid, self.pp_net) diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py index 6b0a3a6a..9aed2a08 100644 --- a/lightsim2grid/tests/test_issue_56.py +++ b/lightsim2grid/tests/test_issue_56.py @@ -49,7 +49,7 @@ def test_dc(self): grid_model.deactivate_powerline(l_id) else: grid_model.deactivate_trafo(l_id - nb_powerline) - grid_model.tell_topo_changed() + grid_model.tell_solver_need_reset() V = 1.0 * self.env.backend.V # np.ones(2 * self.env.n_sub, dtype=np.complex_) res = grid_model.dc_pf(V, 10, 1e-8) if len(res): diff --git a/lightsim2grid/tests/test_ptdf.py b/lightsim2grid/tests/test_ptdf.py index 8bfb6da0..4a98e4ae 100644 --- a/lightsim2grid/tests/test_ptdf.py +++ b/lightsim2grid/tests/test_ptdf.py @@ -36,7 +36,8 @@ def setUp(self) -> None: if solver_type not in self.gridmodel.available_solvers(): self.skipTest("Solver type not supported on this platform") self.gridmodel.change_solver(solver_type) - self.gridmodel.dc_pf(self.V_init, 1, 1e-8) + V = self.gridmodel.dc_pf(self.V_init, 1, 1e-8) + assert len(V), f"dc pf has diverged with error {self.gridmodel.get_dc_solver().get_error()}" self.dcYbus = 1.0 * self.gridmodel.get_dcYbus() self.dcSbus = 1.0 * self.gridmodel.get_dcSbus().real self.Bbus = 1.0 * self.dcYbus.real diff --git a/src/BaseNRSolver.h b/src/BaseNRSolver.h index 8b7c5fab..5a461233 100644 --- a/src/BaseNRSolver.h +++ b/src/BaseNRSolver.h @@ -136,8 +136,16 @@ class BaseNRSolver : public BaseSolver void fill_value_map(Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq); - + const Eigen::VectorXi & pvpq, + bool reset_J); + + void reset_if_needed(){ + if(_solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.has_ybus_some_coeffs_zero()){ + reset(); + } + } protected: // used linear solver LinearSolver _linear_solver; @@ -151,6 +159,8 @@ class BaseNRSolver : public BaseSolver // to store the mapping from the element of J_ in dS_dVm_ and dS_dVa_ // it does not own any memory at all ! std::vector value_map_; + // std::vector col_map_; + // std::vector row_map_; // timers double timer_initialize_; diff --git a/src/BaseNRSolver.tpp b/src/BaseNRSolver.tpp index b0995584..4dbbfb8e 100644 --- a/src/BaseNRSolver.tpp +++ b/src/BaseNRSolver.tpp @@ -46,20 +46,14 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } - reset_timer(); - // std::cout << "dist slack" << std::endl; - - if(_solver_control.need_reset_solver() || - _solver_control.has_dimension_changed()){ - reset(); - } - auto timer = CustTimer(); if(!is_linear_solver_valid()) { // err_ = ErrorType::NotInitError; return false; } - + reset_timer(); + reset_if_needed(); err_ = ErrorType::NoError; // reset the error if previous error happened + auto timer = CustTimer(); Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv); // retrieve_pv_with_slack (not all), add_slack_to_pv (all) real_type slack_absorbed = std::real(Sbus.sum()); // initial guess for slack_absorbed @@ -94,9 +88,11 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl; // std::cout << "slack_absorbed " << slack_absorbed << std::endl; - BaseNRSolver::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed - BaseNRSolver::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed - BaseNRSolver::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRSolver::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRSolver::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed while ((!converged) & (nr_iter_ < max_iter)){ @@ -181,6 +177,7 @@ void BaseNRSolver::reset(){ template void BaseNRSolver::_dSbus_dV(const Eigen::Ref > & Ybus, const Eigen::Ref & V){ + // std::cout << "Ybus.nonZeros(): " << Ybus.nonZeros() << std::endl; auto timer = CustTimer(); const auto size_dS = V.size(); const CplxVect Vnorm = V.array() / V.array().abs(); @@ -198,6 +195,12 @@ void BaseNRSolver::_dSbus_dV(const Eigen::Ref >::InnerIterator it(Ybus, col_id); it; ++it) @@ -298,14 +301,14 @@ void BaseNRSolver::_get_values_J(int & nb_obj_this_col, template void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, - const CplxVect & V, - Eigen::Index slack_bus_id, - const RealVect & slack_weights, - const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq, - const std::vector & pq_inv, - const std::vector & pvpq_inv - ) + const CplxVect & V, + Eigen::Index slack_bus_id, + const RealVect & slack_weights, + const Eigen::VectorXi & pq, + const Eigen::VectorXi & pvpq, + const std::vector & pq_inv, + const std::vector & pvpq_inv + ) { /** Remember, J has the shape: @@ -346,7 +349,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); - fill_value_map(slack_bus_id, pq, pvpq); + fill_value_map(slack_bus_id, pq, pvpq, false); #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; #endif // __COUT_TIMES @@ -356,7 +359,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< #ifdef __COUT_TIMES auto timer3 = CustTimer(); #endif // - if (BaseNRSolver::value_map_.size() == 0) fill_value_map(slack_bus_id,pq, pvpq); + if (BaseNRSolver::value_map_.size() == 0) fill_value_map(slack_bus_id, pq, pvpq, true); fill_jacobian_matrix_kown_sparsity_pattern(slack_bus_id, pq, pvpq ); @@ -541,11 +544,15 @@ template void BaseNRSolver::fill_value_map( Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq + const Eigen::VectorXi & pvpq, + bool reset_J ) { const int n_pvpq = static_cast(pvpq.size()); - value_map_ = std::vector (J_.nonZeros()); + value_map_ = std::vector (); + value_map_.reserve(BaseNRSolver::J_.nonZeros()); + // col_map_ = std::vector (J_.nonZeros()); + // row_map_ = std::vector (J_.nonZeros()); const auto n_row = J_.cols(); unsigned int pos_el = 0; @@ -554,16 +561,18 @@ void BaseNRSolver::fill_value_map( { auto row_id = it.row(); const auto col_id = it.col() - 1; // it's equal to "col_" + if(reset_J) it.valueRef() = 0.; // "forget" previous J value in this setting + if(row_id==0){ // this is the row of the slack bus const Eigen::Index row_id_dS_dVx_r = slack_bus_id; // same for both matrices if(col_id < n_pvpq){ const int col_id_dS_dVa_r = pvpq[col_id]; - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVa_r); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVa_r)); } else{ const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVm_r); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVm_r)); } }else{ row_id -= 1; // "do not consider" the row for slack bus (handled above) @@ -573,7 +582,7 @@ void BaseNRSolver::fill_value_map( const int row_id_dS_dVa_r = pvpq[row_id]; const int col_id_dS_dVa_r = pvpq[col_id]; // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r); - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place // const int is_row_non_null = pq_inv[row_id_dS_dVa_r]; @@ -587,25 +596,27 @@ void BaseNRSolver::fill_value_map( const int row_id_dS_dVa_i = pq[row_id - n_pvpq]; const int col_id_dS_dVa_i = pvpq[col_id]; // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i); - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){ // this is the J12 part (dS_dVm_r) const int row_id_dS_dVm_r = pvpq[row_id]; const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r); - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){ // this is the J22 part (dS_dVm_i) const int row_id_dS_dVm_i = pq[row_id - n_pvpq]; const int col_id_dS_dVm_i = pq[col_id - n_pvpq]; // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i); - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); } } // go to the next element ++pos_el; } } + dS_dVa_.makeCompressed(); + dS_dVm_.makeCompressed(); } template diff --git a/src/BaseNRSolverSingleSlack.h b/src/BaseNRSolverSingleSlack.h index 8cd8522f..09f8042f 100644 --- a/src/BaseNRSolverSingleSlack.h +++ b/src/BaseNRSolverSingleSlack.h @@ -58,7 +58,8 @@ class BaseNRSolverSingleSlack : public BaseNRSolver ); void fill_value_map(const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq); + const Eigen::VectorXi & pvpq, + bool reset_J); }; diff --git a/src/BaseNRSolverSingleSlack.tpp b/src/BaseNRSolverSingleSlack.tpp index fd9e1ac3..7e39ba23 100644 --- a/src/BaseNRSolverSingleSlack.tpp +++ b/src/BaseNRSolverSingleSlack.tpp @@ -40,19 +40,13 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<<", "<::reset_timer(); - // std::cout << "singleslack" << std::endl; - - if(BaseNRSolver::_solver_control.need_reset_solver() || - BaseNRSolver::_solver_control.has_dimension_changed()){ - BaseNRSolver::reset(); - } - if(!BaseNRSolver::is_linear_solver_valid()){ return false; } - + BaseNRSolver::reset_timer(); + BaseNRSolver::reset_if_needed(); BaseNRSolver::err_ = ErrorType::NoError; // reset the error if previous error happened + auto timer = CustTimer(); // initialize once and for all the "inverse" of these vectors // Eigen::VectorXi my_pv = BaseNRSolver::retrieve_pv_with_slack(slack_ids, pv); @@ -84,6 +78,7 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix BaseNRSolver::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed BaseNRSolver::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed BaseNRSolver::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + // BaseNRSolver::J_.setZero(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed or ybus_some_coeffs_zero_ // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed while ((!converged) & (BaseNRSolver::nr_iter_ < max_iter)){ @@ -196,7 +191,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, pq, pvpq, pq_inv, pvpq_inv); - fill_value_map(pq, pvpq); + fill_value_map(pq, pvpq, false); // std::cout << "\t\tfill_jacobian_matrix_unkown_sparsity_pattern" << std::endl; #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; @@ -209,7 +204,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #endif // __COUT_TIMES if (BaseNRSolver::value_map_.size() == 0){ // std::cout << "\t\tfill_value_map called" << std::endl; - fill_value_map(pq, pvpq); + fill_value_map(pq, pvpq, true); } fill_jacobian_matrix_kown_sparsity_pattern(pq, pvpq); // std::cout << "\t\tfill_jacobian_matrix_kown_sparsity_pattern" << std::endl; @@ -264,9 +259,9 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity if(BaseNRSolver::J_.cols() != size_j) { need_insert = true; - BaseNRSolver::J_ = Eigen::SparseMatrix(size_j,size_j); + BaseNRSolver::J_ = Eigen::SparseMatrix(size_j, size_j); // pre allocate a large enough matrix - BaseNRSolver::J_.reserve(2*(BaseNRSolver::dS_dVa_.nonZeros()+BaseNRSolver::dS_dVm_.nonZeros())); + BaseNRSolver::J_.reserve(2*(BaseNRSolver::dS_dVa_.nonZeros() + BaseNRSolver::dS_dVm_.nonZeros())); // from an experiment, outerIndexPtr is initialized, with the number of columns // innerIndexPtr and valuePtr are not. } @@ -359,11 +354,14 @@ it requires that J_ is initialized, in compressed mode. template void BaseNRSolverSingleSlack::fill_value_map( const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq + const Eigen::VectorXi & pvpq, + bool reset_J ) { const int n_pvpq = static_cast(pvpq.size()); - BaseNRSolver::value_map_ = std::vector (BaseNRSolver::J_.nonZeros()); + BaseNRSolver::value_map_.clear(); + // std::cout << "BaseNRSolver::J_.nonZeros(): " << BaseNRSolver::J_.nonZeros() << std::endl; + BaseNRSolver::value_map_.reserve(BaseNRSolver::J_.nonZeros()); const int n_col = static_cast(BaseNRSolver::J_.cols()); unsigned int pos_el = 0; @@ -372,13 +370,14 @@ void BaseNRSolverSingleSlack::fill_value_map( { const int row_id = static_cast(it.row()); const int col_id = static_cast(it.col()); // it's equal to "col_" + if(reset_J) it.valueRef() = 0.; // "forget" previous J value in this setting // real_type & this_el = J_x_ptr[pos_el]; if((col_id < n_pvpq) && (row_id < n_pvpq)){ // this is the J11 part (dS_dVa_r) const int row_id_dS_dVa_r = pvpq[row_id]; const int col_id_dS_dVa_r = pvpq[col_id]; // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r); + BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place // const int is_row_non_null = pq_inv[row_id_dS_dVa_r]; @@ -393,25 +392,27 @@ void BaseNRSolverSingleSlack::fill_value_map( const int row_id_dS_dVa_i = pq[row_id - n_pvpq]; const int col_id_dS_dVa_i = pvpq[col_id]; // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i); + BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){ // this is the J12 part (dS_dVm_r) const int row_id_dS_dVm_r = pvpq[row_id]; const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r); + BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){ // this is the J22 part (dS_dVm_i) const int row_id_dS_dVm_i = pq[row_id - n_pvpq]; const int col_id_dS_dVm_i = pq[col_id - n_pvpq]; // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i); + BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); } // go to the next element ++pos_el; } } + // BaseNRSolver::dS_dVa_.makeCompressed(); + // BaseNRSolver::dS_dVm_.makeCompressed(); } template diff --git a/src/DataGen.cpp b/src/DataGen.cpp index c504fc43..e92152ee 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -333,6 +333,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c Eigen::VectorXi DataGen::get_slack_bus_id() const{ std::vector tmp; + tmp.reserve(gen_slackbus_.size()); Eigen::VectorXi res; const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ @@ -342,7 +343,8 @@ Eigen::VectorXi DataGen::get_slack_bus_id() const{ if(!is_in_vect(my_bus, tmp)) tmp.push_back(my_bus); } } - res = Eigen::VectorXi::Map(&tmp[0], tmp.size()); // force the copy of the data apparently + if(tmp.empty()) throw std::runtime_error("DataGen::get_slack_bus_id: no generator are tagged slack bus for this grid."); + res = Eigen::VectorXi::Map(tmp.data(), tmp.size()); // force the copy of the data apparently return res; } diff --git a/src/DataGen.h b/src/DataGen.h index c143017c..0f531cb5 100644 --- a/src/DataGen.h +++ b/src/DataGen.h @@ -234,6 +234,7 @@ class DataGen: public DataGeneric void deactivate(int gen_id, SolverControl & solver_control) { if (status_[gen_id]){ solver_control.tell_recompute_sbus(); + solver_control.tell_pq_changed(); // bus might now be pq if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ @@ -246,6 +247,7 @@ class DataGen: public DataGeneric void reactivate(int gen_id, SolverControl & solver_control) { if(!status_[gen_id]){ solver_control.tell_recompute_sbus(); + solver_control.tell_pq_changed(); // bus might now be pv if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ diff --git a/src/DataLine.h b/src/DataLine.h index 10b4be09..49f07db7 100644 --- a/src/DataLine.h +++ b/src/DataLine.h @@ -189,6 +189,7 @@ class DataLine : public DataGeneric virtual void get_graph(std::vector > & res) const; void deactivate(int powerline_id, SolverControl & solver_control) { + // std::cout << "line: deactivate called\n"; if(status_[powerline_id]){ solver_control.tell_recompute_ybus(); // but sparsity pattern do not change here (possibly one more coeff at 0.) @@ -199,12 +200,18 @@ class DataLine : public DataGeneric void reactivate(int powerline_id, SolverControl & solver_control) { if(!status_[powerline_id]){ solver_control.tell_recompute_ybus(); - solver_control.tell_ybus_change_sparsity_pattern(); // this might change + solver_control.tell_ybus_change_sparsity_pattern(); // sparsity pattern might change: a non zero coeff can pop up } _reactivate(powerline_id, status_); } - void change_bus_or(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_or_id_, solver_control, nb_bus);} - void change_bus_ex(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_ex_id_, solver_control, nb_bus);} + void change_bus_or(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + // std::cout << "line: change_bus_or called\n"; + _change_bus(powerline_id, new_bus_id, bus_or_id_, solver_control, nb_bus); + } + void change_bus_ex(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + // std::cout << "line: change_bus_or called\n"; + _change_bus(powerline_id, new_bus_id, bus_ex_id_, solver_control, nb_bus); + } int get_bus_or(int powerline_id) {return _get_bus(powerline_id, status_, bus_or_id_);} int get_bus_ex(int powerline_id) {return _get_bus(powerline_id, status_, bus_ex_id_);} virtual void fillYbus(std::vector > & res, diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 7bdbea3e..b7175652 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -17,6 +17,7 @@ GridModel::GridModel(const GridModel & other) init_vm_pu_ = other.init_vm_pu_; sn_mva_ = other.sn_mva_; + compute_results_ = other.compute_results_; // copy the powersystem representation // 1. bus @@ -72,8 +73,11 @@ GridModel::GridModel(const GridModel & other) _solver.change_solver(other._solver.get_type()); _dc_solver.change_solver(other._dc_solver.get_type()); compute_results_ = other.compute_results_; + solver_control_.tell_all_changed(); _dc_solver.set_gridmodel(this); _solver.set_gridmodel(this); + _dc_solver.tell_solver_control(solver_control_); + _solver.tell_solver_control(solver_control_); } //pickle @@ -264,8 +268,13 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) dcSbus_ = CplxVect(); bus_pv_ = Eigen::VectorXi(); bus_pq_ = Eigen::VectorXi(); - slack_weights_ = RealVect(); solver_control_.tell_all_changed(); + + slack_bus_id_ac_me_ = Eigen::VectorXi(); // slack bus id, gridmodel number + slack_bus_id_ac_solver_ = Eigen::VectorXi(); // slack bus id, solver number + slack_bus_id_dc_me_ = Eigen::VectorXi(); + slack_bus_id_dc_solver_ = Eigen::VectorXi(); + slack_weights_ = RealVect(); // reset the solvers if (reset_solver){ @@ -274,6 +283,7 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) _solver.set_gridmodel(this); _dc_solver.set_gridmodel(this); } + } CplxVect GridModel::ac_pf(const CplxVect & Vinit, @@ -301,6 +311,7 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, Ybus_ac_, id_me_to_ac_solver_, id_ac_solver_to_me_, + slack_bus_id_ac_me_, slack_bus_id_ac_solver_, is_ac, solver_control_); @@ -397,6 +408,7 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim Ybus_ac_, id_me_to_ac_solver_, id_ac_solver_to_me_, + slack_bus_id_ac_me_, slack_bus_id_ac_solver_, is_ac, reset_solver); @@ -429,6 +441,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector & id_solver_to_me, + Eigen::VectorXi & slack_bus_id_me, Eigen::VectorXi & slack_bus_id_solver, bool is_ac, const SolverControl & solver_control) @@ -451,7 +464,8 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, if (solver_control.need_reset_solver() || solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed()){ - slack_bus_id_solver = generators_.get_slack_bus_id(); + // std::cout << "slack_bus_id_solver\n"; + slack_bus_id_me = generators_.get_slack_bus_id(); // this is the slack bus ids with the gridmodel ordering, not the solver ordering. // conversion to solver ordering is done in init_slack_bus @@ -462,6 +476,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, if (solver_control.need_reset_solver() || solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed()){ + // std::cout << "init_Ybus;" << std::endl; init_Ybus(Ybus, id_me_to_solver, id_solver_to_me); // std::cout << "init_Ybus;" << std::endl; } @@ -469,12 +484,14 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, solver_control.ybus_change_sparsity_pattern() || solver_control.has_dimension_changed() || solver_control.need_recompute_ybus()){ + // std::cout << "fillYbus;" << std::endl; fillYbus(Ybus, is_ac, id_me_to_solver); // std::cout << "fillYbus;" << std::endl; } if (solver_control.need_reset_solver() || solver_control.has_dimension_changed()) { // init Sbus + // std::cout << "init_Sbus;" << std::endl; Sbus = CplxVect::Constant(id_solver_to_me.size(), 0.); // std::cout << "init_Sbus;" << std::endl; } @@ -483,7 +500,9 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, solver_control.has_slack_participate_changed() || solver_control.has_pv_changed() || solver_control.has_pq_changed()) { - init_slack_bus(Sbus, id_me_to_solver, id_solver_to_me, slack_bus_id_solver); + // std::cout << "init_slack_bus;" << std::endl; + init_slack_bus(Sbus, id_me_to_solver, id_solver_to_me, slack_bus_id_me, slack_bus_id_solver); + // std::cout << "fillpv_pq;" << std::endl; fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control); // std::cout << "fillpv_pq;" << std::endl; } @@ -491,6 +510,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, if (is_ac && (solver_control.need_reset_solver() || solver_control.has_dimension_changed() || solver_control.need_recompute_sbus())){ + // std::cout << "total_gen_per_bus_;" << std::endl; int nb_bus_total = static_cast(bus_vn_kv_.size()); total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); @@ -504,6 +524,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed() || solver_control.has_pq_changed()) { + // std::cout << "fillSbus_me;" << std::endl; fillSbus_me(Sbus, is_ac, id_me_to_solver); // std::cout << "fillSbus_me;" << std::endl; } @@ -566,6 +587,7 @@ void GridModel::process_results(bool conv, static_cast(Vinit.size())); } else { //powerflow diverge + // std::cout << "powerflow diverge" << std::endl; reset_results(); // TODO solver control ??? something to do here ? } @@ -598,11 +620,12 @@ void GridModel::init_Ybus(Eigen::SparseMatrix & Ybus, void GridModel::init_slack_bus(const CplxVect & Sbus, const std::vector& id_me_to_solver, const std::vector& id_solver_to_me, + const Eigen::VectorXi & slack_bus_id_me, Eigen::VectorXi & slack_bus_id_solver){ const int nb_bus = static_cast(id_solver_to_me.size()); // slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size()); - // slack_bus_id_solver = Eigen::VectorXi::Constant(slack_bus_id_solver.size(), _deactivated_bus_id); + slack_bus_id_solver = Eigen::VectorXi::Constant(slack_bus_id_me.size(), _deactivated_bus_id); size_t i = 0; // std::cout << "slack_bus_id_solver 2: "; @@ -616,7 +639,7 @@ void GridModel::init_slack_bus(const CplxVect & Sbus, // std::cout << std::endl; // for(auto el: slack_bus_id_) { - for(auto el: slack_bus_id_solver) { + for(auto el: slack_bus_id_me) { auto tmp = id_me_to_solver[el]; if(tmp == _deactivated_bus_id){ std::ostringstream exc_; @@ -624,7 +647,7 @@ void GridModel::init_slack_bus(const CplxVect & Sbus, exc_ << " You can check element "; exc_ << el; exc_ << ": ["; - for(auto el2 : slack_bus_id_solver) exc_ << el2 << ", "; + for(auto el2 : slack_bus_id_me) exc_ << el2 << ", "; exc_ << "]."; throw std::out_of_range(exc_.str()); } @@ -772,6 +795,7 @@ void GridModel::compute_results(bool ac){ } void GridModel::reset_results(){ + // std::cout << "reset_results\n"; powerlines_.reset_results(); // TODO have a function to dispatch that to all type of elements shunts_.reset_results(); trafos_.reset_results(); @@ -812,8 +836,10 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, Ybus_dc_, id_me_to_dc_solver_, id_dc_solver_to_me_, + slack_bus_id_dc_me_, slack_bus_id_dc_solver_, - is_ac, solver_control_); + is_ac, + solver_control_); // std::cout << "after pre proces\n"; // start the solver if(solver_control_.need_reset_solver() || diff --git a/src/GridModel.h b/src/GridModel.h index 2bf85b74..19717ba3 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -74,6 +74,7 @@ class GridModel : public DataGeneric GridModel(): solver_control_(), + compute_results_(true), init_vm_pu_(1.04), sn_mva_(1.0){ _solver.change_solver(SolverType::SparseLU); @@ -633,6 +634,7 @@ class GridModel : public DataGeneric Eigen::SparseMatrix & Ybus, std::vector & id_me_to_solver, std::vector & id_solver_to_me, + Eigen::VectorXi & slack_bus_id_me, Eigen::VectorXi & slack_bus_id_solver, bool is_ac, const SolverControl & solver_control); @@ -647,7 +649,9 @@ class GridModel : public DataGeneric void init_slack_bus(const CplxVect & Sbus, const std::vector & id_me_to_solver, const std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver); + const Eigen::VectorXi & slack_bus_id_me, + Eigen::VectorXi & slack_bus_id_solver + ); void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver); void fillSbus_me(CplxVect & res, bool ac, const std::vector& id_me_to_solver); void fillpv_pq(const std::vector& id_me_to_solver, @@ -802,7 +806,9 @@ class GridModel : public DataGeneric // 8. slack bus // std::vector slack_bus_id_; - Eigen::VectorXi slack_bus_id_ac_solver_; + Eigen::VectorXi slack_bus_id_ac_me_; // slack bus id, gridmodel number + Eigen::VectorXi slack_bus_id_ac_solver_; // slack bus id, solver number + Eigen::VectorXi slack_bus_id_dc_me_; Eigen::VectorXi slack_bus_id_dc_solver_; RealVect slack_weights_; diff --git a/src/Utils.h b/src/Utils.h index 406c125d..eeaec050 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -96,7 +96,7 @@ class SolverControl void tell_none_changed(){ change_dimension_ = false; pv_changed_ = false; - pq_changed_ = true; + pq_changed_ = false; slack_participate_changed_ = false; need_reset_solver_ = false; need_recompute_sbus_ = false; From 8bb7acd9f9b5fd96eaddecb6c1a63768832504a7 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 15 Dec 2023 14:48:30 +0100 Subject: [PATCH 36/66] let's see failing tests --- .../tests/test_dist_slack_backend.py | 16 +++++-- src/BaseNRSolver.h | 3 +- src/BaseSolver.cpp | 27 ++++++----- src/DCSolver.h | 2 +- src/DCSolver.tpp | 16 ++++++- src/DataGen.cpp | 2 +- src/GridModel.cpp | 48 ++++++++++--------- 7 files changed, 72 insertions(+), 42 deletions(-) diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py index 40c492ae..2d63007e 100644 --- a/lightsim2grid/tests/test_dist_slack_backend.py +++ b/lightsim2grid/tests/test_dist_slack_backend.py @@ -44,28 +44,36 @@ def _prepare_env(self, env): env.set_id(0) def _run_env(self, env): + # print("Run env starts") obs = env.reset() done = False ts = 0 aor = np.zeros((self.max_iter_real, env.n_line)) gen_p = np.zeros((self.max_iter_real, env.n_gen)) + info = None + # print("While starts") while not done: + # print("\tbefore step") obs, reward, done, info = env.step(env.action_space()) aor[ts,:] = obs.a_or gen_p[ts,:] = obs.gen_p ts += 1 if ts >= self.max_iter_real: break - return ts, done, aor, gen_p + # print("Run env stops") + return ts, done, aor, gen_p, info def test_different(self): self._aux_test_different(self.env_ss, self.env_ds) def _aux_test_different(self, env_ss, env_ds): - ts_ss, done_ss, aor_ss, gen_p_ss = self._run_env(env_ss) - ts_ds, done_ds, aor_ds, gen_p_ds = self._run_env(env_ds) + # print("before single slack") + ts_ss, done_ss, aor_ss, gen_p_ss, info_ss = self._run_env(env_ss) + # print("before dist slack") + ts_ds, done_ds, aor_ds, gen_p_ds, info_ds = self._run_env(env_ds) + # print("after dist slack") - assert ts_ss == ts_ds + assert ts_ss == ts_ds, f"ts_ss={ts_ss} != {ts_ds}=ts_ds: info_ds={info_ds['exception']}, info_ss={info_ss['exception']}" assert done_ss == done_ds # non redispatchable gen are not affected diff --git a/src/BaseNRSolver.h b/src/BaseNRSolver.h index 5a461233..dc1019e8 100644 --- a/src/BaseNRSolver.h +++ b/src/BaseNRSolver.h @@ -142,7 +142,8 @@ class BaseNRSolver : public BaseSolver void reset_if_needed(){ if(_solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || - _solver_control.has_ybus_some_coeffs_zero()){ + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.has_slack_participate_changed()){ reset(); } } diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp index 624b5236..843d1a41 100644 --- a/src/BaseSolver.cpp +++ b/src/BaseSolver.cpp @@ -148,32 +148,35 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, // pq: list of index of pq nodes // nb_bus: total number of bus in the grid // returns: res: the ids of all the slack buses (by def: not PV and not PQ) - - Eigen::VectorXi res(nb_bus - pv.size() - pq.size()); + int nb_slacks = nb_bus - pv.size() - pq.size(); + if(nb_slacks == 0){ + // TODO DEBUG MODE + throw std::runtime_error("BaseSolver::extract_slack_bus_id: All buses are tagged as PV or PQ, there can be no slack."); + } + Eigen::VectorXi res(nb_slacks); Eigen::Index i_res = 0; - + // run through both pv and pq nodes and declare they are not slack bus std::vector tmp(nb_bus, true); - for(unsigned int k=0; k < pv.size(); ++k) - { - tmp[pv[k]] = false; - } - for(unsigned int k=0; k < pq.size(); ++k) - { - tmp[pq[k]] = false; - } + for(auto pv_i : pv) tmp[pv_i] = false; + for(auto pq_i : pq) tmp[pq_i] = false; + // run through all buses for(unsigned int k=0; k < nb_bus; ++k) { if(tmp[k]) { + if((i_res >= nb_slacks)){ + // TODO DEBUG MODE + throw std::runtime_error("BaseSolver::extract_slack_bus_id: too many slack found. Maybe a bus is both PV and PQ ?"); + } res[i_res] = k; ++i_res; } } if(res.size() != i_res){ // TODO DEBUG MODE - throw std::runtime_error("BaseSolver::extract_slack_bus_id: No slack bus is found in your grid"); + throw std::runtime_error("BaseSolver::extract_slack_bus_id: Some slacks are not found in your grid."); } return res; } diff --git a/src/DCSolver.h b/src/DCSolver.h index eeb14a24..5e0e0352 100644 --- a/src/DCSolver.h +++ b/src/DCSolver.h @@ -10,7 +10,7 @@ #define DCSOLVER_H #include "BaseSolver.h" -// TODO make err_ more explicit: use an enum + template class BaseDCSolver: public BaseSolver { diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp index 87c2fb3a..30b903bc 100644 --- a/src/DCSolver.tpp +++ b/src/DCSolver.tpp @@ -31,7 +31,8 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix return false; } if(_solver_control.need_reset_solver() || - _solver_control.has_dimension_changed()){ + _solver_control.has_dimension_changed() || + _solver_control.has_ybus_some_coeffs_zero()){ reset(); } @@ -48,18 +49,28 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // TODO SLACK (for now i put all slacks as PV, except the first one) // this should be handled in Sbus, because we know the amount of power absorbed by the slack // so we can compute it correctly ! + // std::cout << "\t\t\tretrieve_pv_with_slack \n"; + // std::cout << "slack_ids: "; + // for(auto el: slack_ids) std::cout << el << ", "; + // std::cout << std::endl; my_pv_ = retrieve_pv_with_slack(slack_ids, pv); + // std::cout << "my_pv_: "; + // for(auto el: my_pv_) std::cout << el << ", "; + // std::cout << std::endl; // const Eigen::VectorXi & my_pv = pv; // find the slack buses + // std::cout << "\t\t\textract_slack_bus_id \n"; slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); // corresp bus -> solverbus + // std::cout << "\t\t\tfill_mat_bus_id \n"; fill_mat_bus_id(sizeYbus_with_slack_); // remove the slack bus from Ybus // and extract only real part + // std::cout << "\t\t\tfill_dcYbus_noslack \n"; fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); #ifdef __COUT_TIMES @@ -72,6 +83,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix #endif // __COUT_TIMES bool just_factorize = false; if(need_factorize_){ + // std::cout << "\t\t\t\t need_factorize_ \n"; ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; @@ -82,6 +94,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } // remove the slack bus from Sbus + // std::cout << "\t\t\t dcSbus_noslack_ \n"; dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); for (int k=0; k < sizeYbus_with_slack_; ++k){ if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus @@ -90,6 +103,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } // solve for theta: Sbus = dcY . theta (make a copy to keep dcSbus_noslack_) + // std::cout << "\t\t\t Va_dc_without_slack \n"; RealVect Va_dc_without_slack = dcSbus_noslack_; ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, just_factorize); if(error != ErrorType::NoError){ diff --git a/src/DataGen.cpp b/src/DataGen.cpp index e92152ee..973f92e5 100644 --- a/src/DataGen.cpp +++ b/src/DataGen.cpp @@ -445,7 +445,7 @@ void DataGen::update_slack_weights(Eigen::Ref(bus_vn_kv_.size()); total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.); total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.); @@ -523,12 +525,13 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, if (solver_control.need_reset_solver() || solver_control.has_dimension_changed() || solver_control.has_slack_participate_changed() || - solver_control.has_pq_changed()) { - // std::cout << "fillSbus_me;" << std::endl; + solver_control.has_pq_changed() || + solver_control.need_recompute_sbus()) { + // std::cout << "\t\tfillSbus_me;" << std::endl; fillSbus_me(Sbus, is_ac, id_me_to_solver); // std::cout << "fillSbus_me;" << std::endl; } - + // std::cout << "\t\tstatic_cast(id_solver_to_me.size());" << std::endl; const int nb_bus_solver = static_cast(id_solver_to_me.size()); CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_); for(int bus_solver_id = 0; bus_solver_id < nb_bus_solver; ++bus_solver_id){ @@ -542,6 +545,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit, // for(auto el: V) std::cout << el << ", "; // std::cout << std::endl; // std::cout << "nb_bus_solver " << nb_bus_solver << std::endl; + // std::cout << "\t\tend pre_process_solver" << std::endl; return V; } @@ -709,7 +713,6 @@ void GridModel::fillpv_pq(const std::vector& id_me_to_solver, const SolverControl & solver_control) { // Nothing to do if neither pv, nor pq nor the dimension of the problem has changed - if(!solver_control.has_pq_changed() && !solver_control.has_pv_changed() && !solver_control.has_dimension_changed()) return; // init pq and pv vector // TODO remove the order here..., i could be faster in this piece of code (looping once through the buses) @@ -840,7 +843,7 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, slack_bus_id_dc_solver_, is_ac, solver_control_); - // std::cout << "after pre proces\n"; + // std::cout << "\tafter pre proces (dc)\n"; // start the solver if(solver_control_.need_reset_solver() || solver_control_.has_dimension_changed() || @@ -848,7 +851,7 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, solver_control_.has_pv_changed() || solver_control_.has_slack_weight_changed()){ // TODO smarter solver: this is done both in ac and in dc ! - // std::cout << "get_slack_weights" << std::endl; + // std::cout << "\tget_slack_weights" << std::endl; slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_); } // std::cout << "V (init to dc pf)\n"; @@ -857,11 +860,12 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, // std::cout << "dcSbus (init to dc pf)\n"; // for(auto el: dcSbus_) std::cout << el << ", "; // std::cout << std::endl; + // std::cout << "\tbefore compute dc pf" << std::endl; conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol); - // std::cout << "after compute_pf\n"; + // std::cout << "\tprocess_results (dc) \n"; // store results (fase -> because I am in dc mode) - process_results(conv, res, Vinit, false, id_me_to_dc_solver_); - // std::cout << "after compute_pf\n"; + process_results(conv, res, Vinit, is_ac, id_me_to_dc_solver_); + // std::cout << "\tafter compute_pf\n"; return res; } From d37aa429b29b55e18aacb4a3ea7dbc9758db6a17 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 18 Dec 2023 09:32:43 +0100 Subject: [PATCH 37/66] fixing some broken tests --- src/BaseNRSolver.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BaseNRSolver.h b/src/BaseNRSolver.h index dc1019e8..bde0d6aa 100644 --- a/src/BaseNRSolver.h +++ b/src/BaseNRSolver.h @@ -143,7 +143,10 @@ class BaseNRSolver : public BaseSolver if(_solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || _solver_control.has_ybus_some_coeffs_zero() || - _solver_control.has_slack_participate_changed()){ + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ){ reset(); } } From 44cea300999c3603cba2f652912ae921c2c56292 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 18 Dec 2023 15:34:32 +0100 Subject: [PATCH 38/66] huge renaming to clean src (cpp) repo --- CHANGELOG.rst | 3 + docs/benchmarks.rst | 27 +- setup.py | 36 +- src/BaseMultiplePowerflow.cpp | 4 +- src/ChooseSolver.h | 17 +- src/GridModel.cpp | 20 +- src/GridModel.h | 90 ++--- src/SecurityAnalysis.cpp | 3 +- src/Solvers.cpp | 2 +- src/Solvers.h | 59 +-- .../DCLineContainer.cpp} | 15 +- .../DCLineContainer.h} | 43 ++- .../GeneratorContainer.cpp} | 74 ++-- .../GeneratorContainer.h} | 37 +- .../GenericContainer.cpp} | 33 +- .../GenericContainer.h} | 27 +- .../LineContainer.cpp} | 59 +-- .../LineContainer.h} | 41 +- .../LoadContainer.cpp} | 34 +- .../LoadContainer.h} | 30 +- .../SGenContainer.cpp} | 67 ++-- .../SGenContainer.h} | 30 +- .../ShuntContainer.cpp} | 43 +-- .../ShuntContainer.h} | 32 +- .../TrafoContainer.cpp} | 97 ++--- .../TrafoContainer.h} | 31 +- src/help_fun_msg.cpp | 65 ++-- src/help_fun_msg.h | 14 +- src/{ => linear_solvers}/CKTSOSolver.cpp | 0 src/{ => linear_solvers}/CKTSOSolver.h | 0 src/{ => linear_solvers}/KLUSolver.cpp | 0 src/{ => linear_solvers}/KLUSolver.h | 0 src/{ => linear_solvers}/NICSLUSolver.cpp | 0 src/{ => linear_solvers}/NICSLUSolver.h | 0 src/{ => linear_solvers}/SparseLUSolver.cpp | 0 src/{ => linear_solvers}/SparseLUSolver.h | 0 src/main.cpp | 355 +++++++++--------- .../BaseAlgo.cpp} | 24 +- .../BaseAlgo.h} | 18 +- .../BaseDCAlgo.h} | 22 +- .../BaseDCAlgo.tpp} | 24 +- .../BaseFDPFAlgo.h} | 22 +- .../BaseFDPFAlgo.tpp} | 10 +- .../BaseNRAlgo.h} | 20 +- .../BaseNRAlgo.tpp} | 38 +- .../BaseNRSingleSlackAlgo.h} | 16 +- .../BaseNRSingleSlackAlgo.tpp} | 188 +++++----- .../GaussSeidelAlgo.cpp} | 6 +- .../GaussSeidelAlgo.h} | 18 +- .../GaussSeidelSynchAlgo.cpp} | 4 +- .../GaussSeidelSynchAlgo.h} | 18 +- 51 files changed, 919 insertions(+), 897 deletions(-) rename src/{DataDCLine.cpp => element_container/DCLineContainer.cpp} (88%) rename src/{DataDCLine.h => element_container/DCLineContainer.h} (92%) rename src/{DataGen.cpp => element_container/GeneratorContainer.cpp} (84%) rename src/{DataGen.h => element_container/GeneratorContainer.h} (92%) rename src/{DataGeneric.cpp => element_container/GenericContainer.cpp} (80%) rename src/{DataGeneric.h => element_container/GenericContainer.h} (89%) rename src/{DataLine.cpp => element_container/LineContainer.cpp} (90%) rename src/{DataLine.h => element_container/LineContainer.h} (91%) rename src/{DataLoad.cpp => element_container/LoadContainer.cpp} (77%) rename src/{DataLoad.h => element_container/LoadContainer.h} (89%) rename src/{DataSGen.cpp => element_container/SGenContainer.cpp} (71%) rename src/{DataSGen.h => element_container/SGenContainer.h} (90%) rename src/{DataShunt.cpp => element_container/ShuntContainer.cpp} (81%) rename src/{DataShunt.h => element_container/ShuntContainer.h} (89%) rename src/{DataTrafo.cpp => element_container/TrafoContainer.cpp} (86%) rename src/{DataTrafo.h => element_container/TrafoContainer.h} (93%) rename src/{ => linear_solvers}/CKTSOSolver.cpp (100%) rename src/{ => linear_solvers}/CKTSOSolver.h (100%) rename src/{ => linear_solvers}/KLUSolver.cpp (100%) rename src/{ => linear_solvers}/KLUSolver.h (100%) rename src/{ => linear_solvers}/NICSLUSolver.cpp (100%) rename src/{ => linear_solvers}/NICSLUSolver.h (100%) rename src/{ => linear_solvers}/SparseLUSolver.cpp (100%) rename src/{ => linear_solvers}/SparseLUSolver.h (100%) rename src/{BaseSolver.cpp => powerflow_algorithm/BaseAlgo.cpp} (87%) rename src/{BaseSolver.h => powerflow_algorithm/BaseAlgo.h} (96%) rename src/{DCSolver.h => powerflow_algorithm/BaseDCAlgo.h} (88%) rename src/{DCSolver.tpp => powerflow_algorithm/BaseDCAlgo.tpp} (92%) rename src/{BaseFDPFSolver.h => powerflow_algorithm/BaseFDPFAlgo.h} (95%) rename src/{BaseFDPFSolver.tpp => powerflow_algorithm/BaseFDPFAlgo.tpp} (93%) rename src/{BaseNRSolver.h => powerflow_algorithm/BaseNRAlgo.h} (95%) rename src/{BaseNRSolver.tpp => powerflow_algorithm/BaseNRAlgo.tpp} (94%) rename src/{BaseNRSolverSingleSlack.h => powerflow_algorithm/BaseNRSingleSlackAlgo.h} (86%) rename src/{BaseNRSolverSingleSlack.tpp => powerflow_algorithm/BaseNRSingleSlackAlgo.tpp} (65%) rename src/{GaussSeidelSolver.cpp => powerflow_algorithm/GaussSeidelAlgo.cpp} (96%) rename src/{GaussSeidelSolver.h => powerflow_algorithm/GaussSeidelAlgo.h} (81%) rename src/{GaussSeidelSynchSolver.cpp => powerflow_algorithm/GaussSeidelSynchAlgo.cpp} (95%) rename src/{GaussSeidelSynchSolver.h => powerflow_algorithm/GaussSeidelSynchAlgo.h} (68%) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c880b94f..025c310d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -39,6 +39,9 @@ Change Log - [ADDED] computation of PTPF (Power Transfer Distribution Factor) is now possible - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` +- [IMPROVED] clean ce cpp side by refactoring: making clearer the difference (linear) solver + vs powerflow algorithm and move same type of files in the same directory. This change + does not really affect python side at the moment (but will in future versions) [0.7.5] 2023-10-05 -------------------- diff --git a/docs/benchmarks.rst b/docs/benchmarks.rst index a49c428a..4ad4aa2c 100644 --- a/docs/benchmarks.rst +++ b/docs/benchmarks.rst @@ -39,7 +39,8 @@ To run the benchmark `cd` in the [benchmark](./benchmarks) folder and type: (we remind that these simulations correspond to simulation on one core of the CPU. Of course it is possible to make use of all the available cores, which would increase the number of steps that can be performed) -We compare up to 19 different solvers: +We compare up to 19 different "solvers" (combination of "linear solver used" (*eg* Eigen, KLU, CKTSO, NICSLU) +and powerflow algorithm (*eg* "Newton Raphson", or "Fast Decoupled")): - **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba acceleration). It is our reference solver. @@ -102,16 +103,24 @@ Computation time In this first subsection we compare the computation times: - **grid2op speed** from a grid2op point of view - (this include the time to compute the powerflow, plus the time to modify the powergrid plus the - time to read back the data once the powerflow has run plus the time to update the environment and - the observations etc.). It is reported in "iteration per second" (`it/s`) and represents the number of grid2op "step" + (this include the time to compute the powerflow, plus the time to modify + the powergrid plus the + time to read back the data once the powerflow has run plus the time to update + the environment and + the observations etc.). It is reported in "iteration per second" (`it/s`) and + represents the number of grid2op "step" that can be computed per second. -- **grid2op 'backend.runpf' time** corresponds to the time the solver take to perform a powerflow - as seen from grid2op (counting the resolution time and some time to check the validity of the results but - not the time to update the grid nor the grid2op environment), for lightsim2grid it includes the time to read back the data +- **grid2op 'backend.runpf' time** corresponds to the time the solver take + to perform a powerflow + as seen from grid2op (counting the resolution time and some time to check + the validity of the results but + not the time to update the grid nor the grid2op environment), for lightsim2grid + it includes the time to read back the data from c++ to python. It is reported in milli seconds (ms). -- **solver powerflow time** corresponds only to the time spent in the solver itself. It does not take into - account any of the checking, nor the transfer of the data python side etc. It is reported in milli seconds (ms) as well. +- **solver powerflow time** corresponds only to the time spent in the solver + itself. It does not take into + account any of the checking, nor the transfer of the data python side etc. + It is reported in milli seconds (ms) as well. There are two major differences between **grid2op 'backend.runpf' time** and **solver powerflow time**. In **grid2op 'backend.runpf' time** the time to initialize the solver (usually with the DC approximation) is counted (it is not in **solver powerflow time**). Secondly, diff --git a/setup.py b/setup.py index 9a0f4f08..8ddbbf5c 100644 --- a/setup.py +++ b/setup.py @@ -95,7 +95,7 @@ "be available, which is maybe ~30% slower than \"KLU\". If you are using grid2op there " "will still be a huge benefit.") -INCLUDE = INCLUDE_suitesparse +INCLUDE = ["src"] + INCLUDE_suitesparse # now add the Eigen library (header only) eigen_path = os.path.abspath(".") @@ -137,31 +137,31 @@ f"-DVERSION_MEDIUM={VERSION_MEDIUM}", f"-DVERSION_MINOR={VERSION_MINOR}"] src_files = ['src/main.cpp', + "src/powerflow_algorithm/GaussSeidelAlgo.cpp", + "src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp", + "src/powerflow_algorithm/BaseAlgo.cpp", + "src/linear_solvers/SparseLUSolver.cpp", "src/help_fun_msg.cpp", - "src/SparseLUSolver.cpp", "src/BaseConstants.cpp", "src/GridModel.cpp", - "src/DataConverter.cpp", - "src/DataLine.cpp", - "src/DataGeneric.cpp", - "src/DataShunt.cpp", - "src/DataTrafo.cpp", - "src/DataLoad.cpp", - "src/DataGen.cpp", - "src/DataSGen.cpp", - "src/DataDCLine.cpp", "src/ChooseSolver.cpp", - "src/GaussSeidelSolver.cpp", - "src/GaussSeidelSynchSolver.cpp", - "src/BaseSolver.cpp", "src/BaseMultiplePowerflow.cpp", "src/Computers.cpp", "src/SecurityAnalysis.cpp", "src/Solvers.cpp", - "src/Utils.cpp"] + "src/Utils.cpp", + "src/DataConverter.cpp", + "src/element_container/LineContainer.cpp", + "src/element_container/GenericContainer.cpp", + "src/element_container/ShuntContainer.cpp", + "src/element_container/TrafoContainer.cpp", + "src/element_container/LoadContainer.cpp", + "src/element_container/GeneratorContainer.cpp", + "src/element_container/SGenContainer.cpp", + "src/element_container/DCLineContainer.cpp"] if KLU_SOLVER_AVAILABLE: - src_files.append("src/KLUSolver.cpp") + src_files.append("src/linear_solvers/KLUSolver.cpp") extra_compile_args_tmp.append("-DKLU_SOLVER_AVAILABLE") print("INFO: Using KLU package") @@ -208,7 +208,7 @@ if include_nicslu and libnicslu_path is not None: LIBS.append(os.path.join(path_nicslu, libnicslu_path)) include_dirs.append(os.path.join(path_nicslu, "include")) - src_files.append("src/NICSLUSolver.cpp") + src_files.append("src/linear_solvers/NICSLUSolver.cpp") extra_compile_args.append("-DNICSLU_SOLVER_AVAILABLE") print("INFO: Using NICSLU package") @@ -255,7 +255,7 @@ if include_cktso and libcktso_path is not None: LIBS.append(os.path.join(path_cktso, libcktso_path)) include_dirs.append(os.path.join(path_cktso, "include")) - src_files.append("src/CKTSOSolver.cpp") + src_files.append("src/linear_solvers/CKTSOSolver.cpp") extra_compile_args.append("-DCKTSO_SOLVER_AVAILABLE") print("INFO: Using CKTSO package") diff --git a/src/BaseMultiplePowerflow.cpp b/src/BaseMultiplePowerflow.cpp index 67938697..48aac800 100644 --- a/src/BaseMultiplePowerflow.cpp +++ b/src/BaseMultiplePowerflow.cpp @@ -33,8 +33,8 @@ bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix reset(); } - // benefit from dynamic stuff and inheritance by having a method that returns a BaseSolver * + // benefit from dynamic stuff and inheritance by having a method that returns a BaseAlgo * bool compute_pf(const Eigen::SparseMatrix & Ybus, // size (nb_bus, nb_bus) CplxVect & V, // size nb_bus const CplxVect & Sbus, // size nb_bus @@ -388,9 +385,9 @@ class ChooseSolver /** returns a pointer to the current solver used **/ - const BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) const { + const BaseAlgo * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) const { if (check_right_solver_) check_right_solver(error_msg); - const BaseSolver * res; + const BaseAlgo * res; if(_solver_type == SolverType::SparseLU){res = &_solver_lu;} else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;} else if(_solver_type == SolverType::DC){res = &_solver_dc;} @@ -422,9 +419,9 @@ class ChooseSolver else throw std::runtime_error("Unknown solver type encountered (ChooseSolver get_prt_solver const)"); return res; } - BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) { + BaseAlgo * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) { if (check_right_solver_) check_right_solver(error_msg); - BaseSolver * res; + BaseAlgo * res; if(_solver_type == SolverType::SparseLU){res = &_solver_lu;} else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;} else if(_solver_type == SolverType::DC){res = &_solver_dc;} @@ -464,8 +461,8 @@ class ChooseSolver // TODO have a way to use Union here https://en.cppreference.com/w/cpp/language/union SparseLUSolver _solver_lu; SparseLUSolverSingleSlack _solver_lu_single; - GaussSeidelSolver _solver_gaussseidel; - GaussSeidelSynchSolver _solver_gaussseidelsynch; + GaussSeidelAlgo _solver_gaussseidel; + GaussSeidelSynchAlgo _solver_gaussseidelsynch; DCSolver _solver_dc; FDPF_XB_SparseLUSolver _solver_fdpf_xb_lu; FDPF_BX_SparseLUSolver _solver_fdpf_bx_lu; diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 85307d3f..5f4579a1 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -146,21 +146,21 @@ void GridModel::set_state(GridModel::StateRes & my_state) std::vector & bus_status = std::get<7>(my_state); // powerlines - DataLine::StateRes & state_lines = std::get<8>(my_state); + LineContainer::StateRes & state_lines = std::get<8>(my_state); // shunts - DataShunt::StateRes & state_shunts = std::get<9>(my_state); + ShuntContainer::StateRes & state_shunts = std::get<9>(my_state); // trafos - DataTrafo::StateRes & state_trafos = std::get<10>(my_state); + TrafoContainer::StateRes & state_trafos = std::get<10>(my_state); // generators - DataGen::StateRes & state_gens = std::get<11>(my_state); + GeneratorContainer::StateRes & state_gens = std::get<11>(my_state); // loads - DataLoad::StateRes & state_loads = std::get<12>(my_state); + LoadContainer::StateRes & state_loads = std::get<12>(my_state); // static gen - DataSGen::StateRes & state_sgens= std::get<13>(my_state); + SGenContainer::StateRes & state_sgens= std::get<13>(my_state); // storage units - DataLoad::StateRes & state_storages = std::get<14>(my_state); + LoadContainer::StateRes & state_storages = std::get<14>(my_state); // dc lines - DataDCLine::StateRes & state_dc_lines = std::get<15>(my_state); + DCLineContainer::StateRes & state_dc_lines = std::get<15>(my_state); // assign it to this instance @@ -338,7 +338,7 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, }; void GridModel::check_solution_q_values_onegen(CplxVect & res, - const DataGen::GenInfo& gen, + const GeneratorContainer::GenInfo& gen, bool check_q_limits) const{ if(check_q_limits) { @@ -626,8 +626,6 @@ void GridModel::init_slack_bus(const CplxVect & Sbus, const std::vector& id_solver_to_me, const Eigen::VectorXi & slack_bus_id_me, Eigen::VectorXi & slack_bus_id_solver){ - - const int nb_bus = static_cast(id_solver_to_me.size()); // slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size()); slack_bus_id_solver = Eigen::VectorXi::Constant(slack_bus_id_me.size(), _deactivated_bus_id); diff --git a/src/GridModel.h b/src/GridModel.h index 19717ba3..eee6cc7c 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -27,14 +27,14 @@ #include "Eigen/SparseLU" // import data classes -#include "DataGeneric.h" -#include "DataLine.h" -#include "DataShunt.h" -#include "DataTrafo.h" -#include "DataLoad.h" -#include "DataGen.h" -#include "DataSGen.h" -#include "DataDCLine.h" +#include "element_container/GenericContainer.h" +#include "element_container/LineContainer.h" +#include "element_container/ShuntContainer.h" +#include "element_container/TrafoContainer.h" +#include "element_container/LoadContainer.h" +#include "element_container/GeneratorContainer.h" +#include "element_container/SGenContainer.h" +#include "element_container/DCLineContainer.h" // import newton raphson solvers using different linear algebra solvers #include "ChooseSolver.h" @@ -42,7 +42,7 @@ // enum class SolverType; //TODO implement a BFS check to make sure the Ymatrix is "connected" [one single component] -class GridModel : public DataGeneric +class GridModel : public GenericContainer { public: typedef std::tuple< @@ -55,21 +55,21 @@ class GridModel : public DataGeneric std::vector, // bus_vn_kv std::vector, // bus_status // powerlines - DataLine::StateRes , + LineContainer::StateRes , // shunts - DataShunt::StateRes, + ShuntContainer::StateRes, // trafos - DataTrafo::StateRes, + TrafoContainer::StateRes, // gens - DataGen::StateRes, + GeneratorContainer::StateRes, // loads - DataLoad::StateRes, + LoadContainer::StateRes, // static generators - DataSGen::StateRes, + SGenContainer::StateRes, // storage units - DataLoad::StateRes, + LoadContainer::StateRes, //dc lines - DataDCLine::StateRes + DCLineContainer::StateRes > StateRes; GridModel(): @@ -101,7 +101,7 @@ class GridModel : public DataGeneric const std::vector & id_dc_solver_to_me() const {return id_dc_solver_to_me_;} // retrieve the underlying data (raw class) - const DataGen & get_generators_as_data() const {return generators_;} + const GeneratorContainer & get_generators_as_data() const {return generators_;} void turnedoff_no_pv(){generators_.turnedoff_no_pv();} // turned off generators are not pv void turnedoff_pv(){generators_.turnedoff_pv();} // turned off generators are pv bool get_turnedoff_gen_pv() {return generators_.get_turnedoff_gen_pv();} @@ -109,11 +109,11 @@ class GridModel : public DataGeneric generators_.update_slack_weights(could_be_slack, solver_control_); } - const DataSGen & get_static_generators_as_data() const {return sgens_;} - const DataLoad & get_loads_as_data() const {return loads_;} - const DataLine & get_powerlines_as_data() const {return powerlines_;} - const DataTrafo & get_trafos_as_data() const {return trafos_;} - const DataDCLine & get_dclines_as_data() const {return dc_lines_;} + const SGenContainer & get_static_generators_as_data() const {return sgens_;} + const LoadContainer & get_loads_as_data() const {return loads_;} + const LineContainer & get_powerlines_as_data() const {return powerlines_;} + const TrafoContainer & get_trafos_as_data() const {return trafos_;} + const DCLineContainer & get_dclines_as_data() const {return dc_lines_;} Eigen::Ref get_bus_vn_kv() const {return bus_vn_kv_;} std::tuple assign_slack_to_most_connected(); void consider_only_main_component(); @@ -271,7 +271,7 @@ class GridModel : public DataGeneric void tell_recompute_sbus(){solver_control_.tell_recompute_sbus();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. void tell_solver_need_reset(){solver_control_.tell_solver_need_reset();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. void tell_ybus_change_sparsity_pattern(){solver_control_.tell_ybus_change_sparsity_pattern();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. - const SolverControl & get_solver_control() const { return solver_control_;} + const SolverControl & get_solver_control() const {return solver_control_;} // dc powerflow CplxVect dc_pf(const CplxVect & Vinit, @@ -317,14 +317,14 @@ class GridModel : public DataGeneric Eigen::Index nb_trafo() const {return trafos_.nb();} // read only data accessor - const DataLine & get_lines() const {return powerlines_;} - const DataDCLine & get_dclines() const {return dc_lines_;} - const DataTrafo & get_trafos() const {return trafos_;} - const DataGen & get_generators() const {return generators_;} - const DataLoad & get_loads() const {return loads_;} - const DataLoad & get_storages() const {return storages_;} - const DataSGen & get_static_generators() const {return sgens_;} - const DataShunt & get_shunts() const {return shunts_;} + const LineContainer & get_lines() const {return powerlines_;} + const DCLineContainer & get_dclines() const {return dc_lines_;} + const TrafoContainer & get_trafos() const {return trafos_;} + const GeneratorContainer & get_generators() const {return generators_;} + const LoadContainer & get_loads() const {return loads_;} + const LoadContainer & get_storages() const {return storages_;} + const SGenContainer & get_static_generators() const {return sgens_;} + const ShuntContainer & get_shunts() const {return shunts_;} const std::vector & get_bus_status() const {return bus_status_;} void set_line_names(const std::vector & names){ @@ -740,7 +740,7 @@ class GridModel : public DataGeneric int size); void check_solution_q_values( CplxVect & res, bool check_q_limits) const; - void check_solution_q_values_onegen(CplxVect & res, const DataGen::GenInfo& gen, bool check_q_limits) const; + void check_solution_q_values_onegen(CplxVect & res, const GeneratorContainer::GenInfo& gen, bool check_q_limits) const; protected: // memory for the import @@ -776,35 +776,35 @@ class GridModel : public DataGeneric std::vector id_dc_solver_to_me_; // 2. powerline - DataLine powerlines_; + LineContainer powerlines_; // 3. shunt - DataShunt shunts_; + ShuntContainer shunts_; // 4. transformers // have the r, x, h and ratio // ratio is computed from the tap, so maybe store tap num and tap_step_pct - DataTrafo trafos_; + TrafoContainer trafos_; // 5. generators RealVect total_q_min_per_bus_; RealVect total_q_max_per_bus_; Eigen::VectorXi total_gen_per_bus_; - DataGen generators_; + GeneratorContainer generators_; // 6. loads - DataLoad loads_; + LoadContainer loads_; - // 6. static generators (P,Q generators) - DataSGen sgens_; + // 7. static generators (P,Q generators) + SGenContainer sgens_; - // 7. storage units - DataLoad storages_; + // 8. storage units + LoadContainer storages_; - // hvdc - DataDCLine dc_lines_; + // 9. hvdc + DCLineContainer dc_lines_; - // 8. slack bus + // 10. slack bus // std::vector slack_bus_id_; Eigen::VectorXi slack_bus_id_ac_me_; // slack bus id, gridmodel number Eigen::VectorXi slack_bus_id_ac_solver_; // slack bus id, solver number diff --git a/src/SecurityAnalysis.cpp b/src/SecurityAnalysis.cpp index f4d5c9a1..ccab4068 100644 --- a/src/SecurityAnalysis.cpp +++ b/src/SecurityAnalysis.cpp @@ -7,6 +7,7 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. #include "SecurityAnalysis.h" + #include #include /* isfinite */ @@ -89,7 +90,7 @@ void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ } } - if(bus_1_id != DataGeneric::_deactivated_bus_id && bus_2_id != DataGeneric::_deactivated_bus_id) + if(bus_1_id != GenericContainer::_deactivated_bus_id && bus_2_id != GenericContainer::_deactivated_bus_id) { // element is connected this_cont_coeffs.push_back({bus_1_id, bus_1_id, y_ff}); diff --git a/src/Solvers.cpp b/src/Solvers.cpp index eed71c93..9cd80602 100644 --- a/src/Solvers.cpp +++ b/src/Solvers.cpp @@ -13,7 +13,7 @@ // this is why i need to define them here for every specialization. template -void BaseFDPFSolver::fillBp_Bpp(Eigen::SparseMatrix & Bp, Eigen::SparseMatrix & Bpp) const +void BaseFDPFAlgo::fillBp_Bpp(Eigen::SparseMatrix & Bp, Eigen::SparseMatrix & Bpp) const { _gridmodel->fillBp_Bpp(Bp, Bpp, XB_BX); } diff --git a/src/Solvers.h b/src/Solvers.h index 4adaac89..705be7e3 100644 --- a/src/Solvers.h +++ b/src/Solvers.h @@ -6,36 +6,39 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseNRSolver.h" -#include "BaseNRSolverSingleSlack.h" -#include "DCSolver.h" -#include "BaseFDPFSolver.h" -#include "SparseLUSolver.h" -#include "KLUSolver.h" -#include "NICSLUSolver.h" -#include "CKTSOSolver.h" +#include "powerflow_algorithm/BaseDCAlgo.h" +#include "powerflow_algorithm/BaseNRAlgo.h" +#include "powerflow_algorithm/BaseNRSingleSlackAlgo.h" +#include "powerflow_algorithm/BaseFDPFAlgo.h" +#include "powerflow_algorithm/GaussSeidelSynchAlgo.h" +#include "powerflow_algorithm/GaussSeidelAlgo.h" + +#include "linear_solvers/SparseLUSolver.h" +#include "linear_solvers/KLUSolver.h" +#include "linear_solvers/NICSLUSolver.h" +#include "linear_solvers/CKTSOSolver.h" /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen**/ -typedef BaseNRSolver SparseLUSolver; +typedef BaseNRAlgo SparseLUSolver; /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, do not consider multiple slack bus**/ -typedef BaseNRSolverSingleSlack SparseLUSolverSingleSlack; +typedef BaseNRSingleSlackAlgo SparseLUSolverSingleSlack; /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, only suitable for the DC approximation**/ -typedef BaseDCSolver DCSolver; +typedef BaseDCAlgo DCSolver; /** Solver based on Fast Decoupled, using the SparseLU decomposition of Eigen**/ -typedef BaseFDPFSolver FDPF_XB_SparseLUSolver; -typedef BaseFDPFSolver FDPF_BX_SparseLUSolver; +typedef BaseFDPFAlgo FDPF_XB_SparseLUSolver; +typedef BaseFDPFAlgo FDPF_BX_SparseLUSolver; #ifdef KLU_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the KLU linear solver**/ - typedef BaseNRSolver KLUSolver; + typedef BaseNRAlgo KLUSolver; /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack KLUSolverSingleSlack; + typedef BaseNRSingleSlackAlgo KLUSolverSingleSlack; /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/ - typedef BaseDCSolver KLUDCSolver; + typedef BaseDCAlgo KLUDCSolver; /** Solver based on Fast Decoupled, using the KLU linear solver**/ - typedef BaseFDPFSolver FDPF_XB_KLUSolver; - typedef BaseFDPFSolver FDPF_BX_KLUSolver; + typedef BaseFDPFAlgo FDPF_XB_KLUSolver; + typedef BaseFDPFAlgo FDPF_BX_KLUSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the KLU linear solver**/ @@ -51,14 +54,14 @@ typedef BaseFDPFSolver FDPF_BX_SparseLUSol #ifdef NICSLU_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license)**/ - typedef BaseNRSolver NICSLUSolver; + typedef BaseNRAlgo NICSLUSolver; /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack NICSLUSolverSingleSlack; + typedef BaseNRSingleSlackAlgo NICSLUSolverSingleSlack; /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), only suitable for the DC approximation**/ - typedef BaseDCSolver NICSLUDCSolver; + typedef BaseDCAlgo NICSLUDCSolver; /** Solver based on Fast Decoupled, using the NICSLU linear solver (needs a specific license)**/ - typedef BaseFDPFSolver FDPF_XB_NICSLUSolver; - typedef BaseFDPFSolver FDPF_BX_NICSLUSolver; + typedef BaseFDPFAlgo FDPF_XB_NICSLUSolver; + typedef BaseFDPFAlgo FDPF_BX_NICSLUSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license)**/ @@ -74,14 +77,14 @@ typedef BaseFDPFSolver FDPF_BX_SparseLUSol #ifdef CKTSO_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license)**/ - typedef BaseNRSolver CKTSOSolver; + typedef BaseNRAlgo CKTSOSolver; /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack CKTSOSolverSingleSlack; + typedef BaseNRSingleSlackAlgo CKTSOSolverSingleSlack; /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), only suitable for the DC approximation**/ - typedef BaseDCSolver CKTSODCSolver; + typedef BaseDCAlgo CKTSODCSolver; /** Solver based on Fast Decoupled, using the CKTSO linear solver (needs a specific license)**/ - typedef BaseFDPFSolver FDPF_XB_CKTSOSolver; - typedef BaseFDPFSolver FDPF_BX_CKTSOSolver; + typedef BaseFDPFAlgo FDPF_XB_CKTSOSolver; + typedef BaseFDPFAlgo FDPF_BX_CKTSOSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license)**/ diff --git a/src/DataDCLine.cpp b/src/element_container/DCLineContainer.cpp similarity index 88% rename from src/DataDCLine.cpp rename to src/element_container/DCLineContainer.cpp index c74301be..716229b0 100644 --- a/src/DataDCLine.cpp +++ b/src/element_container/DCLineContainer.cpp @@ -6,16 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataDCLine.h" +#include "DCLineContainer.h" + #include #include -DataDCLine::StateRes DataDCLine::get_state() const +DCLineContainer::StateRes DCLineContainer::get_state() const { std::vector loss_percent(loss_percent_.begin(), loss_percent_.end()); std::vector loss_mw(loss_mw_.begin(), loss_mw_.end()); std::vector status = status_; - DataDCLine::StateRes res(names_, + DCLineContainer::StateRes res(names_, from_gen_.get_state(), to_gen_.get_state(), loss_percent, @@ -24,7 +25,7 @@ DataDCLine::StateRes DataDCLine::get_state() const return res; } -void DataDCLine::set_state(DataDCLine::StateRes & my_state){ +void DCLineContainer::set_state(DCLineContainer::StateRes & my_state){ reset_results(); names_ = std::get<0>(my_state); from_gen_.set_state(std::get<1>(my_state)); @@ -37,7 +38,7 @@ void DataDCLine::set_state(DataDCLine::StateRes & my_state){ loss_mw_ = RealVect::Map(&loss_mw[0], loss_percent.size()); } -void DataDCLine::init(const Eigen::VectorXi & branch_from_id, +void DCLineContainer::init(const Eigen::VectorXi & branch_from_id, const Eigen::VectorXi & branch_to_id, const RealVect & p_mw, const RealVect & loss_percent, @@ -61,7 +62,7 @@ void DataDCLine::init(const Eigen::VectorXi & branch_from_id, to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id); } -void DataDCLine::nb_line_end(std::vector & res) const +void DCLineContainer::nb_line_end(std::vector & res) const { const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); @@ -76,7 +77,7 @@ void DataDCLine::nb_line_end(std::vector & res) const } // TODO DC LINE: one side might be in the connected comp and not the other ! -void DataDCLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void DCLineContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); diff --git a/src/DataDCLine.h b/src/element_container/DCLineContainer.h similarity index 92% rename from src/DataDCLine.h rename to src/element_container/DCLineContainer.h index b9363d2c..14abf214 100644 --- a/src/DataDCLine.h +++ b/src/element_container/DCLineContainer.h @@ -6,23 +6,22 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATADCLINE_H -#define DATADCLINE_H +#ifndef DCLINECONTAINER_H +#define DCLINECONTAINER_H #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" -#include "DataGen.h" +#include "Utils.h" +#include "GenericContainer.h" +#include "GeneratorContainer.h" -class DataDCLine : public DataGeneric +class DCLineContainer : public GenericContainer { public: class DCLineInfo @@ -39,8 +38,8 @@ class DataDCLine : public DataGeneric real_type target_vm_ex_pu; real_type loss_pct; real_type loss_mw; - DataGen::GenInfo gen_or; - DataGen::GenInfo gen_ex; + GeneratorContainer::GenInfo gen_or; + GeneratorContainer::GenInfo gen_ex; bool has_res; real_type res_p_or_mw; @@ -52,7 +51,7 @@ class DataDCLine : public DataGeneric real_type res_v_ex_kv; real_type res_theta_ex_deg; - DCLineInfo(const DataDCLine & r_data_dcline, int my_id): + DCLineInfo(const DCLineContainer & r_data_dcline, int my_id): id(-1), name(""), connected(false), @@ -109,13 +108,13 @@ class DataDCLine : public DataGeneric typedef DCLineInfo DataInfo; private: - typedef DataConstIterator DataDCLineConstIterator; + typedef GenericContainerConstIterator DCLineConstIterator; public: typedef std::tuple< std::vector, - DataGen::StateRes, - DataGen::StateRes, + GeneratorContainer::StateRes, + GeneratorContainer::StateRes, std::vector, // loss_percent std::vector, // vm_to_pu std::vector // loss_mw @@ -124,9 +123,9 @@ class DataDCLine : public DataGeneric int nb() const { return static_cast(from_gen_.nb()); } // iterator - typedef DataDCLineConstIterator const_iterator_type; - const_iterator_type begin() const {return DataDCLineConstIterator(this, 0); } - const_iterator_type end() const {return DataDCLineConstIterator(this, nb()); } + typedef DCLineConstIterator const_iterator_type; + const_iterator_type begin() const {return DCLineConstIterator(this, 0); } + const_iterator_type end() const {return DCLineConstIterator(this, nb()); } DCLineInfo operator[](int id) const { if(id < 0) @@ -141,11 +140,11 @@ class DataDCLine : public DataGeneric } // underlying generators are not pv when powerline is off - DataDCLine(): from_gen_(false), to_gen_(false) {}; + DCLineContainer(): from_gen_(false), to_gen_(false) {}; // pickle - DataDCLine::StateRes get_state() const; - void set_state(DataDCLine::StateRes & my_state); + DCLineContainer::StateRes get_state() const; + void set_state(DCLineContainer::StateRes & my_state); // TODO min_p, max_p void init(const Eigen::VectorXi & branch_from_id, @@ -297,12 +296,12 @@ class DataDCLine : public DataGeneric protected: // it is modeled as 2 generators that are "linked" together // see https://pandapower.readthedocs.io/en/v2.0.1/elements/dcline.html#electric-model - DataGen from_gen_; - DataGen to_gen_; + GeneratorContainer from_gen_; + GeneratorContainer to_gen_; RealVect loss_percent_; RealVect loss_mw_; std::vector status_; }; -#endif //DATADCLINE_H \ No newline at end of file +#endif //DCLINECONTAINER_H \ No newline at end of file diff --git a/src/DataGen.cpp b/src/element_container/GeneratorContainer.cpp similarity index 84% rename from src/DataGen.cpp rename to src/element_container/GeneratorContainer.cpp index 973f92e5..254813f9 100644 --- a/src/DataGen.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,11 +6,11 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataGen.h" +#include "GeneratorContainer.h" #include #include -void DataGen::init(const RealVect & generators_p, +void GeneratorContainer::init(const RealVect & generators_p, const RealVect & generators_v, const RealVect & generators_min_q, const RealVect & generators_max_q, @@ -24,7 +24,7 @@ void DataGen::init(const RealVect & generators_p, if(min_q_.size() != max_q_.size()) { std::ostringstream exc_; - exc_ << "DataGen::init: Impossible to initialize generator with generators_min_q of size "; + exc_ << "GeneratorContainer::init: Impossible to initialize generator with generators_min_q of size "; exc_ << min_q_.size(); exc_ << " and generators_max_q of size "; exc_ << max_q_.size(); @@ -36,7 +36,7 @@ void DataGen::init(const RealVect & generators_p, if (min_q_(gen_id) > max_q_(gen_id)) { std::ostringstream exc_; - exc_ << "DataGen::init: Impossible to initialize generator min_q being above max_q for generator "; + exc_ << "GeneratorContainer::init: Impossible to initialize generator min_q being above max_q for generator "; exc_ << gen_id; throw std::runtime_error(exc_.str()); } @@ -49,7 +49,7 @@ void DataGen::init(const RealVect & generators_p, q_mvar_ = RealVect::Zero(generators_p.size()); } -void DataGen::init_full(const RealVect & generators_p, +void GeneratorContainer::init_full(const RealVect & generators_p, const RealVect & generators_v, const RealVect & generators_q, const std::vector & voltage_regulator_on, @@ -64,7 +64,7 @@ void DataGen::init_full(const RealVect & generators_p, } -DataGen::StateRes DataGen::get_state() const +GeneratorContainer::StateRes GeneratorContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector vm_pu(vm_pu_.begin(), vm_pu_.end()); @@ -76,13 +76,13 @@ DataGen::StateRes DataGen::get_state() const std::vector slack_bus = gen_slackbus_; std::vector voltage_regulator_on = voltage_regulator_on_; std::vector slack_weight = gen_slack_weight_; - DataGen::StateRes res(names_, turnedoff_gen_pv_, voltage_regulator_on, + GeneratorContainer::StateRes res(names_, turnedoff_gen_pv_, voltage_regulator_on, p_mw, vm_pu, q_mvar, min_q, max_q, bus_id, status, slack_bus, slack_weight); return res; } -void DataGen::set_state(DataGen::StateRes & my_state) +void GeneratorContainer::set_state(GeneratorContainer::StateRes & my_state) { reset_results(); names_ = std::get<0>(my_state); @@ -114,7 +114,7 @@ void DataGen::set_state(DataGen::StateRes & my_state) gen_slack_weight_ = slack_weight; } -RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver){ +RealVect GeneratorContainer::get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver){ const int nb_gen = nb(); int bus_id_me, bus_id_solver; RealVect res = RealVect::Zero(nb_bus_solver); @@ -126,7 +126,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGen::get_slack_weights: Generator with id "; + exc_ << "GeneratorContainer::get_slack_weights: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -139,7 +139,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto return res; } -void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void GeneratorContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; cplx_type tmp; @@ -152,7 +152,7 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::fillSbus: Generator with id "; + exc_ << "GeneratorContainer::fillSbus: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -166,7 +166,7 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv } } -void DataGen::fillpv(std::vector & bus_pv, +void GeneratorContainer::fillpv(std::vector & bus_pv, std::vector & has_bus_been_added, const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const @@ -184,7 +184,7 @@ void DataGen::fillpv(std::vector & bus_pv, if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::fillpv: Generator with id "; + exc_ << "GeneratorContainer::fillpv: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -197,7 +197,7 @@ void DataGen::fillpv(std::vector & bus_pv, } } -void DataGen::compute_results(const Eigen::Ref & Va, +void GeneratorContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -211,7 +211,7 @@ void DataGen::compute_results(const Eigen::Ref & Va, res_p_ = p_mw_; } -void DataGen::reset_results(){ +void GeneratorContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV @@ -219,7 +219,7 @@ void DataGen::reset_results(){ // bus_slack_weight_ = RealVect(); } -void DataGen::get_vm_for_dc(RealVect & Vm){ +void GeneratorContainer::get_vm_for_dc(RealVect & Vm){ const int nb_gen = nb(); int bus_id_me; for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ @@ -235,14 +235,14 @@ void DataGen::get_vm_for_dc(RealVect & Vm){ } } -void DataGen::change_p(int gen_id, real_type new_p, SolverControl & solver_control) +void GeneratorContainer::change_p(int gen_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_p: Impossible to change the active value of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_p: Impossible to change the active value of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -261,14 +261,14 @@ void DataGen::change_p(int gen_id, real_type new_p, SolverControl & solver_contr p_mw_(gen_id) = new_p; } -void DataGen::change_q(int gen_id, real_type new_q, SolverControl & solver_control) +void GeneratorContainer::change_q(int gen_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_q: Impossible to change the reactive value of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_q: Impossible to change the reactive value of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -279,14 +279,14 @@ void DataGen::change_q(int gen_id, real_type new_q, SolverControl & solver_contr q_mvar_(gen_id) = new_q; } -void DataGen::change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control) +void GeneratorContainer::change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_p: Impossible to change the voltage setpoint of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_p: Impossible to change the voltage setpoint of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -295,7 +295,7 @@ void DataGen::change_v(int gen_id, real_type new_v_pu, SolverControl & solver_co vm_pu_(gen_id) = new_v_pu; } -void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) const +void GeneratorContainer::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; @@ -311,7 +311,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::set_vm: Generator with id "; + exc_ << "GeneratorContainer::set_vm: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -331,7 +331,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c } } -Eigen::VectorXi DataGen::get_slack_bus_id() const{ +Eigen::VectorXi GeneratorContainer::get_slack_bus_id() const{ std::vector tmp; tmp.reserve(gen_slackbus_.size()); Eigen::VectorXi res; @@ -343,17 +343,17 @@ Eigen::VectorXi DataGen::get_slack_bus_id() const{ if(!is_in_vect(my_bus, tmp)) tmp.push_back(my_bus); } } - if(tmp.empty()) throw std::runtime_error("DataGen::get_slack_bus_id: no generator are tagged slack bus for this grid."); + if(tmp.empty()) throw std::runtime_error("GeneratorContainer::get_slack_bus_id: no generator are tagged slack bus for this grid."); res = Eigen::VectorXi::Map(tmp.data(), tmp.size()); // force the copy of the data apparently return res; } -void DataGen::set_p_slack(const RealVect& node_mismatch, +void GeneratorContainer::set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver) { if(bus_slack_weight_.size() == 0){ // TODO DEBUG MODE: perform this check only in debug mode - throw std::runtime_error("DataGen::set_p_slack: Impossible to set the active value of generators for the slack bus: no known slack (you should haved called DataGen::get_slack_weights first)"); + throw std::runtime_error("Generator::set_p_slack: Impossible to set the active value of generators for the slack bus: no known slack (you should haved called Generator::get_slack_weights first)"); } const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ @@ -372,7 +372,7 @@ void DataGen::set_p_slack(const RealVect& node_mismatch, } } -void DataGen::init_q_vector(int nb_bus, +void GeneratorContainer::init_q_vector(int nb_bus, Eigen::VectorXi & total_gen_per_bus, RealVect & total_q_min_per_bus, RealVect & total_q_max_per_bus) const // delta_q_per_gen_) // total number of bus on the grid @@ -392,7 +392,7 @@ void DataGen::init_q_vector(int nb_bus, } } -void DataGen::set_q(const RealVect & reactive_mismatch, +void GeneratorContainer::set_q(const RealVect & reactive_mismatch, const std::vector & id_grid_to_solver, bool ac, const Eigen::VectorXi & total_gen_per_bus, @@ -431,7 +431,7 @@ void DataGen::set_q(const RealVect & reactive_mismatch, } -void DataGen::update_slack_weights(Eigen::Ref > could_be_slack, +void GeneratorContainer::update_slack_weights(Eigen::Ref > could_be_slack, SolverControl & solver_control) { const int nb_gen = nb(); @@ -456,7 +456,7 @@ void DataGen::update_slack_weights(Eigen::Ref & bus_status) const { +void GeneratorContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { @@ -465,7 +465,7 @@ void DataGen::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::reconnect_connected_buses: Generator with id "; + exc_ << "Generator::reconnect_connected_buses: Generator with id "; exc_ << gen_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_gen(...)` ?."; throw std::runtime_error(exc_.str()); @@ -474,7 +474,7 @@ void DataGen::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataGen::gen_p_per_bus(std::vector & res) const +void GeneratorContainer::gen_p_per_bus(std::vector & res) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) @@ -485,7 +485,7 @@ void DataGen::gen_p_per_bus(std::vector & res) const } } -void DataGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void GeneratorContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_gen = nb(); SolverControl unused_solver_control; for(int gen_id = 0; gen_id < nb_gen; ++gen_id) diff --git a/src/DataGen.h b/src/element_container/GeneratorContainer.h similarity index 92% rename from src/DataGen.h rename to src/element_container/GeneratorContainer.h index 0f531cb5..d9b854d1 100644 --- a/src/DataGen.h +++ b/src/element_container/GeneratorContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,20 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATAGEN_H -#define DATAGEN_H +#ifndef GENERATORCONTAINER_H +#define GENERATORCONTAINER_H #include #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class represents the list of all generators. @@ -30,7 +29,7 @@ The convention used for the generator is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/gen.html#electric-model **/ -class DataGen: public DataGeneric +class GeneratorContainer: public GenericContainer { public: class GenInfo @@ -57,7 +56,7 @@ class DataGen: public DataGeneric real_type res_v_kv; real_type res_theta_deg; - GenInfo(const DataGen & r_data_gen, int my_id): + GenInfo(const GeneratorContainer & r_data_gen, int my_id): id(-1), name(""), connected(false), @@ -108,7 +107,7 @@ class DataGen: public DataGeneric typedef GenInfo DataInfo; private: - typedef DataConstIterator DataGenConstIterator; + typedef GenericContainerConstIterator GeneratorConstIterator; public: typedef std::tuple< @@ -126,8 +125,8 @@ class DataGen: public DataGeneric std::vector // gen_slack_weight_ > StateRes; - DataGen():turnedoff_gen_pv_(true){}; - DataGen(bool turnedoff_gen_pv):turnedoff_gen_pv_(turnedoff_gen_pv) {}; + GeneratorContainer():turnedoff_gen_pv_(true){}; + GeneratorContainer(bool turnedoff_gen_pv):turnedoff_gen_pv_(turnedoff_gen_pv) {}; // TODO add pmin and pmax here ! void init(const RealVect & generators_p, @@ -149,9 +148,9 @@ class DataGen: public DataGeneric int nb() const { return static_cast(p_mw_.size()); } // iterator - typedef DataGenConstIterator const_iterator_type; - const_iterator_type begin() const {return DataGenConstIterator(this, 0); } - const_iterator_type end() const {return DataGenConstIterator(this, nb()); } + typedef GeneratorConstIterator const_iterator_type; + const_iterator_type begin() const {return GeneratorConstIterator(this, 0); } + const_iterator_type end() const {return GeneratorConstIterator(this, nb()); } GenInfo operator[](int id) const { if(id < 0) @@ -166,8 +165,8 @@ class DataGen: public DataGeneric } // pickle - DataGen::StateRes get_state() const; - void set_state(DataGen::StateRes & my_state ); + GeneratorContainer::StateRes get_state() const; + void set_state(GeneratorContainer::StateRes & my_state ); // slack handling /** @@ -176,7 +175,7 @@ class DataGen: public DataGeneric **/ void add_slackbus(int gen_id, real_type weight, SolverControl & solver_control){ // TODO DEBUG MODE - if(weight <= 0.) throw std::runtime_error("DataGen::add_slackbus Cannot assign a negative weight to the slack bus."); + if(weight <= 0.) throw std::runtime_error("GeneratorContainer::add_slackbus Cannot assign a negative weight to the slack bus."); if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); gen_slackbus_[gen_id] = true; if(gen_slack_weight_[gen_id] != weight) solver_control.tell_slack_weight_changed(); @@ -212,7 +211,7 @@ class DataGen: public DataGeneric } } // TODO DEBUG MODE - if(res_gen_id == -1) throw std::runtime_error("DataGen::assign_slack_bus No generator connected to the desired buses"); + if(res_gen_id == -1) throw std::runtime_error("GeneratorContainer::assign_slack_bus No generator connected to the desired buses"); return res_gen_id; } @@ -348,4 +347,4 @@ class DataGen: public DataGeneric bool turnedoff_gen_pv_; // are turned off generators (including one with p=0) pv ? }; -#endif //DATAGEN_H +#endif //GENERATORCONTAINER_H diff --git a/src/DataGeneric.cpp b/src/element_container/GenericContainer.cpp similarity index 80% rename from src/DataGeneric.cpp rename to src/element_container/GenericContainer.cpp index 3b7c330c..6daa4fe7 100644 --- a/src/DataGeneric.cpp +++ b/src/element_container/GenericContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,14 +6,15 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataGeneric.h" +#include "GenericContainer.h" + #include #include -const int DataGeneric::_deactivated_bus_id = -1; +const int GenericContainer::_deactivated_bus_id = -1; // TODO all functions bellow are generic ! Make a base class for that -void DataGeneric::_get_amps(RealVect & a, const RealVect & p, const RealVect & q, const RealVect & v){ +void GenericContainer::_get_amps(RealVect & a, const RealVect & p, const RealVect & q, const RealVect & v){ const real_type _1_sqrt_3 = 1.0 / std::sqrt(3.); RealVect p2q2 = p.array() * p.array() + q.array() * q.array(); p2q2 = p2q2.array().cwiseSqrt(); @@ -26,22 +27,22 @@ void DataGeneric::_get_amps(RealVect & a, const RealVect & p, const RealVect & q } a = p2q2.array() * _1_sqrt_3 / v_tmp.array(); } -void DataGeneric::_reactivate(int el_id, std::vector & status){ +void GenericContainer::_reactivate(int el_id, std::vector & status){ bool val = status.at(el_id); status.at(el_id) = true; //TODO why it's needed to do that again } -void DataGeneric::_deactivate(int el_id, std::vector & status){ +void GenericContainer::_deactivate(int el_id, std::vector & status){ bool val = status.at(el_id); status.at(el_id) = false; //TODO why it's needed to do that again } -void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus){ +void GenericContainer::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus){ // bus id here "me_id" and NOT "solver_id" // throw error: object id does not exist if(el_id >= el_bus_ids.size()) { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id "; + exc_ << "GenericContainer::_change_bus: Cannot change the bus of element with id "; exc_ << el_id; exc_ << " while the grid counts "; exc_ << el_bus_ids.size(); @@ -52,7 +53,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id "; + exc_ << "GenericContainer::_change_bus: Cannot change the bus of element with id "; exc_ << el_id; exc_ << " (id should be >= 0)"; throw std::out_of_range(exc_.str()); @@ -63,7 +64,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change an element to bus "; + exc_ << "GenericContainer::_change_bus: Cannot change an element to bus "; exc_ << new_bus_me_id; exc_ << " There are only "; exc_ << nb_bus; @@ -74,7 +75,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: new bus id should be >=0 and not "; + exc_ << "GenericContainer::_change_bus: new bus id should be >=0 and not "; exc_ << new_bus_me_id; throw std::out_of_range(exc_.str()); } @@ -92,7 +93,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el bus_me_id = new_bus_me_id; } -int DataGeneric::_get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_) +int GenericContainer::_get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_) { int res; bool val = status_.at(el_id); // also check if the el_id is out of bound @@ -103,7 +104,7 @@ int DataGeneric::_get_bus(int el_id, const std::vector & status_, const Ei return res; } -void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, +void GenericContainer::v_kv_from_vpu(const Eigen::Ref & Va, const Eigen::Ref & Vm, const std::vector & status, int nb_element, @@ -120,7 +121,7 @@ void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, if(bus_solver_id == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::v_kv_from_vpu: The element of id "; + exc_ << "GenericContainer::v_kv_from_vpu: The element of id "; exc_ << bus_solver_id; exc_ << " is connected to a disconnected bus"; throw std::runtime_error(exc_.str()); @@ -131,7 +132,7 @@ void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, } -void DataGeneric::v_deg_from_va(const Eigen::Ref & Va, +void GenericContainer::v_deg_from_va(const Eigen::Ref & Va, const Eigen::Ref & Vm, const std::vector & status, int nb_element, @@ -148,7 +149,7 @@ void DataGeneric::v_deg_from_va(const Eigen::Ref & Va, if(bus_solver_id == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::v_deg_from_va: The element of id "; + exc_ << "GenericContainer::v_deg_from_va: The element of id "; exc_ << bus_solver_id; exc_ << " is connected to a disconnected bus"; throw std::runtime_error(exc_.str()); diff --git a/src/DataGeneric.h b/src/element_container/GenericContainer.h similarity index 89% rename from src/DataGeneric.h rename to src/element_container/GenericContainer.h index 768cc7dd..c0e6da36 100644 --- a/src/DataGeneric.h +++ b/src/element_container/GenericContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,23 +6,22 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATAGENERIC_H -#define DATAGENERIC_H +#ifndef GENERIC_CONTAINER_H +#define GENERIC_CONTAINER_H #include // for std::find -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" +#include "Utils.h" #include "BaseConstants.h" // iterator type template -class DataConstIterator +class GenericContainerConstIterator { protected: typedef typename DataType::DataInfo DataInfo; @@ -34,22 +33,22 @@ class DataConstIterator DataInfo my_info; // functions - DataConstIterator(const DataType * const data_, int id): + GenericContainerConstIterator(const DataType * const data_, int id): _p_data_(data_), my_id(id), my_info(*data_, id) {}; const DataInfo& operator*() const { return my_info; } - bool operator==(const DataConstIterator & other) const { return (my_id == other.my_id) && (_p_data_ == other._p_data_); } - bool operator!=(const DataConstIterator & other) const { return !(*this == other); } - DataConstIterator & operator++() + bool operator==(const GenericContainerConstIterator & other) const { return (my_id == other.my_id) && (_p_data_ == other._p_data_); } + bool operator!=(const GenericContainerConstIterator & other) const { return !(*this == other); } + GenericContainerConstIterator & operator++() { ++my_id; my_info = DataInfo(*_p_data_, my_id); return *this; } - DataConstIterator & operator--() + GenericContainerConstIterator & operator--() { --my_id; my_info = DataInfo(*_p_data_, my_id); @@ -63,7 +62,7 @@ class DataConstIterator /** Base class for every object that can be manipulated **/ -class DataGeneric : public BaseConstants +class GenericContainer : public BaseConstants { public: @@ -109,7 +108,7 @@ class DataGeneric : public BaseConstants } /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/ - virtual ~DataGeneric() {}; + virtual ~GenericContainer() {}; protected: std::vector names_; @@ -168,5 +167,5 @@ class DataGeneric : public BaseConstants bool is_in_vect(int val, const T & cont) const {return std::find(cont.begin(), cont.end(), val) != cont.end();} }; -#endif // DATAGENERIC_H +#endif // GENERIC_CONTAINER_H diff --git a/src/DataLine.cpp b/src/element_container/LineContainer.cpp similarity index 90% rename from src/DataLine.cpp rename to src/element_container/LineContainer.cpp index 984f5b27..610aa458 100644 --- a/src/DataLine.cpp +++ b/src/element_container/LineContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,10 +6,11 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataLine.h" +#include "LineContainer.h" + #include -void DataLine::init(const RealVect & branch_r, +void LineContainer::init(const RealVect & branch_r, const RealVect & branch_x, const CplxVect & branch_h, const Eigen::VectorXi & branch_from_id, @@ -38,7 +39,7 @@ void DataLine::init(const RealVect & branch_r, _update_model_coeffs(); } -void DataLine::init(const RealVect & branch_r, +void LineContainer::init(const RealVect & branch_r, const RealVect & branch_x, const CplxVect & branch_h_or, const CplxVect & branch_h_ex, @@ -68,7 +69,7 @@ void DataLine::init(const RealVect & branch_r, _update_model_coeffs(); } -DataLine::StateRes DataLine::get_state() const +LineContainer::StateRes LineContainer::get_state() const { std::vector branch_r(powerlines_r_.begin(), powerlines_r_.end()); std::vector branch_x(powerlines_x_.begin(), powerlines_x_.end()); @@ -77,10 +78,10 @@ DataLine::StateRes DataLine::get_state() const std::vector branch_from_id(bus_or_id_.begin(), bus_or_id_.end()); std::vector branch_to_id(bus_ex_id_.begin(), bus_ex_id_.end()); std::vector status = status_; - DataLine::StateRes res(names_, branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); + LineContainer::StateRes res(names_, branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); return res; } -void DataLine::set_state(DataLine::StateRes & my_state) +void LineContainer::set_state(LineContainer::StateRes & my_state) { reset_results(); names_ = std::get<0>(my_state); @@ -107,7 +108,7 @@ void DataLine::set_state(DataLine::StateRes & my_state) _update_model_coeffs(); } -void DataLine::_update_model_coeffs() +void LineContainer::_update_model_coeffs() { const auto my_size = powerlines_r_.size(); @@ -143,12 +144,12 @@ void DataLine::_update_model_coeffs() } } -void DataLine::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) +void LineContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) { throw std::runtime_error("You should not use that!"); } -void DataLine::fillYbus(std::vector > & res, +void LineContainer::fillYbus(std::vector > & res, bool ac, const std::vector & id_grid_to_solver, real_type sn_mva) const @@ -169,7 +170,7 @@ void DataLine::fillYbus(std::vector > & res, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillYbusBranch: the line with id "; + exc_ << "Line::fillYbusBranch: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -178,7 +179,7 @@ void DataLine::fillYbus(std::vector > & res, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillYbusBranch: the line with id "; + exc_ << "Line::fillYbusBranch: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -204,7 +205,7 @@ void DataLine::fillYbus(std::vector > & res, } } -void DataLine::fillBp_Bpp(std::vector > & Bp, +void LineContainer::fillBp_Bpp(std::vector > & Bp, std::vector > & Bpp, const std::vector & id_grid_to_solver, real_type sn_mva, @@ -234,7 +235,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: the line with id "; + exc_ << "LineContainer::fillBp_Bpp: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -243,7 +244,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: the line with id "; + exc_ << "LineContainer::fillBp_Bpp: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -259,7 +260,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, ys_bpp = 1. / (0. + my_i * powerlines_x_(line_id)); }else{ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; + exc_ << "LineContainer::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; exc_ << line_id; throw std::runtime_error(exc_.str()); } @@ -289,7 +290,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, } -void DataLine::fillBf_for_PTDF(std::vector > & Bf, +void LineContainer::fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, int nb_powerline, @@ -306,7 +307,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << "LineContainer::fillBf_for_PTDF: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -315,7 +316,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << "LineContainer::fillBf_for_PTDF: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -337,7 +338,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, } -void DataLine::reset_results() +void LineContainer::reset_results() { res_powerline_por_ = RealVect(); // in MW res_powerline_qor_ = RealVect(); // in MVar @@ -349,7 +350,7 @@ void DataLine::reset_results() res_powerline_aex_ = RealVect(); // in kA } -void DataLine::compute_results(const Eigen::Ref & Va, +void LineContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -378,7 +379,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::compute_results: the line with id "; + exc_ << "LineContainer::compute_results: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -387,7 +388,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::compute_results: the line with id "; + exc_ << "LineContainer::compute_results: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -440,7 +441,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, _get_amps(res_powerline_aex_, res_powerline_pex_, res_powerline_qex_, res_powerline_vex_); } -void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ +void LineContainer::reconnect_connected_buses(std::vector & bus_status) const{ const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -451,7 +452,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_or_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << "LineContainer::reconnect_connected_buses: Line with id "; exc_ << line_id; exc_ << " is connected (origin) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; throw std::runtime_error(exc_.str()); @@ -462,7 +463,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_ex_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << "LineContainer::reconnect_connected_buses: Line with id "; exc_ << line_id; exc_ << " is connected (ext) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; throw std::runtime_error(exc_.str()); @@ -471,7 +472,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ } } -void DataLine::nb_line_end(std::vector & res) const{ +void LineContainer::nb_line_end(std::vector & res) const{ const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ // don't do anything if the element is disconnected @@ -483,7 +484,7 @@ void DataLine::nb_line_end(std::vector & res) const{ } } -void DataLine::get_graph(std::vector > & res) const +void LineContainer::get_graph(std::vector > & res) const { const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -496,7 +497,7 @@ void DataLine::get_graph(std::vector > & res) const } } -void DataLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void LineContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb_line = nb(); SolverControl unused_solver_control; diff --git a/src/DataLine.h b/src/element_container/LineContainer.h similarity index 91% rename from src/DataLine.h rename to src/element_container/LineContainer.h index 49f07db7..fb788177 100644 --- a/src/DataLine.h +++ b/src/element_container/LineContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,31 +6,24 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATALINE_H -#define DATALINE_H +#ifndef LINE_CONTAINER_H +#define LINE_CONTAINER_H #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all the powerlines on the grid. -The convention used for the generator is the same as in pandapower: -https://pandapower.readthedocs.io/en/latest/elements/line.html - -and for modeling of the Ybus matrix: -https://pandapower.readthedocs.io/en/latest/elements/line.html#electric-model - **/ -class DataLine : public DataGeneric +class LineContainer : public GenericContainer { public: class LineInfo @@ -60,7 +53,7 @@ class DataLine : public DataGeneric real_type res_a_ex_ka; real_type res_theta_ex_deg; - LineInfo(const DataLine & r_data_line, int my_id): + LineInfo(const LineContainer & r_data_line, int my_id): id(my_id), name(""), connected(false), @@ -118,7 +111,7 @@ class DataLine : public DataGeneric typedef LineInfo DataInfo; private: - typedef DataConstIterator DataLineConstIterator; + typedef GenericContainerConstIterator LineConstIterator; public: typedef std::tuple< @@ -132,7 +125,7 @@ class DataLine : public DataGeneric std::vector // status_ > StateRes; - DataLine() {}; + LineContainer() {}; void init(const RealVect & branch_r, const RealVect & branch_x, @@ -150,8 +143,8 @@ class DataLine : public DataGeneric ); // pickle - DataLine::StateRes get_state() const; - void set_state(DataLine::StateRes & my_state ); + LineContainer::StateRes get_state() const; + void set_state(LineContainer::StateRes & my_state ); template void check_size(const T& my_state) { @@ -159,18 +152,18 @@ class DataLine : public DataGeneric unsigned int size_th = 6; if (my_state.size() != size_th) { - std::cout << "LightSim::DataLine state size " << my_state.size() << " instead of "<< size_th << std::endl; + std::cout << "LightSim::LineContainer state size " << my_state.size() << " instead of "<< size_th << std::endl; // TODO more explicit error message - throw std::runtime_error("Invalid state when loading LightSim::DataLine"); + throw std::runtime_error("Invalid state when loading LightSim::LineContainer"); } } int nb() const { return static_cast(powerlines_r_.size()); } // make it iterable - typedef DataLineConstIterator const_iterator_type; - const_iterator_type begin() const {return DataLineConstIterator(this, 0); } - const_iterator_type end() const {return DataLineConstIterator(this, nb()); } + typedef LineConstIterator const_iterator_type; + const_iterator_type begin() const {return LineConstIterator(this, 0); } + const_iterator_type end() const {return LineConstIterator(this, nb()); } LineInfo operator[](int id) const { if(id < 0) @@ -302,4 +295,4 @@ class DataLine : public DataGeneric CplxVect ydc_tt_; }; -#endif //DATALINE_H +#endif //LINE_CONTAINER_H diff --git a/src/DataLoad.cpp b/src/element_container/LoadContainer.cpp similarity index 77% rename from src/DataLoad.cpp rename to src/element_container/LoadContainer.cpp index 4354f762..4a4de716 100644 --- a/src/DataLoad.cpp +++ b/src/element_container/LoadContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,10 +6,10 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataLoad.h" +#include "LoadContainer.h" #include -void DataLoad::init(const RealVect & loads_p, +void LoadContainer::init(const RealVect & loads_p, const RealVect & loads_q, const Eigen::VectorXi & loads_bus_id) { @@ -20,16 +20,16 @@ void DataLoad::init(const RealVect & loads_p, } -DataLoad::StateRes DataLoad::get_state() const +LoadContainer::StateRes LoadContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataLoad::StateRes res(names_, p_mw, q_mvar, bus_id, status); + LoadContainer::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } -void DataLoad::set_state(DataLoad::StateRes & my_state ) +void LoadContainer::set_state(LoadContainer::StateRes & my_state ) { reset_results(); names_ = std::get<0>(my_state); @@ -47,7 +47,7 @@ void DataLoad::set_state(DataLoad::StateRes & my_state ) } -void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void LoadContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { int nb_load = nb(); int bus_id_me, bus_id_solver; cplx_type tmp; @@ -59,7 +59,7 @@ void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLoad::fillSbus: the load with id "; + exc_ << "LoadContainer::fillSbus: the load with id "; exc_ << load_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -70,7 +70,7 @@ void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol } } -void DataLoad::compute_results(const Eigen::Ref & Va, +void LoadContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -85,19 +85,19 @@ void DataLoad::compute_results(const Eigen::Ref & Va, res_q_ = q_mvar_; } -void DataLoad::reset_results(){ +void LoadContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV } -void DataLoad::change_p(int load_id, real_type new_p, SolverControl & solver_control) +void LoadContainer::change_p(int load_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(load_id); // and this check that load_id is not out of bound if(!my_status) { std::ostringstream exc_; - exc_ << "DataLoad::change_p: Impossible to change the active value of a disconnected load (check load id "; + exc_ << "LoadContainer::change_p: Impossible to change the active value of a disconnected load (check load id "; exc_ << load_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -106,13 +106,13 @@ void DataLoad::change_p(int load_id, real_type new_p, SolverControl & solver_con p_mw_(load_id) = new_p; } -void DataLoad::change_q(int load_id, real_type new_q, SolverControl & solver_control) +void LoadContainer::change_q(int load_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(load_id); // and this check that load_id is not out of bound if(!my_status) { std::ostringstream exc_; - exc_ << "DataLoad::change_q: Impossible to change the reactive value of a disconnected load (check load id "; + exc_ << "LoadContainer::change_q: Impossible to change the reactive value of a disconnected load (check load id "; exc_ << load_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -121,7 +121,7 @@ void DataLoad::change_q(int load_id, real_type new_q, SolverControl & solver_con q_mvar_(load_id) = new_q; } -void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { +void LoadContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_load = nb(); for(int load_id = 0; load_id < nb_load; ++load_id) { @@ -130,7 +130,7 @@ void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLoad::reconnect_connected_buses: Load with id "; + exc_ << "LoadContainer::reconnect_connected_buses: Load with id "; exc_ << load_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_load(...)` ?."; throw std::runtime_error(exc_.str()); @@ -139,7 +139,7 @@ void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataLoad::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void LoadContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) diff --git a/src/DataLoad.h b/src/element_container/LoadContainer.h similarity index 89% rename from src/DataLoad.h rename to src/element_container/LoadContainer.h index 5f08d4bd..3786d522 100644 --- a/src/DataLoad.h +++ b/src/element_container/LoadContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATALOAD_H -#define DATALOAD_H +#ifndef LOAD_CONTAINER_H +#define LOAD_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all loads on the grid. @@ -31,7 +31,7 @@ NOTE: this class is also used for the storage units! So storage units are modele which entails that negative storage: the unit is discharging, power is injected in the grid, positive storage: the unit is charging, power is taken from the grid. **/ -class DataLoad : public DataGeneric +class LoadContainer : public GenericContainer { // TODO make a single class for load and shunt and just specialize the part where the // TODO powerflow equations are located (when i update the Y matrix) @@ -56,7 +56,7 @@ class DataLoad : public DataGeneric real_type res_v_kv; real_type res_theta_deg; - LoadInfo(const DataLoad & r_data_load, int my_id): + LoadInfo(const LoadContainer & r_data_load, int my_id): id(-1), name(""), connected(false), @@ -95,12 +95,12 @@ class DataLoad : public DataGeneric typedef LoadInfo DataInfo; private: - typedef DataConstIterator DataLoadConstIterator; + typedef GenericContainerConstIterator LoadContainerConstIterator; public: - typedef DataLoadConstIterator const_iterator_type; - const_iterator_type begin() const {return DataLoadConstIterator(this, 0); } - const_iterator_type end() const {return DataLoadConstIterator(this, nb()); } + typedef LoadContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return LoadContainerConstIterator(this, 0); } + const_iterator_type end() const {return LoadContainerConstIterator(this, nb()); } LoadInfo operator[](int id) const { if(id < 0) @@ -124,11 +124,11 @@ class DataLoad : public DataGeneric std::vector // status > StateRes; - DataLoad() {}; + LoadContainer() {}; // pickle (python) - DataLoad::StateRes get_state() const; - void set_state(DataLoad::StateRes & my_state ); + LoadContainer::StateRes get_state() const; + void set_state(LoadContainer::StateRes & my_state ); void init(const RealVect & loads_p, @@ -189,4 +189,4 @@ class DataLoad : public DataGeneric RealVect res_theta_; // in degree }; -#endif //DATALOAD_H +#endif //LOAD_CONTAINER_H diff --git a/src/DataSGen.cpp b/src/element_container/SGenContainer.cpp similarity index 71% rename from src/DataSGen.cpp rename to src/element_container/SGenContainer.cpp index 08d9100e..48c1baa2 100644 --- a/src/DataSGen.cpp +++ b/src/element_container/SGenContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,8 +6,9 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataSGen.h" -void DataSGen::init(const RealVect & sgen_p, +#include "SGenContainer.h" + +void SGenContainer::init(const RealVect & sgen_p, const RealVect & sgen_q, const RealVect & sgen_pmin, const RealVect & sgen_pmax, @@ -16,13 +17,13 @@ void DataSGen::init(const RealVect & sgen_p, const Eigen::VectorXi & sgen_bus_id) { int size = static_cast(sgen_p.size()); - DataGeneric::check_size(sgen_p, size, "sgen_p"); - DataGeneric::check_size(sgen_q, size, "sgen_q"); - DataGeneric::check_size(sgen_pmin, size, "sgen_pmin"); - DataGeneric::check_size(sgen_pmax, size, "sgen_pmax"); - DataGeneric::check_size(sgen_qmin, size, "sgen_qmin"); - DataGeneric::check_size(sgen_qmax, size, "sgen_qmax"); - DataGeneric::check_size(sgen_bus_id, size, "sgen_bus_id"); + GenericContainer::check_size(sgen_p, size, "sgen_p"); + GenericContainer::check_size(sgen_q, size, "sgen_q"); + GenericContainer::check_size(sgen_pmin, size, "sgen_pmin"); + GenericContainer::check_size(sgen_pmax, size, "sgen_pmax"); + GenericContainer::check_size(sgen_qmin, size, "sgen_qmin"); + GenericContainer::check_size(sgen_qmax, size, "sgen_qmax"); + GenericContainer::check_size(sgen_bus_id, size, "sgen_bus_id"); p_mw_ = sgen_p; q_mvar_ = sgen_q; @@ -34,7 +35,7 @@ void DataSGen::init(const RealVect & sgen_p, status_ = std::vector(sgen_p.size(), true); } -DataSGen::StateRes DataSGen::get_state() const +SGenContainer::StateRes SGenContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); @@ -44,11 +45,11 @@ DataSGen::StateRes DataSGen::get_state() const std::vector q_max(q_max_mvar_.begin(), q_max_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataSGen::StateRes res(names_, p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); + SGenContainer::StateRes res(names_, p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); return res; } -void DataSGen::set_state(DataSGen::StateRes & my_state ) +void SGenContainer::set_state(SGenContainer::StateRes & my_state ) { reset_results(); @@ -62,14 +63,14 @@ void DataSGen::set_state(DataSGen::StateRes & my_state ) std::vector & bus_id = std::get<7>(my_state); std::vector & status = std::get<8>(my_state); auto size = p_mw.size(); - DataGeneric::check_size(p_mw, size, "p_mw"); - DataGeneric::check_size(q_mvar, size, "q_mvar"); - DataGeneric::check_size(p_min, size, "p_min"); - DataGeneric::check_size(p_max, size, "p_max"); - DataGeneric::check_size(q_min, size, "q_min"); - DataGeneric::check_size(q_max, size, "q_max"); - DataGeneric::check_size(bus_id, size, "bus_id"); - DataGeneric::check_size(status, size, "status"); + GenericContainer::check_size(p_mw, size, "p_mw"); + GenericContainer::check_size(q_mvar, size, "q_mvar"); + GenericContainer::check_size(p_min, size, "p_min"); + GenericContainer::check_size(p_max, size, "p_max"); + GenericContainer::check_size(q_min, size, "q_min"); + GenericContainer::check_size(q_max, size, "q_max"); + GenericContainer::check_size(bus_id, size, "bus_id"); + GenericContainer::check_size(status, size, "status"); p_mw_ = RealVect::Map(&p_mw[0], size); q_mvar_ = RealVect::Map(&q_mvar[0], size); @@ -82,7 +83,7 @@ void DataSGen::set_state(DataSGen::StateRes & my_state ) status_ = status; } -void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void SGenContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { const int nb_sgen = nb(); int bus_id_me, bus_id_solver; cplx_type tmp; @@ -94,7 +95,7 @@ void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataSGen::fillSbus: Static Generator with id "; + exc_ << "SGenContainer::fillSbus: Static Generator with id "; exc_ << sgen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -105,7 +106,7 @@ void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol } } -void DataSGen::compute_results(const Eigen::Ref & Va, +void SGenContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -121,19 +122,19 @@ void DataSGen::compute_results(const Eigen::Ref & Va, else res_q_ = RealVect::Zero(nb_sgen); } -void DataSGen::reset_results(){ +void SGenContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV } -void DataSGen::change_p(int sgen_id, real_type new_p, SolverControl & solver_control) +void SGenContainer::change_p(int sgen_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(sgen_id); // and this check that load_id is not out of bound if(!my_status) { std::ostringstream exc_; - exc_ << "DataSGen::change_p: Impossible to change the active value of a disconnected static generator (check sgen id "; + exc_ << "SGenContainer::change_p: Impossible to change the active value of a disconnected static generator (check sgen id "; exc_ << sgen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -142,13 +143,13 @@ void DataSGen::change_p(int sgen_id, real_type new_p, SolverControl & solver_con p_mw_(sgen_id) = new_p; } -void DataSGen::change_q(int sgen_id, real_type new_q, SolverControl & solver_control) +void SGenContainer::change_q(int sgen_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(sgen_id); // and this check that load_id is not out of bound if(!my_status) { std::ostringstream exc_; - exc_ << "DataSGen::change_q: Impossible to change the reactive value of a disconnected static generator (check sgen id "; + exc_ << "SGenContainer::change_q: Impossible to change the reactive value of a disconnected static generator (check sgen id "; exc_ << sgen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -157,7 +158,7 @@ void DataSGen::change_q(int sgen_id, real_type new_q, SolverControl & solver_con q_mvar_(sgen_id) = new_q; } -void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { +void SGenContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_sgen = nb(); for(int sgen_id = 0; sgen_id < nb_sgen; ++sgen_id) { @@ -166,7 +167,7 @@ void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataSGen::reconnect_connected_buses: Static Generator with id "; + exc_ << "SGenContainer::reconnect_connected_buses: Static Generator with id "; exc_ << sgen_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_sgen(...)` ?."; throw std::runtime_error(exc_.str()); @@ -175,7 +176,7 @@ void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataSGen::gen_p_per_bus(std::vector & res) const +void SGenContainer::gen_p_per_bus(std::vector & res) const { const int nb_gen = nb(); for(int sgen_id = 0; sgen_id < nb_gen; ++sgen_id) @@ -186,7 +187,7 @@ void DataSGen::gen_p_per_bus(std::vector & res) const } } -void DataSGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void SGenContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) diff --git a/src/DataSGen.h b/src/element_container/SGenContainer.h similarity index 90% rename from src/DataSGen.h rename to src/element_container/SGenContainer.h index d9c1b309..ae05cdb4 100644 --- a/src/DataSGen.h +++ b/src/element_container/SGenContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATASGEN_H -#define DATASGEN_H +#ifndef SGEN_CONTAINER_H +#define SGEN_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all static generator (PQ generators) on the grid. @@ -28,7 +28,7 @@ The convention used for the static is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/sgen.html#electric-model **/ -class DataSGen: public DataGeneric +class SGenContainer: public GenericContainer { // TODO make a single class for load and shunt and just specialize the part where the // TODO powerflow equations are located (when i update the Y matrix) @@ -60,7 +60,7 @@ class DataSGen: public DataGeneric real_type res_v_kv; real_type res_theta_deg; - SGenInfo(const DataSGen & r_data_sgen, int my_id): + SGenInfo(const SGenContainer & r_data_sgen, int my_id): id(-1), name(""), connected(false), @@ -108,12 +108,12 @@ class DataSGen: public DataGeneric typedef SGenInfo DataInfo; private: - typedef DataConstIterator DataSGenConstIterator; + typedef GenericContainerConstIterator SGenContainerConstIterator; public: - typedef DataSGenConstIterator const_iterator_type; - const_iterator_type begin() const {return DataSGenConstIterator(this, 0); } - const_iterator_type end() const {return DataSGenConstIterator(this, nb()); } + typedef SGenContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return SGenContainerConstIterator(this, 0); } + const_iterator_type end() const {return SGenContainerConstIterator(this, nb()); } SGenInfo operator[](int id) const { if(id < 0) @@ -140,11 +140,11 @@ class DataSGen: public DataGeneric std::vector // status > StateRes; - DataSGen() {}; + SGenContainer() {}; // pickle (python) - DataSGen::StateRes get_state() const; - void set_state(DataSGen::StateRes & my_state ); + SGenContainer::StateRes get_state() const; + void set_state(SGenContainer::StateRes & my_state ); void init(const RealVect & sgen_p, @@ -214,4 +214,4 @@ class DataSGen: public DataGeneric RealVect res_theta_; // in degree }; -#endif //DATASGEN_H +#endif //SGEN_CONTAINER_H diff --git a/src/DataShunt.cpp b/src/element_container/ShuntContainer.cpp similarity index 81% rename from src/DataShunt.cpp rename to src/element_container/ShuntContainer.cpp index ba52bbdb..58178348 100644 --- a/src/DataShunt.cpp +++ b/src/element_container/ShuntContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,10 +6,11 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataShunt.h" +#include "ShuntContainer.h" + #include -void DataShunt::init(const RealVect & shunt_p_mw, +void ShuntContainer::init(const RealVect & shunt_p_mw, const RealVect & shunt_q_mvar, const Eigen::VectorXi & shunt_bus_id) { @@ -19,17 +20,17 @@ void DataShunt::init(const RealVect & shunt_p_mw, status_ = std::vector(p_mw_.size(), true); // by default everything is connected } -DataShunt::StateRes DataShunt::get_state() const +ShuntContainer::StateRes ShuntContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); std::vector bus_id(bus_id_.begin(), bus_id_.end()); std::vector status = status_; - DataShunt::StateRes res(names_, p_mw, q_mvar, bus_id, status); + ShuntContainer::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } -void DataShunt::set_state(DataShunt::StateRes & my_state ) +void ShuntContainer::set_state(ShuntContainer::StateRes & my_state ) { reset_results(); names_ = std::get<0>(my_state); @@ -46,7 +47,7 @@ void DataShunt::set_state(DataShunt::StateRes & my_state ) status_ = status; } -void DataShunt::fillYbus(std::vector > & res, +void ShuntContainer::fillYbus(std::vector > & res, bool ac, const std::vector & id_grid_to_solver, real_type sn_mva) const @@ -67,7 +68,7 @@ void DataShunt::fillYbus(std::vector > & res, bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataShunt::fillYbus: the shunt with id "; + exc_ << "ShuntContainer::fillYbus: the shunt with id "; exc_ << shunt_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -77,7 +78,7 @@ void DataShunt::fillYbus(std::vector > & res, } } -void DataShunt::fillBp_Bpp(std::vector > & Bp, +void ShuntContainer::fillBp_Bpp(std::vector > & Bp, std::vector > & Bpp, const std::vector & id_grid_to_solver, real_type sn_mva, @@ -94,7 +95,7 @@ void DataShunt::fillBp_Bpp(std::vector > & Bp, bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataShunt::fillBp_Bpp: the shunt with id "; + exc_ << "ShuntContainer::fillBp_Bpp: the shunt with id "; exc_ << shunt_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -106,7 +107,7 @@ void DataShunt::fillBp_Bpp(std::vector > & Bp, } } -void DataShunt::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const // in DC i need that +void ShuntContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const // in DC i need that { if(ac) return; // in AC I do not do that // std::cout << " ok i use this function" << std::endl; @@ -126,11 +127,11 @@ void DataShunt::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_so } } -void DataShunt::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver){ - throw std::runtime_error("DataShunt::fillYbus_spmat: should not be used anymore !"); +void ShuntContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver){ + throw std::runtime_error("ShuntContainer::fillYbus_spmat: should not be used anymore !"); } -void DataShunt::compute_results(const Eigen::Ref & Va, +void ShuntContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -148,7 +149,7 @@ void DataShunt::compute_results(const Eigen::Ref & Va, int bus_id_me = bus_id_(shunt_id); int bus_solver_id = id_grid_to_solver[bus_id_me]; if(bus_solver_id == _deactivated_bus_id){ - throw std::runtime_error("DataShunt::compute_results: A shunt is connected to a disconnected bus."); + throw std::runtime_error("ShuntContainer::compute_results: A shunt is connected to a disconnected bus."); } cplx_type E = V(bus_solver_id); cplx_type y = -my_one_ * (p_mw_(shunt_id) + my_i * q_mvar_(shunt_id)) / sn_mva; @@ -160,13 +161,13 @@ void DataShunt::compute_results(const Eigen::Ref & Va, } } -void DataShunt::reset_results(){ +void ShuntContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV } -void DataShunt::change_p(int shunt_id, real_type new_p, SolverControl & solver_control) +void ShuntContainer::change_p(int shunt_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the active value of a disconnected shunt"); @@ -178,7 +179,7 @@ void DataShunt::change_p(int shunt_id, real_type new_p, SolverControl & solver_c } -void DataShunt::change_q(int shunt_id, real_type new_q, SolverControl & solver_control) +void ShuntContainer::change_q(int shunt_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the reactive value of a disconnected shunt"); @@ -188,7 +189,7 @@ void DataShunt::change_q(int shunt_id, real_type new_q, SolverControl & solver_c q_mvar_(shunt_id) = new_q; } -void DataShunt::reconnect_connected_buses(std::vector & bus_status) const { +void ShuntContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_shunt = nb(); for(int shunt_id = 0; shunt_id < nb_shunt; ++shunt_id) { @@ -197,7 +198,7 @@ void DataShunt::reconnect_connected_buses(std::vector & bus_status) const if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataShunt::reconnect_connected_buses: Shunt with id "; + exc_ << "ShuntContainer::reconnect_connected_buses: Shunt with id "; exc_ << shunt_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_shunt(...)` ?."; throw std::runtime_error(exc_.str()); @@ -206,7 +207,7 @@ void DataShunt::reconnect_connected_buses(std::vector & bus_status) const } } -void DataShunt::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void ShuntContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) diff --git a/src/DataShunt.h b/src/element_container/ShuntContainer.h similarity index 89% rename from src/DataShunt.h rename to src/element_container/ShuntContainer.h index 53e5c33b..2cbed982 100644 --- a/src/DataShunt.h +++ b/src/element_container/ShuntContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,18 +6,16 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATASHUNT_H -#define DATASHUNT_H - -#include "Utils.h" +#ifndef SHUNT_CONTAINER_H +#define SHUNT_CONTAINER_H #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" - -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all shunts on the grid. @@ -28,7 +26,7 @@ The convention used for the shunt is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/shunt.html#electric-model **/ -class DataShunt : public DataGeneric +class ShuntContainer : public GenericContainer { // iterators part public: @@ -50,7 +48,7 @@ class DataShunt : public DataGeneric real_type res_v_kv; real_type res_theta_deg; - ShuntInfo(const DataShunt & r_data_shunt, int my_id): + ShuntInfo(const ShuntContainer & r_data_shunt, int my_id): id(-1), name(""), connected(false), @@ -89,12 +87,12 @@ class DataShunt : public DataGeneric typedef ShuntInfo DataInfo; private: - typedef DataConstIterator DataShuntConstIterator; + typedef GenericContainerConstIterator ShuntContainerConstIterator; public: - typedef DataShuntConstIterator const_iterator_type; - const_iterator_type begin() const {return DataShuntConstIterator(this, 0); } - const_iterator_type end() const {return DataShuntConstIterator(this, nb()); } + typedef ShuntContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return ShuntContainerConstIterator(this, 0); } + const_iterator_type end() const {return ShuntContainerConstIterator(this, nb()); } ShuntInfo operator[](int id) const { if(id < 0) @@ -117,7 +115,7 @@ class DataShunt : public DataGeneric std::vector // status > StateRes; - DataShunt() {}; + ShuntContainer() {}; void init(const RealVect & shunt_p_mw, const RealVect & shunt_q_mvar, @@ -125,8 +123,8 @@ class DataShunt : public DataGeneric ); // pickle (python) - DataShunt::StateRes get_state() const; - void set_state(DataShunt::StateRes & my_state ); + ShuntContainer::StateRes get_state() const; + void set_state(ShuntContainer::StateRes & my_state ); int nb() const { return static_cast(p_mw_.size()); } @@ -191,4 +189,4 @@ class DataShunt : public DataGeneric RealVect res_theta_; // in kV }; -#endif //DATASHUNT_H +#endif //SHUNT_CONTAINER_H diff --git a/src/DataTrafo.cpp b/src/element_container/TrafoContainer.cpp similarity index 86% rename from src/DataTrafo.cpp rename to src/element_container/TrafoContainer.cpp index 15afa2a5..ac4c3461 100644 --- a/src/DataTrafo.cpp +++ b/src/element_container/TrafoContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,11 +6,12 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataTrafo.h" +#include "TrafoContainer.h" + #include #include -void DataTrafo::init(const RealVect & trafo_r, +void TrafoContainer::init(const RealVect & trafo_r, const RealVect & trafo_x, const CplxVect & trafo_b, const RealVect & trafo_tap_step_pct, @@ -27,15 +28,15 @@ void DataTrafo::init(const RealVect & trafo_r, DOES NOT WORK WITH POWERLINES **/ const int size = static_cast(trafo_r.size()); - DataGeneric::check_size(trafo_r, size, "trafo_r"); - DataGeneric::check_size(trafo_x, size, "trafo_x"); - DataGeneric::check_size(trafo_b, size, "trafo_b"); - DataGeneric::check_size(trafo_tap_step_pct, size, "trafo_tap_step_pct"); - DataGeneric::check_size(trafo_tap_pos, size, "trafo_tap_pos"); - DataGeneric::check_size(trafo_shift_degree, size, "trafo_shift_degree"); - DataGeneric::check_size(trafo_tap_hv, static_cast::size_type>(size), "trafo_tap_hv"); - DataGeneric::check_size(trafo_hv_id, size, "trafo_hv_id"); - DataGeneric::check_size(trafo_lv_id, size, "trafo_lv_id"); + GenericContainer::check_size(trafo_r, size, "trafo_r"); + GenericContainer::check_size(trafo_x, size, "trafo_x"); + GenericContainer::check_size(trafo_b, size, "trafo_b"); + GenericContainer::check_size(trafo_tap_step_pct, size, "trafo_tap_step_pct"); + GenericContainer::check_size(trafo_tap_pos, size, "trafo_tap_pos"); + GenericContainer::check_size(trafo_shift_degree, size, "trafo_shift_degree"); + GenericContainer::check_size(trafo_tap_hv, static_cast::size_type>(size), "trafo_tap_hv"); + GenericContainer::check_size(trafo_hv_id, size, "trafo_hv_id"); + GenericContainer::check_size(trafo_lv_id, size, "trafo_lv_id"); //TODO "parrallel" in the pandapower dataframe, like for lines, are not handled. Handle it python side! @@ -55,7 +56,7 @@ void DataTrafo::init(const RealVect & trafo_r, } -DataTrafo::StateRes DataTrafo::get_state() const +TrafoContainer::StateRes TrafoContainer::get_state() const { std::vector branch_r(r_.begin(), r_.end()); std::vector branch_x(x_.begin(), x_.end()); @@ -66,12 +67,12 @@ DataTrafo::StateRes DataTrafo::get_state() const std::vector ratio(ratio_.begin(), ratio_.end()); std::vector shift(shift_.begin(), shift_.end()); std::vector is_tap_hv_side = is_tap_hv_side_; - DataTrafo::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); + TrafoContainer::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); return res; } -void DataTrafo::set_state(DataTrafo::StateRes & my_state) +void TrafoContainer::set_state(TrafoContainer::StateRes & my_state) { reset_results(); @@ -87,15 +88,15 @@ void DataTrafo::set_state(DataTrafo::StateRes & my_state) std::vector & shift = std::get<9>(my_state); auto size = branch_r.size(); - DataGeneric::check_size(branch_r, size, "branch_r"); - DataGeneric::check_size(branch_x, size, "branch_x"); - DataGeneric::check_size(branch_h, size, "branch_h"); - DataGeneric::check_size(bus_hv_id, size, "bus_hv_id"); - DataGeneric::check_size(bus_lv_id, size, "bus_lv_id"); - DataGeneric::check_size(status, size, "status"); - DataGeneric::check_size(ratio, size, "ratio"); - DataGeneric::check_size(is_tap_hv_side, size, "is_tap_hv_side"); - DataGeneric::check_size(shift, size, "shift"); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h, size, "branch_h"); + GenericContainer::check_size(bus_hv_id, size, "bus_hv_id"); + GenericContainer::check_size(bus_lv_id, size, "bus_lv_id"); + GenericContainer::check_size(status, size, "status"); + GenericContainer::check_size(ratio, size, "ratio"); + GenericContainer::check_size(is_tap_hv_side, size, "is_tap_hv_side"); + GenericContainer::check_size(shift, size, "shift"); // now assign the values r_ = RealVect::Map(&branch_r[0], size); @@ -113,7 +114,7 @@ void DataTrafo::set_state(DataTrafo::StateRes & my_state) } -void DataTrafo::_update_model_coeffs() +void TrafoContainer::_update_model_coeffs() { const Eigen::Index my_size = r_.size(); @@ -163,12 +164,12 @@ void DataTrafo::_update_model_coeffs() } } -void DataTrafo::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) +void TrafoContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) { throw std::runtime_error("You should not use that!"); } -void DataTrafo::fillYbus(std::vector > & res, +void TrafoContainer::fillYbus(std::vector > & res, bool ac, const std::vector & id_grid_to_solver, real_type sn_mva) const @@ -186,7 +187,7 @@ void DataTrafo::fillYbus(std::vector > & res, int bus_hv_solver_id = id_grid_to_solver[bus_hv_id_me]; if(bus_hv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillYbus: the trafo with id "; + exc_ << "TrafoContainer::fillYbus: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -195,7 +196,7 @@ void DataTrafo::fillYbus(std::vector > & res, int bus_lv_solver_id = id_grid_to_solver[bus_lv_id_me]; if(bus_lv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillYbus: the trafo with id "; + exc_ << "TrafoContainer::fillYbus: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -221,7 +222,7 @@ void DataTrafo::fillYbus(std::vector > & res, } } -void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver){ +void TrafoContainer::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver){ if(ac) return; // return; const int nb_trafo = nb(); @@ -235,7 +236,7 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s bus_id_solver_lv = id_grid_to_solver[bus_id_me]; if(bus_id_solver_lv == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::hack_Sbus_for_dc_phase_shifter: the trafo with id "; + exc_ << "TrafoContainer::hack_Sbus_for_dc_phase_shifter: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -244,7 +245,7 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s bus_id_solver_hv = id_grid_to_solver[bus_id_me]; if(bus_id_solver_hv == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::hack_Sbus_for_dc_phase_shifter: the trafo with id "; + exc_ << "TrafoContainer::hack_Sbus_for_dc_phase_shifter: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -254,7 +255,7 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s } } -void DataTrafo::compute_results(const Eigen::Ref & Va, +void TrafoContainer::compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, const Eigen::Ref & V, const std::vector & id_grid_to_solver, @@ -284,7 +285,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, int bus_hv_solver_id = id_grid_to_solver[bus_hv_id_me]; if(bus_hv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::compute_results: the trafo with id "; + exc_ << "TrafoContainer::compute_results: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -293,7 +294,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, int bus_lv_solver_id = id_grid_to_solver[bus_lv_id_me]; if(bus_lv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::compute_results: the trafo with id "; + exc_ << "TrafoContainer::compute_results: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -346,7 +347,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, _get_amps(res_a_lv_, res_p_lv_, res_q_lv_, res_v_lv_); } -void DataTrafo::reset_results(){ +void TrafoContainer::reset_results(){ res_p_hv_ = RealVect(); // in MW res_q_hv_ = RealVect(); // in MVar res_v_hv_ = RealVect(); // in kV @@ -358,7 +359,7 @@ void DataTrafo::reset_results(){ } -void DataTrafo::fillBp_Bpp(std::vector > & Bp, +void TrafoContainer::fillBp_Bpp(std::vector > & Bp, std::vector > & Bpp, const std::vector & id_grid_to_solver, real_type sn_mva, @@ -389,7 +390,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: the trafo with id "; + exc_ << "TrafoContainer::fillBp_Bpp: the trafo with id "; exc_ << tr_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -398,7 +399,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: the trafo with id "; + exc_ << "TrafoContainer::fillBp_Bpp: the trafo with id "; exc_ << tr_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -428,7 +429,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, ys_bpp = 1. / (0. + my_i * x_(tr_id)); }else{ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; + exc_ << "TrafoContainer::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; exc_ << tr_id; throw std::runtime_error(exc_.str()); } @@ -459,7 +460,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, } -void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, +void TrafoContainer::fillBf_for_PTDF(std::vector > & Bf, const std::vector & id_grid_to_solver, real_type sn_mva, int nb_powerline, @@ -476,7 +477,7 @@ void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBf_for_PTDF: the line with id "; + exc_ << "TrafoContainer::fillBf_for_PTDF: the line with id "; exc_ << tr_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -508,7 +509,7 @@ void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, } -void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ +void TrafoContainer::reconnect_connected_buses(std::vector & bus_status) const{ const Eigen::Index nb_trafo = nb(); for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ @@ -519,7 +520,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_or_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << "TrafoContainer::reconnect_connected_buses: Trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; throw std::runtime_error(exc_.str()); @@ -530,7 +531,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_ex_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << "TrafoContainer::reconnect_connected_buses: Trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; throw std::runtime_error(exc_.str()); @@ -539,7 +540,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ } } -void DataTrafo::nb_line_end(std::vector & res) const{ +void TrafoContainer::nb_line_end(std::vector & res) const{ const Eigen::Index nb_trafo = nb(); for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ @@ -552,7 +553,7 @@ void DataTrafo::nb_line_end(std::vector & res) const{ } } -void DataTrafo::get_graph(std::vector > & res) const +void TrafoContainer::get_graph(std::vector > & res) const { const auto my_size = nb(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -565,7 +566,7 @@ void DataTrafo::get_graph(std::vector > & res) const } } -void DataTrafo::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void TrafoContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb_line = nb(); SolverControl unused_solver_control; diff --git a/src/DataTrafo.h b/src/element_container/TrafoContainer.h similarity index 93% rename from src/DataTrafo.h rename to src/element_container/TrafoContainer.h index 033a58aa..6e150255 100644 --- a/src/DataTrafo.h +++ b/src/element_container/TrafoContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-2023, RTE (https://www.rte-france.com) // See AUTHORS.txt // This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. // If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,18 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATATRAFO_H -#define DATATRAFO_H +#ifndef TRAFO_CONTAINER_H +#define TRAFO_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" - -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all transformers on the grid. @@ -30,7 +29,7 @@ The convention used for the transformer is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/trafo.html#electric-model **/ -class DataTrafo : public DataGeneric +class TrafoContainer : public GenericContainer { public: class TrafoInfo @@ -61,7 +60,7 @@ class DataTrafo : public DataGeneric real_type res_a_lv_ka; real_type res_theta_lv_deg; - TrafoInfo(const DataTrafo & r_data_trafo, int my_id): + TrafoInfo(const TrafoContainer & r_data_trafo, int my_id): id(-1), name(""), connected(false), @@ -121,7 +120,7 @@ class DataTrafo : public DataGeneric typedef TrafoInfo DataInfo; private: - typedef DataConstIterator DataTrafoConstIterator; + typedef GenericContainerConstIterator TrafoContainerConstIterator; public: typedef std::tuple< @@ -137,7 +136,7 @@ class DataTrafo : public DataGeneric std::vector // shift_ > StateRes; - DataTrafo() {}; + TrafoContainer() {}; void init(const RealVect & trafo_r, const RealVect & trafo_x, @@ -151,15 +150,15 @@ class DataTrafo : public DataGeneric const Eigen::VectorXi & trafo_lv_id ); //pickle - DataTrafo::StateRes get_state() const; - void set_state(DataTrafo::StateRes & my_state ); + TrafoContainer::StateRes get_state() const; + void set_state(TrafoContainer::StateRes & my_state ); int nb() const { return static_cast(r_.size()); } // make it iterable - typedef DataTrafoConstIterator const_iterator_type; - const_iterator_type begin() const {return DataTrafoConstIterator(this, 0); } - const_iterator_type end() const {return DataTrafoConstIterator(this, nb()); } + typedef TrafoContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return TrafoContainerConstIterator(this, 0); } + const_iterator_type end() const {return TrafoContainerConstIterator(this, nb()); } TrafoInfo operator[](int id) const { if(id < 0) @@ -288,4 +287,4 @@ class DataTrafo : public DataGeneric RealVect dc_x_tau_shift_; }; -#endif //DATATRAFO_H +#endif //TRAFO_CONTAINER_H diff --git a/src/help_fun_msg.cpp b/src/help_fun_msg.cpp index a33c1b67..48f58a9b 100644 --- a/src/help_fun_msg.cpp +++ b/src/help_fun_msg.cpp @@ -808,12 +808,12 @@ const std::string DocIterator::has_res = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataGen = R"mydelimiter( +const std::string DocIterator::GeneratorContainer = R"mydelimiter( This class allows to iterate through the generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. In lightsim2grid they are modeled as "pv" meanings you give the active production setpoint and voltage magnitude setpoint - (see :attr:`lightsim2grid.elements.DataSGen` for more exotic PQ generators). + (see :attr:`lightsim2grid.elements.SGenContainer` for more exotic PQ generators). The active production value setpoint are modified only for the generators participating to the slack buses (see :attr:`lightsim2grid.elements.GenInfo.is_slack` and :attr:`lightsim2grid.elements.GenInfo.slack_weight`). @@ -847,7 +847,8 @@ const std::string DocIterator::DataGen = R"mydelimiter( )mydelimiter"; const std::string DocIterator::GenInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataGen` + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.GeneratorContainer` It allows to read information from each generator of the powergrid. @@ -932,11 +933,12 @@ const std::string DocIterator::max_p_mw = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataSGen = R"mydelimiter( +const std::string DocIterator::SGenContainer = R"mydelimiter( This class allows to iterate through the static generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. - In lightsim2grid they are two types of generators the more standard PV generators (see :attr:`lightsim2grid.elements.DataGen`). These + In lightsim2grid they are two types of generators the more standard PV generators (see + :attr:`lightsim2grid.elements.GeneratorContainer`). These are more exotic generators known as PQ, where you give the active production value and reactive production value. It's basically like loads, but using the generator convention (if the value is positive, it means power is taken from the grid to the element) @@ -972,7 +974,8 @@ const std::string DocIterator::DataSGen = R"mydelimiter( )mydelimiter"; const std::string DocIterator::SGenInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataSGen` + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.SGenContainer` It allows to read information from each static generator of the powergrid. @@ -1001,7 +1004,7 @@ const std::string DocIterator::SGenInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataLoad = R"mydelimiter( +const std::string DocIterator::LoadContainer = R"mydelimiter( This class allows to iterate through the loads **and storage units** of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1049,7 +1052,8 @@ const std::string DocIterator::DataLoad = R"mydelimiter( )mydelimiter"; const std::string DocIterator::LoadInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataLoad`. + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.LoadContainer`. We remind the reader that storage units are also modeled as load in lightsim2grid. It allows to read information from each load / storage unit of the powergrid. @@ -1088,7 +1092,7 @@ const std::string DocIterator::LoadInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataShunt = R"mydelimiter( +const std::string DocIterator::ShuntContainer = R"mydelimiter( This class allows to iterate through the load of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1122,7 +1126,8 @@ const std::string DocIterator::DataShunt = R"mydelimiter( )mydelimiter"; const std::string DocIterator::ShuntInfo = R"mydelimiter( - This class represents what you get from retrieving the shunts from :class:`lightsim2grid.elements.DataShunt`. + This class represents what you get from retrieving the shunts from + :class:`lightsim2grid.elements.ShuntContainer`. It allows to read information from each shunt of the powergrid. @@ -1150,7 +1155,7 @@ const std::string DocIterator::ShuntInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataTrafo = R"mydelimiter( +const std::string DocIterator::TrafoContainer = R"mydelimiter( This class allows to iterate through the transformers of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1184,7 +1189,8 @@ const std::string DocIterator::DataTrafo = R"mydelimiter( )mydelimiter"; const std::string DocIterator::TrafoInfo = R"mydelimiter( - This class represents what you get from retrieving the transformers from :class:`lightsim2grid.elements.DataTrafo`. + This class represents what you get from retrieving the transformers from + :class:`lightsim2grid.elements.TrafoContainer`. It allows to read information from each transformer of the powergrid. @@ -1321,7 +1327,7 @@ const std::string DocIterator::res_a_hv_ka = R"mydelimiter( )mydelimiter" + DocIterator::only_avail_res; -const std::string DocIterator::DataLine = R"mydelimiter( +const std::string DocIterator::LineContainer = R"mydelimiter( This class allows to iterate through the powerlines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1355,7 +1361,8 @@ const std::string DocIterator::DataLine = R"mydelimiter( )mydelimiter"; const std::string DocIterator::LineInfo = R"mydelimiter( - This class represents what you get from retrieving the powerlines from :class:`lightsim2grid.elements.DataLine`. + This class represents what you get from retrieving the powerlines from + :class:`lightsim2grid.elements.LineContainer`. It allows to read information from each powerline of the powergrid. @@ -1475,7 +1482,7 @@ const std::string DocIterator::res_a_ex_ka = R"mydelimiter( )mydelimiter" + DocIterator::only_avail_res; -const std::string DocIterator::DataDCLine = R"mydelimiter( +const std::string DocIterator::DCLineContainer = R"mydelimiter( This class allows to iterate through the dc lines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1523,7 +1530,8 @@ const std::string DocIterator::DataDCLine = R"mydelimiter( const std::string DocIterator::DCLineInfo = R"mydelimiter( - This class represents what you get from retrieving the dc powerlines from :class:`lightsim2grid.elements.DataDCLine`. + This class represents what you get from retrieving the dc powerlines from + :class:`lightsim2grid.elements.DCLineContainer`. It allows to read information from each dc powerline of the powergrid. @@ -1789,7 +1797,8 @@ const std::string DocGridModel::get_dc_solver = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_lines = R"mydelimiter( - This function allows to retrieve the powerlines (as a :class:`lightsim2grid.elements.DataLine` object, + This function allows to retrieve the powerlines (as a + :class:`lightsim2grid.elements.LineContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1807,7 +1816,8 @@ const std::string DocGridModel::get_lines = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_trafos = R"mydelimiter( - This function allows to retrieve the transformers (as a :class:`lightsim2grid.elements.DataLine` object, + This function allows to retrieve the transformers (as a + :class:`lightsim2grid.elements.LineContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1825,7 +1835,8 @@ const std::string DocGridModel::get_trafos = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_generators = R"mydelimiter( - This function allows to retrieve the (standard) generators (as a :class:`lightsim2grid.elements.DataGen` object, + This function allows to retrieve the (standard) generators (as a + :class:`lightsim2grid.elements.GeneratorContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1843,7 +1854,8 @@ const std::string DocGridModel::get_generators = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_static_generators = R"mydelimiter( - This function allows to retrieve the (more exotic) static generators (as a :class:`lightsim2grid.elements.DataSGen` object, + This function allows to retrieve the (more exotic) static generators (as a + :class:`lightsim2grid.elements.SGenContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1861,7 +1873,8 @@ const std::string DocGridModel::get_static_generators = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_shunts = R"mydelimiter( - This function allows to retrieve the shunts (as a :class:`lightsim2grid.elements.DataShunt` object, + This function allows to retrieve the shunts (as a + :class:`lightsim2grid.elements.ShuntContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1879,12 +1892,13 @@ const std::string DocGridModel::get_shunts = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_storages = R"mydelimiter( - This function allows to retrieve the storage units (as a :class:`lightsim2grid.elements.DataLoad` object, + This function allows to retrieve the storage units (as a + :class:`lightsim2grid.elements.LoadContainer` object, see :ref:`elements-modeled` for more information) .. note:: We want to emphize that, as far as lightsim2grid is concerned, the storage units are modeled as loads. This is why - this function will return a :class:`lightsim2grid.elements.DataLoad`. + this function will return a :class:`lightsim2grid.elements.LoadContainer`. Examples --------- @@ -1901,7 +1915,7 @@ const std::string DocGridModel::get_storages = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_loads = R"mydelimiter( - This function allows to retrieve the loads (as a :class:`lightsim2grid.elements.DataLoad` object, + This function allows to retrieve the loads (as a :class:`lightsim2grid.elements.LoadContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1920,7 +1934,8 @@ const std::string DocGridModel::get_loads = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_dclines = R"mydelimiter( - This function allows to retrieve the dc powerlines (as a :class:`lightsim2grid.elements.DataDCLine` object, + This function allows to retrieve the dc powerlines (as a + :class:`lightsim2grid.elements.DCLineContainer` object, see :ref:`elements-modeled` for more information) Examples diff --git a/src/help_fun_msg.h b/src/help_fun_msg.h index 3f503b13..cf98db44 100644 --- a/src/help_fun_msg.h +++ b/src/help_fun_msg.h @@ -90,25 +90,25 @@ struct DocIterator static const std::string h_pu; // specific to generators - static const std::string DataGen; + static const std::string GeneratorContainer; static const std::string GenInfo; static const std::string is_slack; static const std::string slack_weight; // specific to sgens - static const std::string DataSGen; + static const std::string SGenContainer; static const std::string SGenInfo; // specific to loads (and storage units) - static const std::string DataLoad; + static const std::string LoadContainer; static const std::string LoadInfo; // specific to shunts - static const std::string DataShunt; + static const std::string ShuntContainer; static const std::string ShuntInfo; // specific to transformers - static const std::string DataTrafo; + static const std::string TrafoContainer; static const std::string TrafoInfo; static const std::string bus_hv_id; static const std::string bus_lv_id; @@ -127,7 +127,7 @@ struct DocIterator static const std::string res_theta_lv_deg; // specific to powerlines - static const std::string DataLine; + static const std::string LineContainer; static const std::string LineInfo; static const std::string bus_or_id; static const std::string bus_ex_id; @@ -144,7 +144,7 @@ struct DocIterator // specific to dc lines static const std::string dc_line_formula; - static const std::string DataDCLine; + static const std::string DCLineContainer; static const std::string DCLineInfo; static const std::string target_p_or_mw; static const std::string target_vm_or_pu; diff --git a/src/CKTSOSolver.cpp b/src/linear_solvers/CKTSOSolver.cpp similarity index 100% rename from src/CKTSOSolver.cpp rename to src/linear_solvers/CKTSOSolver.cpp diff --git a/src/CKTSOSolver.h b/src/linear_solvers/CKTSOSolver.h similarity index 100% rename from src/CKTSOSolver.h rename to src/linear_solvers/CKTSOSolver.h diff --git a/src/KLUSolver.cpp b/src/linear_solvers/KLUSolver.cpp similarity index 100% rename from src/KLUSolver.cpp rename to src/linear_solvers/KLUSolver.cpp diff --git a/src/KLUSolver.h b/src/linear_solvers/KLUSolver.h similarity index 100% rename from src/KLUSolver.h rename to src/linear_solvers/KLUSolver.h diff --git a/src/NICSLUSolver.cpp b/src/linear_solvers/NICSLUSolver.cpp similarity index 100% rename from src/NICSLUSolver.cpp rename to src/linear_solvers/NICSLUSolver.cpp diff --git a/src/NICSLUSolver.h b/src/linear_solvers/NICSLUSolver.h similarity index 100% rename from src/NICSLUSolver.h rename to src/linear_solvers/NICSLUSolver.h diff --git a/src/SparseLUSolver.cpp b/src/linear_solvers/SparseLUSolver.cpp similarity index 100% rename from src/SparseLUSolver.cpp rename to src/linear_solvers/SparseLUSolver.cpp diff --git a/src/SparseLUSolver.h b/src/linear_solvers/SparseLUSolver.h similarity index 100% rename from src/SparseLUSolver.h rename to src/linear_solvers/SparseLUSolver.h diff --git a/src/main.cpp b/src/main.cpp index a743bbae..453e3d3a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -349,31 +349,31 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) #endif // CKTSO_SOLVER_AVAILABLE (or _READ_THE_DOCS) - py::class_(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str()) + py::class_(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str()) .def(py::init<>()) - .def("get_Va", &GaussSeidelSolver::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) - .def("get_Vm", &GaussSeidelSolver::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) - .def("get_V", &GaussSeidelSolver::get_V, DocSolver::get_V.c_str()) - .def("get_error", &GaussSeidelSolver::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information - .def("get_nb_iter", &GaussSeidelSolver::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization - .def("reset", &GaussSeidelSolver::reset, DocSolver::reset.c_str()) // reset the solver to its original state - .def("converged", &GaussSeidelSolver::converged, DocSolver::converged.c_str()) // whether the solver has converged - .def("compute_pf", &GaussSeidelSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow - .def("get_timers", &GaussSeidelSolver::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part - .def("solve", &GaussSeidelSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization - - py::class_(m, "GaussSeidelSynchSolver", DocSolver::GaussSeidelSynchSolver.c_str()) + .def("get_Va", &GaussSeidelAlgo::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) + .def("get_Vm", &GaussSeidelAlgo::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) + .def("get_V", &GaussSeidelAlgo::get_V, DocSolver::get_V.c_str()) + .def("get_error", &GaussSeidelAlgo::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information + .def("get_nb_iter", &GaussSeidelAlgo::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization + .def("reset", &GaussSeidelAlgo::reset, DocSolver::reset.c_str()) // reset the solver to its original state + .def("converged", &GaussSeidelAlgo::converged, DocSolver::converged.c_str()) // whether the solver has converged + .def("compute_pf", &GaussSeidelAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow + .def("get_timers", &GaussSeidelAlgo::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part + .def("solve", &GaussSeidelAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization + + py::class_(m, "GaussSeidelSynchSolver", DocSolver::GaussSeidelSynchSolver.c_str()) .def(py::init<>()) - .def("get_Va", &GaussSeidelSynchSolver::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) - .def("get_Vm", &GaussSeidelSynchSolver::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) - .def("get_V", &GaussSeidelSynchSolver::get_V, DocSolver::get_V.c_str()) - .def("get_error", &GaussSeidelSynchSolver::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information - .def("get_nb_iter", &GaussSeidelSynchSolver::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization - .def("reset", &GaussSeidelSynchSolver::reset, DocSolver::reset.c_str()) // reset the solver to its original state - .def("converged", &GaussSeidelSynchSolver::converged, DocSolver::converged.c_str()) // whether the solver has converged - .def("compute_pf", &GaussSeidelSynchSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow - .def("get_timers", &GaussSeidelSynchSolver::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part - .def("solve", &GaussSeidelSynchSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization + .def("get_Va", &GaussSeidelSynchAlgo::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) + .def("get_Vm", &GaussSeidelSynchAlgo::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) + .def("get_V", &GaussSeidelSynchAlgo::get_V, DocSolver::get_V.c_str()) + .def("get_error", &GaussSeidelSynchAlgo::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information + .def("get_nb_iter", &GaussSeidelSynchAlgo::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization + .def("reset", &GaussSeidelSynchAlgo::reset, DocSolver::reset.c_str()) // reset the solver to its original state + .def("converged", &GaussSeidelSynchAlgo::converged, DocSolver::converged.c_str()) // whether the solver has converged + .def("compute_pf", &GaussSeidelSynchAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow + .def("get_timers", &GaussSeidelSynchAlgo::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part + .def("solve", &GaussSeidelSynchAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization // Only "const" method are exported // it is so that i cannot modify the internal solver of a gridmodel python side @@ -396,192 +396,192 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_fdpf_bx_lu", &ChooseSolver::get_fdpf_bx_lu, py::return_value_policy::reference, DocGridModel::_internal_do_not_use.c_str()); // iterator for generators - py::class_(m, "DataGen", DocIterator::DataGen.c_str()) - .def("__len__", [](const DataGen & data) { return data.nb(); }) - .def("__getitem__", [](const DataGen & data, int k){return data[k]; } ) - .def("__iter__", [](const DataGen & data) { + py::class_(m, "GeneratorContainer", DocIterator::GeneratorContainer.c_str()) + .def("__len__", [](const GeneratorContainer & data) { return data.nb(); }) + .def("__getitem__", [](const GeneratorContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const GeneratorContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "GenInfo", DocIterator::GenInfo.c_str()) - .def_readonly("id", &DataGen::GenInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataGen::GenInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataGen::GenInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataGen::GenInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("is_slack", &DataGen::GenInfo::is_slack, DocIterator::is_slack.c_str()) - .def_readonly("slack_weight", &DataGen::GenInfo::slack_weight, DocIterator::slack_weight.c_str()) - .def_readonly("voltage_regulator_on", &DataGen::GenInfo::voltage_regulator_on, "TODO") - .def_readonly("target_p_mw", &DataGen::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_vm_pu", &DataGen::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str()) - .def_readonly("target_q_mvar", &DataGen::GenInfo::target_q_mvar, "TODO") - .def_readonly("min_q_mvar", &DataGen::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) - .def_readonly("max_q_mvar", &DataGen::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) - .def_readonly("has_res", &DataGen::GenInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataGen::GenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataGen::GenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataGen::GenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataGen::GenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "GenInfo", DocIterator::GenInfo.c_str()) + .def_readonly("id", &GeneratorContainer::GenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &GeneratorContainer::GenInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &GeneratorContainer::GenInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &GeneratorContainer::GenInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("is_slack", &GeneratorContainer::GenInfo::is_slack, DocIterator::is_slack.c_str()) + .def_readonly("slack_weight", &GeneratorContainer::GenInfo::slack_weight, DocIterator::slack_weight.c_str()) + .def_readonly("voltage_regulator_on", &GeneratorContainer::GenInfo::voltage_regulator_on, "TODO") + .def_readonly("target_p_mw", &GeneratorContainer::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_vm_pu", &GeneratorContainer::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str()) + .def_readonly("target_q_mvar", &GeneratorContainer::GenInfo::target_q_mvar, "TODO") + .def_readonly("min_q_mvar", &GeneratorContainer::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) + .def_readonly("max_q_mvar", &GeneratorContainer::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) + .def_readonly("has_res", &GeneratorContainer::GenInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &GeneratorContainer::GenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &GeneratorContainer::GenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &GeneratorContainer::GenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &GeneratorContainer::GenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for sgens - py::class_(m, "DataSGen", DocIterator::DataSGen.c_str()) - .def("__len__", [](const DataSGen & data) { return data.nb(); }) - .def("__getitem__", [](const DataSGen & data, int k){return data[k]; } ) - .def("__iter__", [](const DataSGen & data) { + py::class_(m, "SGenContainer", DocIterator::SGenContainer.c_str()) + .def("__len__", [](const SGenContainer & data) { return data.nb(); }) + .def("__getitem__", [](const SGenContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const SGenContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "SGenInfo", DocIterator::SGenInfo.c_str()) - .def_readonly("id", &DataSGen::SGenInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataSGen::SGenInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataSGen::SGenInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataSGen::SGenInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("min_q_mvar", &DataSGen::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) - .def_readonly("max_q_mvar", &DataSGen::SGenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) - .def_readonly("min_p_mw", &DataSGen::SGenInfo::min_p_mw, DocIterator::min_p_mw.c_str()) - .def_readonly("max_p_mw", &DataSGen::SGenInfo::max_p_mw, DocIterator::max_p_mw.c_str()) - .def_readonly("target_p_mw", &DataSGen::SGenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataSGen::SGenInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataSGen::SGenInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataSGen::SGenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataSGen::SGenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataSGen::SGenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataSGen::SGenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "SGenInfo", DocIterator::SGenInfo.c_str()) + .def_readonly("id", &SGenContainer::SGenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &SGenContainer::SGenInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &SGenContainer::SGenInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &SGenContainer::SGenInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("min_q_mvar", &SGenContainer::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) + .def_readonly("max_q_mvar", &SGenContainer::SGenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) + .def_readonly("min_p_mw", &SGenContainer::SGenInfo::min_p_mw, DocIterator::min_p_mw.c_str()) + .def_readonly("max_p_mw", &SGenContainer::SGenInfo::max_p_mw, DocIterator::max_p_mw.c_str()) + .def_readonly("target_p_mw", &SGenContainer::SGenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &SGenContainer::SGenInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &SGenContainer::SGenInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &SGenContainer::SGenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &SGenContainer::SGenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &SGenContainer::SGenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &SGenContainer::SGenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for loads (and storage units) - py::class_(m, "DataLoad", DocIterator::DataLoad.c_str()) - .def("__len__", [](const DataLoad & data) { return data.nb(); }) - .def("__getitem__", [](const DataLoad & data, int k){return data[k]; } ) - .def("__iter__", [](const DataLoad & data) { + py::class_(m, "LoadContainer", DocIterator::LoadContainer.c_str()) + .def("__len__", [](const LoadContainer & data) { return data.nb(); }) + .def("__getitem__", [](const LoadContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const LoadContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "LoadInfo", DocIterator::LoadInfo.c_str()) - .def_readonly("id", &DataLoad::LoadInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataLoad::LoadInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataLoad::LoadInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataLoad::LoadInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("target_p_mw", &DataLoad::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataLoad::LoadInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataLoad::LoadInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataLoad::LoadInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataLoad::LoadInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataLoad::LoadInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataLoad::LoadInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "LoadInfo", DocIterator::LoadInfo.c_str()) + .def_readonly("id", &LoadContainer::LoadInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &LoadContainer::LoadInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &LoadContainer::LoadInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &LoadContainer::LoadInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("target_p_mw", &LoadContainer::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &LoadContainer::LoadInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &LoadContainer::LoadInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &LoadContainer::LoadInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &LoadContainer::LoadInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &LoadContainer::LoadInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &LoadContainer::LoadInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for shunts - py::class_(m, "DataShunt", DocIterator::DataShunt.c_str()) - .def("__len__", [](const DataShunt & data) { return data.nb(); }) - .def("__getitem__", [](const DataShunt & data, int k){return data[k]; } ) - .def("__iter__", [](const DataShunt & data) { + py::class_(m, "ShuntContainer", DocIterator::ShuntContainer.c_str()) + .def("__len__", [](const ShuntContainer & data) { return data.nb(); }) + .def("__getitem__", [](const ShuntContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const ShuntContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "ShuntInfo", DocIterator::ShuntInfo.c_str()) - .def_readonly("id", &DataShunt::ShuntInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataShunt::ShuntInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataShunt::ShuntInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataShunt::ShuntInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("target_p_mw", &DataShunt::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataShunt::ShuntInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataShunt::ShuntInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataShunt::ShuntInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataShunt::ShuntInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataShunt::ShuntInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataShunt::ShuntInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "ShuntInfo", DocIterator::ShuntInfo.c_str()) + .def_readonly("id", &ShuntContainer::ShuntInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &ShuntContainer::ShuntInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &ShuntContainer::ShuntInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &ShuntContainer::ShuntInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("target_p_mw", &ShuntContainer::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &ShuntContainer::ShuntInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &ShuntContainer::ShuntInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &ShuntContainer::ShuntInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &ShuntContainer::ShuntInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &ShuntContainer::ShuntInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &ShuntContainer::ShuntInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for trafos - py::class_(m, "DataTrafo", DocIterator::DataTrafo.c_str()) - .def("__len__", [](const DataTrafo & data) { return data.nb(); }) - .def("__getitem__", [](const DataTrafo & data, int k){return data[k]; } ) - .def("__iter__", [](const DataTrafo & data) { + py::class_(m, "TrafoContainer", DocIterator::TrafoContainer.c_str()) + .def("__len__", [](const TrafoContainer & data) { return data.nb(); }) + .def("__getitem__", [](const TrafoContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const TrafoContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "TrafoInfo", DocIterator::TrafoInfo.c_str()) - .def_readonly("id", &DataTrafo::TrafoInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataTrafo::TrafoInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataTrafo::TrafoInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_hv_id", &DataTrafo::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str()) - .def_readonly("bus_lv_id", &DataTrafo::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str()) - .def_readonly("r_pu", &DataTrafo::TrafoInfo::r_pu, DocIterator::r_pu.c_str()) - .def_readonly("x_pu", &DataTrafo::TrafoInfo::x_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_pu", &DataTrafo::TrafoInfo::h_pu, DocIterator::h_pu.c_str()) - .def_readonly("is_tap_hv_side", &DataTrafo::TrafoInfo::is_tap_hv_side, DocIterator::is_tap_hv_side.c_str()) - .def_readonly("ratio", &DataTrafo::TrafoInfo::ratio, DocIterator::ratio.c_str()) - .def_readonly("shift_rad", &DataTrafo::TrafoInfo::shift_rad, DocIterator::shift_rad.c_str()) - .def_readonly("has_res", &DataTrafo::TrafoInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_hv_mw", &DataTrafo::TrafoInfo::res_p_hv_mw, DocIterator::res_p_hv_mw.c_str()) - .def_readonly("res_q_hv_mvar", &DataTrafo::TrafoInfo::res_q_hv_mvar, DocIterator::res_q_hv_mvar.c_str()) - .def_readonly("res_v_hv_kv", &DataTrafo::TrafoInfo::res_v_hv_kv, DocIterator::res_v_hv_kv.c_str()) - .def_readonly("res_a_hv_ka", &DataTrafo::TrafoInfo::res_a_hv_ka, DocIterator::res_a_hv_ka.c_str()) - .def_readonly("res_p_lv_mw", &DataTrafo::TrafoInfo::res_p_lv_mw, DocIterator::res_p_lv_mw.c_str()) - .def_readonly("res_q_lv_mvar", &DataTrafo::TrafoInfo::res_q_lv_mvar, DocIterator::res_q_lv_mvar.c_str()) - .def_readonly("res_v_lv_kv", &DataTrafo::TrafoInfo::res_v_lv_kv, DocIterator::res_v_lv_kv.c_str()) - .def_readonly("res_a_lv_ka", &DataTrafo::TrafoInfo::res_a_lv_ka, DocIterator::res_a_lv_ka.c_str()) - .def_readonly("res_theta_hv_deg", &DataTrafo::TrafoInfo::res_theta_hv_deg, DocIterator::res_theta_hv_deg.c_str()) - .def_readonly("res_theta_lv_deg", &DataTrafo::TrafoInfo::res_theta_lv_deg, DocIterator::res_theta_lv_deg.c_str()); + py::class_(m, "TrafoInfo", DocIterator::TrafoInfo.c_str()) + .def_readonly("id", &TrafoContainer::TrafoInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &TrafoContainer::TrafoInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &TrafoContainer::TrafoInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_hv_id", &TrafoContainer::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str()) + .def_readonly("bus_lv_id", &TrafoContainer::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str()) + .def_readonly("r_pu", &TrafoContainer::TrafoInfo::r_pu, DocIterator::r_pu.c_str()) + .def_readonly("x_pu", &TrafoContainer::TrafoInfo::x_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_pu", &TrafoContainer::TrafoInfo::h_pu, DocIterator::h_pu.c_str()) + .def_readonly("is_tap_hv_side", &TrafoContainer::TrafoInfo::is_tap_hv_side, DocIterator::is_tap_hv_side.c_str()) + .def_readonly("ratio", &TrafoContainer::TrafoInfo::ratio, DocIterator::ratio.c_str()) + .def_readonly("shift_rad", &TrafoContainer::TrafoInfo::shift_rad, DocIterator::shift_rad.c_str()) + .def_readonly("has_res", &TrafoContainer::TrafoInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_hv_mw", &TrafoContainer::TrafoInfo::res_p_hv_mw, DocIterator::res_p_hv_mw.c_str()) + .def_readonly("res_q_hv_mvar", &TrafoContainer::TrafoInfo::res_q_hv_mvar, DocIterator::res_q_hv_mvar.c_str()) + .def_readonly("res_v_hv_kv", &TrafoContainer::TrafoInfo::res_v_hv_kv, DocIterator::res_v_hv_kv.c_str()) + .def_readonly("res_a_hv_ka", &TrafoContainer::TrafoInfo::res_a_hv_ka, DocIterator::res_a_hv_ka.c_str()) + .def_readonly("res_p_lv_mw", &TrafoContainer::TrafoInfo::res_p_lv_mw, DocIterator::res_p_lv_mw.c_str()) + .def_readonly("res_q_lv_mvar", &TrafoContainer::TrafoInfo::res_q_lv_mvar, DocIterator::res_q_lv_mvar.c_str()) + .def_readonly("res_v_lv_kv", &TrafoContainer::TrafoInfo::res_v_lv_kv, DocIterator::res_v_lv_kv.c_str()) + .def_readonly("res_a_lv_ka", &TrafoContainer::TrafoInfo::res_a_lv_ka, DocIterator::res_a_lv_ka.c_str()) + .def_readonly("res_theta_hv_deg", &TrafoContainer::TrafoInfo::res_theta_hv_deg, DocIterator::res_theta_hv_deg.c_str()) + .def_readonly("res_theta_lv_deg", &TrafoContainer::TrafoInfo::res_theta_lv_deg, DocIterator::res_theta_lv_deg.c_str()); // iterator for trafos - py::class_(m, "DataLine", DocIterator::DataLine.c_str()) - .def("__len__", [](const DataLine & data) { return data.nb(); }) - .def("__getitem__", [](const DataLine & data, int k){return data[k]; } ) - .def("__iter__", [](const DataLine & data) { + py::class_(m, "LineContainer", DocIterator::LineContainer.c_str()) + .def("__len__", [](const LineContainer & data) { return data.nb(); }) + .def("__getitem__", [](const LineContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const LineContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "LineInfo", DocIterator::LineInfo.c_str()) - .def_readonly("id", &DataLine::LineInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataLine::LineInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataLine::LineInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_or_id", &DataLine::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) - .def_readonly("bus_ex_id", &DataLine::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) - .def_readonly("r_pu", &DataLine::LineInfo::r_pu, DocIterator::r_pu.c_str()) - .def_readonly("x_pu", &DataLine::LineInfo::x_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_pu", &DataLine::LineInfo::h_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_or_pu", &DataLine::LineInfo::h_or_pu, DocIterator::h_pu.c_str()) - .def_readonly("h_ex_pu", &DataLine::LineInfo::h_ex_pu, DocIterator::h_pu.c_str()) - .def_readonly("has_res", &DataLine::LineInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_or_mw", &DataLine::LineInfo::res_p_or_mw, DocIterator::res_p_or_mw.c_str()) - .def_readonly("res_q_or_mvar", &DataLine::LineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar.c_str()) - .def_readonly("res_v_or_kv", &DataLine::LineInfo::res_v_or_kv, DocIterator::res_v_or_kv.c_str()) - .def_readonly("res_a_or_ka", &DataLine::LineInfo::res_a_or_ka, DocIterator::res_a_or_ka.c_str()) - .def_readonly("res_p_ex_mw", &DataLine::LineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw.c_str()) - .def_readonly("res_q_ex_mvar", &DataLine::LineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar.c_str()) - .def_readonly("res_v_ex_kv", &DataLine::LineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv.c_str()) - .def_readonly("res_a_ex_ka", &DataLine::LineInfo::res_a_ex_ka, DocIterator::res_a_ex_ka.c_str()) - .def_readonly("res_theta_or_deg", &DataLine::LineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg.c_str()) - .def_readonly("res_theta_ex_deg", &DataLine::LineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg.c_str()); + py::class_(m, "LineInfo", DocIterator::LineInfo.c_str()) + .def_readonly("id", &LineContainer::LineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &LineContainer::LineInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &LineContainer::LineInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_or_id", &LineContainer::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) + .def_readonly("bus_ex_id", &LineContainer::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) + .def_readonly("r_pu", &LineContainer::LineInfo::r_pu, DocIterator::r_pu.c_str()) + .def_readonly("x_pu", &LineContainer::LineInfo::x_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_pu", &LineContainer::LineInfo::h_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_or_pu", &LineContainer::LineInfo::h_or_pu, DocIterator::h_pu.c_str()) + .def_readonly("h_ex_pu", &LineContainer::LineInfo::h_ex_pu, DocIterator::h_pu.c_str()) + .def_readonly("has_res", &LineContainer::LineInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_or_mw", &LineContainer::LineInfo::res_p_or_mw, DocIterator::res_p_or_mw.c_str()) + .def_readonly("res_q_or_mvar", &LineContainer::LineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar.c_str()) + .def_readonly("res_v_or_kv", &LineContainer::LineInfo::res_v_or_kv, DocIterator::res_v_or_kv.c_str()) + .def_readonly("res_a_or_ka", &LineContainer::LineInfo::res_a_or_ka, DocIterator::res_a_or_ka.c_str()) + .def_readonly("res_p_ex_mw", &LineContainer::LineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw.c_str()) + .def_readonly("res_q_ex_mvar", &LineContainer::LineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar.c_str()) + .def_readonly("res_v_ex_kv", &LineContainer::LineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv.c_str()) + .def_readonly("res_a_ex_ka", &LineContainer::LineInfo::res_a_ex_ka, DocIterator::res_a_ex_ka.c_str()) + .def_readonly("res_theta_or_deg", &LineContainer::LineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg.c_str()) + .def_readonly("res_theta_ex_deg", &LineContainer::LineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg.c_str()); // iterator for dc lines - py::class_(m, "DataDCLine", DocIterator::DataDCLine.c_str()) - .def("__len__", [](const DataDCLine & data) { return data.nb(); }) - .def("__getitem__", [](const DataDCLine & data, int k){return data[k]; } ) - .def("__iter__", [](const DataDCLine & data) { + py::class_(m, "DCLineContainer", DocIterator::DCLineContainer.c_str()) + .def("__len__", [](const DCLineContainer & data) { return data.nb(); }) + .def("__getitem__", [](const DCLineContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const DCLineContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "DCLineInfo", DocIterator::DCLineInfo.c_str()) - .def_readonly("id", &DataDCLine::DCLineInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataDCLine::DCLineInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataDCLine::DCLineInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_or_id", &DataDCLine::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) - .def_readonly("bus_ex_id", &DataDCLine::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) - .def_readonly("target_p_or_mw", &DataDCLine::DCLineInfo::target_p_or_mw, DocIterator::target_p_or_mw.c_str()) - .def_readonly("target_vm_or_pu", &DataDCLine::DCLineInfo::target_vm_or_pu, DocIterator::target_vm_or_pu.c_str()) - .def_readonly("target_vm_ex_pu", &DataDCLine::DCLineInfo::target_vm_ex_pu, DocIterator::target_vm_ex_pu.c_str()) - .def_readonly("loss_pct", &DataDCLine::DCLineInfo::loss_pct, DocIterator::loss_pct.c_str()) - .def_readonly("loss_mw", &DataDCLine::DCLineInfo::loss_mw, DocIterator::loss_mw.c_str()) - .def_readonly("gen_or", &DataDCLine::DCLineInfo::gen_or, DocIterator::gen_or.c_str()) - .def_readonly("gen_ex", &DataDCLine::DCLineInfo::gen_ex, DocIterator::gen_ex.c_str()) - .def_readonly("has_res", &DataDCLine::DCLineInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_or_mw", &DataDCLine::DCLineInfo::res_p_or_mw, DocIterator::res_p_or_mw_dcline.c_str()) - .def_readonly("res_p_ex_mw", &DataDCLine::DCLineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw_dcline.c_str()) - .def_readonly("res_q_or_mvar", &DataDCLine::DCLineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar_dcline.c_str()) - .def_readonly("res_q_ex_mvar", &DataDCLine::DCLineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar_dcline.c_str()) - .def_readonly("res_v_or_kv", &DataDCLine::DCLineInfo::res_v_or_kv, DocIterator::res_v_or_kv_dcline.c_str()) - .def_readonly("res_v_ex_kv", &DataDCLine::DCLineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv_dcline.c_str()) - .def_readonly("res_theta_or_deg", &DataDCLine::DCLineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg_dcline.c_str()) - .def_readonly("res_theta_ex_deg", &DataDCLine::DCLineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg_dcline.c_str()) + py::class_(m, "DCLineInfo", DocIterator::DCLineInfo.c_str()) + .def_readonly("id", &DCLineContainer::DCLineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DCLineContainer::DCLineInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &DCLineContainer::DCLineInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_or_id", &DCLineContainer::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) + .def_readonly("bus_ex_id", &DCLineContainer::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) + .def_readonly("target_p_or_mw", &DCLineContainer::DCLineInfo::target_p_or_mw, DocIterator::target_p_or_mw.c_str()) + .def_readonly("target_vm_or_pu", &DCLineContainer::DCLineInfo::target_vm_or_pu, DocIterator::target_vm_or_pu.c_str()) + .def_readonly("target_vm_ex_pu", &DCLineContainer::DCLineInfo::target_vm_ex_pu, DocIterator::target_vm_ex_pu.c_str()) + .def_readonly("loss_pct", &DCLineContainer::DCLineInfo::loss_pct, DocIterator::loss_pct.c_str()) + .def_readonly("loss_mw", &DCLineContainer::DCLineInfo::loss_mw, DocIterator::loss_mw.c_str()) + .def_readonly("gen_or", &DCLineContainer::DCLineInfo::gen_or, DocIterator::gen_or.c_str()) + .def_readonly("gen_ex", &DCLineContainer::DCLineInfo::gen_ex, DocIterator::gen_ex.c_str()) + .def_readonly("has_res", &DCLineContainer::DCLineInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_or_mw", &DCLineContainer::DCLineInfo::res_p_or_mw, DocIterator::res_p_or_mw_dcline.c_str()) + .def_readonly("res_p_ex_mw", &DCLineContainer::DCLineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw_dcline.c_str()) + .def_readonly("res_q_or_mvar", &DCLineContainer::DCLineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar_dcline.c_str()) + .def_readonly("res_q_ex_mvar", &DCLineContainer::DCLineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar_dcline.c_str()) + .def_readonly("res_v_or_kv", &DCLineContainer::DCLineInfo::res_v_or_kv, DocIterator::res_v_or_kv_dcline.c_str()) + .def_readonly("res_v_ex_kv", &DCLineContainer::DCLineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv_dcline.c_str()) + .def_readonly("res_theta_or_deg", &DCLineContainer::DCLineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg_dcline.c_str()) + .def_readonly("res_theta_ex_deg", &DCLineContainer::DCLineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg_dcline.c_str()) ; // converters @@ -603,6 +603,9 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("need_recompute_sbus", &SolverControl::need_recompute_sbus, "TODO") .def("need_recompute_ybus", &SolverControl::need_recompute_ybus, "TODO") .def("ybus_change_sparsity_pattern", &SolverControl::ybus_change_sparsity_pattern, "TODO") + .def("has_slack_weight_changed", &SolverControl::has_slack_weight_changed, "TODO") + .def("has_v_changed", &SolverControl::has_v_changed, "TODO") + .def("has_ybus_some_coeffs_zero", &SolverControl::has_ybus_some_coeffs_zero, "TODO") ; py::class_(m, "GridModel", DocGridModel::GridModel.c_str()) diff --git a/src/BaseSolver.cpp b/src/powerflow_algorithm/BaseAlgo.cpp similarity index 87% rename from src/BaseSolver.cpp rename to src/powerflow_algorithm/BaseAlgo.cpp index 843d1a41..1849562f 100644 --- a/src/BaseSolver.cpp +++ b/src/powerflow_algorithm/BaseAlgo.cpp @@ -6,11 +6,11 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseSolver.h" +#include "BaseAlgo.h" #include "GridModel.h" // needs to be included here because of the forward declaration -void BaseSolver::reset(){ +void BaseAlgo::reset(){ // reset timers reset_timer(); @@ -27,7 +27,7 @@ void BaseSolver::reset(){ } -RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, +RealVect BaseAlgo::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & pv, @@ -53,7 +53,7 @@ RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, return res; } -RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, +RealVect BaseAlgo::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const CplxVect & Sbus, Eigen::Index slack_id, // id of the ref slack bus @@ -117,7 +117,7 @@ RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, } -bool BaseSolver::_check_for_convergence(const RealVect & F, +bool BaseAlgo::_check_for_convergence(const RealVect & F, real_type tol) { auto timer = CustTimer(); @@ -128,7 +128,7 @@ bool BaseSolver::_check_for_convergence(const RealVect & F, return res; } -bool BaseSolver::_check_for_convergence(const RealVect & p, +bool BaseAlgo::_check_for_convergence(const RealVect & p, const RealVect & q, real_type tol) { @@ -140,7 +140,7 @@ bool BaseSolver::_check_for_convergence(const RealVect & p, return res; } -Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, +Eigen::VectorXi BaseAlgo::extract_slack_bus_id(const Eigen::VectorXi & pv, const Eigen::VectorXi & pq, unsigned int nb_bus) { @@ -151,7 +151,7 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, int nb_slacks = nb_bus - pv.size() - pq.size(); if(nb_slacks == 0){ // TODO DEBUG MODE - throw std::runtime_error("BaseSolver::extract_slack_bus_id: All buses are tagged as PV or PQ, there can be no slack."); + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: All buses are tagged as PV or PQ, there can be no slack."); } Eigen::VectorXi res(nb_slacks); Eigen::Index i_res = 0; @@ -168,7 +168,7 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, { if((i_res >= nb_slacks)){ // TODO DEBUG MODE - throw std::runtime_error("BaseSolver::extract_slack_bus_id: too many slack found. Maybe a bus is both PV and PQ ?"); + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: too many slack found. Maybe a bus is both PV and PQ ?"); } res[i_res] = k; ++i_res; @@ -176,18 +176,18 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, } if(res.size() != i_res){ // TODO DEBUG MODE - throw std::runtime_error("BaseSolver::extract_slack_bus_id: Some slacks are not found in your grid."); + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: Some slacks are not found in your grid."); } return res; } -void BaseSolver::get_Bf(Eigen::SparseMatrix & Bf) const { +void BaseAlgo::get_Bf(Eigen::SparseMatrix & Bf) const { if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); _gridmodel->fillBf_for_PTDF(Bf); } -void BaseSolver::get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const { +void BaseAlgo::get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const { if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); _gridmodel->fillBf_for_PTDF(Bf_T, true); } diff --git a/src/BaseSolver.h b/src/powerflow_algorithm/BaseAlgo.h similarity index 96% rename from src/BaseSolver.h rename to src/powerflow_algorithm/BaseAlgo.h index deeaaa11..8556609b 100644 --- a/src/BaseSolver.h +++ b/src/powerflow_algorithm/BaseAlgo.h @@ -6,8 +6,8 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASESOLVER_H -#define BASESOLVER_H +#ifndef BASEALGO_H +#define BASEALGO_H #include #include @@ -32,17 +32,17 @@ class GridModel; /** -This class represents a solver to compute powerflow. +This class represents a algorithm to compute powerflow. It can be derived for different usecase, for example for DC powerflow, AC powerflow using Newton Raphson method etc. **/ -class BaseSolver : public BaseConstants +class BaseAlgo : public BaseConstants { public: const bool IS_AC; // should be static ideally... public: - BaseSolver(bool is_ac=true): + BaseAlgo(bool is_ac=true): BaseConstants(), IS_AC(is_ac), n_(-1), @@ -52,7 +52,7 @@ class BaseSolver : public BaseConstants timer_check_(0.), timer_total_nr_(0.){}; - virtual ~BaseSolver(){} + virtual ~BaseAlgo(){} void set_gridmodel(const GridModel * gridmodel){ _gridmodel = gridmodel; @@ -233,9 +233,9 @@ class BaseSolver : public BaseConstants private: // no copy allowed - BaseSolver( const BaseSolver & ) ; - BaseSolver & operator=( const BaseSolver & ) ; + BaseAlgo( const BaseAlgo & ) ; + BaseAlgo & operator=( const BaseAlgo & ) ; }; -#endif // BASESOLVER_H +#endif // BASEALGO_H diff --git a/src/DCSolver.h b/src/powerflow_algorithm/BaseDCAlgo.h similarity index 88% rename from src/DCSolver.h rename to src/powerflow_algorithm/BaseDCAlgo.h index 5e0e0352..06e69350 100644 --- a/src/DCSolver.h +++ b/src/powerflow_algorithm/BaseDCAlgo.h @@ -6,23 +6,23 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DCSOLVER_H -#define DCSOLVER_H +#ifndef BASE_DC_ALGO_H +#define BASE_DC_ALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" template -class BaseDCSolver: public BaseSolver +class BaseDCAlgo: public BaseAlgo { public: - BaseDCSolver(): - BaseSolver(false), + BaseDCAlgo(): + BaseAlgo(false), _linear_solver(), need_factorize_(true), sizeYbus_with_slack_(0), sizeYbus_without_slack_(0){}; - ~BaseDCSolver(){} + ~BaseDCAlgo(){} virtual void reset(); @@ -45,8 +45,8 @@ class BaseDCSolver: public BaseSolver private: // no copy allowed - BaseDCSolver( const BaseSolver & ) =delete ; - BaseDCSolver & operator=( const BaseSolver & ) =delete; + BaseDCAlgo( const BaseDCAlgo & ) =delete ; + BaseDCAlgo & operator=( const BaseDCAlgo & ) =delete; protected: void fill_mat_bus_id(int nb_bus_solver); @@ -72,6 +72,6 @@ class BaseDCSolver: public BaseSolver }; -#include "DCSolver.tpp" +#include "BaseDCAlgo.tpp" -#endif // DCSOLVER_H +#endif // BASE_DC_ALGO_H diff --git a/src/DCSolver.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp similarity index 92% rename from src/DCSolver.tpp rename to src/powerflow_algorithm/BaseDCAlgo.tpp index 30b903bc..ed7e8ab6 100644 --- a/src/DCSolver.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -10,7 +10,7 @@ // TODO SLACK !!! template -bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix & Ybus, +bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -37,7 +37,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } auto timer = CustTimer(); - BaseSolver::reset_timer(); + BaseAlgo::reset_timer(); sizeYbus_with_slack_ = static_cast(Ybus.rows()); #ifdef __COUT_TIMES @@ -131,7 +131,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // retrieve back the results in the proper shape (add back the slack bus) // TODO have a better way for this, for example using `.segment(0,npv)` - // see the BaseSolver.cpp: _evaluate_Fx + // see the BaseAlgo.cpp: _evaluate_Fx RealVect Va_dc = RealVect::Constant(sizeYbus_with_slack_, my_zero_); // fill Va from dc approx for (int ybus_id=0; ybus_id < sizeYbus_with_slack_; ++ybus_id){ @@ -164,7 +164,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix } template -void BaseDCSolver::fill_mat_bus_id(int nb_bus_solver){ +void BaseDCAlgo::fill_mat_bus_id(int nb_bus_solver){ mat_bus_id_ = Eigen::VectorXi::Constant(nb_bus_solver, -1); // Eigen::VectorXi me_to_ybus = Eigen::VectorXi::Constant(nb_bus_solver - slack_bus_ids_solver.size(), -1); int solver_id = 0; @@ -177,14 +177,14 @@ void BaseDCSolver::fill_mat_bus_id(int nb_bus_solver){ } template -void BaseDCSolver::fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat){ +void BaseDCAlgo::fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat){ // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29 remove_slack_buses(nb_bus_solver, ref_mat, dcYbus_noslack_); } template template // ref_mat_type should be `real_type` or `cplx_type` -void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ +void BaseDCAlgo::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ res_mat = Eigen::SparseMatrix(sizeYbus_without_slack_, sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? std::vector > tripletList; tripletList.reserve(ref_mat.nonZeros()); @@ -205,8 +205,8 @@ void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eig } template -void BaseDCSolver::reset(){ - BaseSolver::reset(); +void BaseDCAlgo::reset(){ + BaseAlgo::reset(); _linear_solver.reset(); need_factorize_ = true; sizeYbus_with_slack_ = 0; @@ -219,7 +219,7 @@ void BaseDCSolver::reset(){ } template -RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix & dcYbus){ +RealMat BaseDCAlgo::get_ptdf(const Eigen::SparseMatrix & dcYbus){ Eigen::SparseMatrix Bf_T_with_slack; RealMat PTDF; RealVect rhs = RealVect::Zero(sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? @@ -229,7 +229,7 @@ RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix::get_ptdf(const Eigen::SparseMatrix -Eigen::SparseMatrix BaseDCSolver::get_lodf(){ +Eigen::SparseMatrix BaseDCAlgo::get_lodf(){ // TODO return dcYbus_noslack_; } template -Eigen::SparseMatrix BaseDCSolver::get_bsdf(){ +Eigen::SparseMatrix BaseDCAlgo::get_bsdf(){ // TODO return dcYbus_noslack_; diff --git a/src/BaseFDPFSolver.h b/src/powerflow_algorithm/BaseFDPFAlgo.h similarity index 95% rename from src/BaseFDPFSolver.h rename to src/powerflow_algorithm/BaseFDPFAlgo.h index f670eeac..67b98747 100644 --- a/src/BaseFDPFSolver.h +++ b/src/powerflow_algorithm/BaseFDPFAlgo.h @@ -6,19 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASEFDPFSOLVER_H -#define BASEFDPFSOLVER_H +#ifndef BASEFDPFALGO_H +#define BASEFDPFALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" /** Base class for Fast Decoupled Powerflow based solver **/ template -class BaseFDPFSolver : public BaseSolver +class BaseFDPFAlgo: public BaseAlgo { public: - BaseFDPFSolver():BaseSolver(true), need_factorize_(true) {} + BaseFDPFAlgo():BaseAlgo(true), need_factorize_(true) {} virtual bool compute_pf(const Eigen::SparseMatrix & Ybus, @@ -47,7 +47,7 @@ class BaseFDPFSolver : public BaseSolver virtual void reset() { - BaseSolver::reset(); + BaseAlgo::reset(); // solution of the problem Bp_ = Eigen::SparseMatrix (); // the B prime matrix (size n_pvpq) Bpp_ = Eigen::SparseMatrix(); // the B double prime matrix (size n_pq) @@ -67,7 +67,7 @@ class BaseFDPFSolver : public BaseSolver protected: virtual void reset_timer(){ - BaseSolver::reset_timer(); + BaseAlgo::reset_timer(); } CplxVect evaluate_mismatch(const Eigen::SparseMatrix & Ybus, @@ -213,10 +213,10 @@ class BaseFDPFSolver : public BaseSolver private: // no copy allowed - BaseFDPFSolver( const BaseFDPFSolver & ) =delete ; - BaseFDPFSolver & operator=( const BaseFDPFSolver & ) =delete ; + BaseFDPFAlgo( const BaseFDPFAlgo & ) =delete ; + BaseFDPFAlgo & operator=( const BaseFDPFAlgo & ) =delete ; }; -#include "BaseFDPFSolver.tpp" +#include "BaseFDPFAlgo.tpp" -#endif // BASEFDPFSOLVER_H +#endif // BASEFDPFALGO_H diff --git a/src/BaseFDPFSolver.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp similarity index 93% rename from src/BaseFDPFSolver.tpp rename to src/powerflow_algorithm/BaseFDPFAlgo.tpp index 080d1915..fe13caf0 100644 --- a/src/BaseFDPFSolver.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -9,7 +9,7 @@ // inspired from pypower https://github.com/rwl/PYPOWER/blob/master/pypower/fdpf.py template -bool BaseFDPFSolver::compute_pf(const Eigen::SparseMatrix & Ybus, +bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -32,14 +32,14 @@ bool BaseFDPFSolver::compute_pf(const Eigen::SparseMatrix::compute_pf(const Eigen::SparseMatrix -void BaseFDPFSolver::fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp, +void BaseFDPFAlgo::fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp, const Eigen::SparseMatrix & grid_Bpp, const std::vector & pvpq_inv, const std::vector & pq_inv, @@ -166,7 +166,7 @@ void BaseFDPFSolver::fill_sparse_matrices(const Eigen::Spar } template -void BaseFDPFSolver::aux_fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp_Bpp, +void BaseFDPFAlgo::aux_fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp_Bpp, const std::vector & ind_inv, Eigen::Index mat_dim, Eigen::SparseMatrix & res) diff --git a/src/BaseNRSolver.h b/src/powerflow_algorithm/BaseNRAlgo.h similarity index 95% rename from src/BaseNRSolver.h rename to src/powerflow_algorithm/BaseNRAlgo.h index bde0d6aa..2b68155e 100644 --- a/src/BaseNRSolver.h +++ b/src/powerflow_algorithm/BaseNRAlgo.h @@ -6,19 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASENRSOLVER_H -#define BASENRSOLVER_H +#ifndef BASE_NR_ALGO_H +#define BASE_NR_ALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" /** Base class for Newton Raphson based solver **/ template -class BaseNRSolver : public BaseSolver +class BaseNRAlgo : public BaseAlgo { public: - BaseNRSolver():BaseSolver(true), need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {} + BaseNRAlgo():BaseAlgo(true), need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {} virtual Eigen::Ref > get_J() const { @@ -56,7 +56,7 @@ class BaseNRSolver : public BaseSolver protected: virtual void reset_timer(){ - BaseSolver::reset_timer(); + BaseAlgo::reset_timer(); timer_dSbus_ = 0.; timer_fillJ_ = 0.; timer_initialize_ = 0.; @@ -198,8 +198,8 @@ class BaseNRSolver : public BaseSolver private: // no copy allowed - BaseNRSolver( const BaseNRSolver & ) =delete ; - BaseNRSolver & operator=( const BaseNRSolver & ) =delete ; + BaseNRAlgo( const BaseNRAlgo & ) =delete ; + BaseNRAlgo & operator=( const BaseNRAlgo & ) =delete ; /** helper function to print the max_col left most columns of the J matrix **/ void print_J(int min_col=-1, int max_col=-1) const{ @@ -235,6 +235,6 @@ class BaseNRSolver : public BaseSolver } }; -#include "BaseNRSolver.tpp" +#include "BaseNRAlgo.tpp" -#endif // BASENRSOLVER_H +#endif // BASE_NR_ALGO_H diff --git a/src/BaseNRSolver.tpp b/src/powerflow_algorithm/BaseNRAlgo.tpp similarity index 94% rename from src/BaseNRSolver.tpp rename to src/powerflow_algorithm/BaseNRAlgo.tpp index 4dbbfb8e..0977d7d6 100644 --- a/src/BaseNRSolver.tpp +++ b/src/powerflow_algorithm/BaseNRAlgo.tpp @@ -6,13 +6,13 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -// #include "BaseNRSolver.h" // now a template class, so this file will be included instead ! +// #include "BaseNRAlgo.h" // now a template class, so this file will be included instead ! // TODO get rid of the pvpq, pv, pq etc and put the jacobian "in the right order" // to ease and make way faster the filling of the sparse matrix J template -bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix & Ybus, +bool BaseNRAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -35,14 +35,14 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){ // TODO DEBUG MODE std::ostringstream exc_; - exc_ << "BaseNRSolver::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; + exc_ << "BaseNRAlgo::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ // TODO DEBUG MODE std::ostringstream exc_; - exc_ << "BaseNRSolver::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; + exc_ << "BaseNRAlgo::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } @@ -89,12 +89,12 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl; // std::cout << "slack_absorbed " << slack_absorbed << std::endl; value_map_.clear(); // TODO smarter solver: only needed if ybus has changed - // BaseNRSolver::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed - // BaseNRSolver::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed - // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed - // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed while ((!converged) & (nr_iter_ < max_iter)){ nr_iter_++; fill_jacobian_matrix(Ybus, V_, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); @@ -161,8 +161,8 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix } template -void BaseNRSolver::reset(){ - BaseSolver::reset(); +void BaseNRAlgo::reset(){ + BaseAlgo::reset(); // reset specific attributes J_ = Eigen::SparseMatrix(); // the jacobian matrix dS_dVm_ = Eigen::SparseMatrix(); @@ -175,7 +175,7 @@ void BaseNRSolver::reset(){ } template -void BaseNRSolver::_dSbus_dV(const Eigen::Ref > & Ybus, +void BaseNRAlgo::_dSbus_dV(const Eigen::Ref > & Ybus, const Eigen::Ref & V){ // std::cout << "Ybus.nonZeros(): " << Ybus.nonZeros() << std::endl; auto timer = CustTimer(); @@ -236,7 +236,7 @@ void BaseNRSolver::_dSbus_dV(const Eigen::Ref -void BaseNRSolver::_get_values_J(int & nb_obj_this_col, +void BaseNRAlgo::_get_values_J(int & nb_obj_this_col, std::vector & inner_index, std::vector & values, const Eigen::Ref > & mat, // ex. dS_dVa_r @@ -263,7 +263,7 @@ void BaseNRSolver::_get_values_J(int & nb_obj_this_col, } template -void BaseNRSolver::_get_values_J(int & nb_obj_this_col, +void BaseNRAlgo::_get_values_J(int & nb_obj_this_col, std::vector & inner_index, std::vector & values, const Eigen::Ref > & mat, // ex. dS_dVa_r @@ -300,7 +300,7 @@ void BaseNRSolver::_get_values_J(int & nb_obj_this_col, } template -void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, +void BaseNRAlgo::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, const CplxVect & V, Eigen::Index slack_bus_id, const RealVect & slack_weights, @@ -359,7 +359,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< #ifdef __COUT_TIMES auto timer3 = CustTimer(); #endif // - if (BaseNRSolver::value_map_.size() == 0) fill_value_map(slack_bus_id, pq, pvpq, true); + if (BaseNRAlgo::value_map_.size() == 0) fill_value_map(slack_bus_id, pq, pvpq, true); fill_jacobian_matrix_kown_sparsity_pattern(slack_bus_id, pq, pvpq ); @@ -371,7 +371,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< } template -void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( +void BaseNRAlgo::fill_jacobian_matrix_unkown_sparsity_pattern( const Eigen::SparseMatrix & Ybus, const CplxVect & V, Eigen::Index slack_bus_id, @@ -541,7 +541,7 @@ dS_dVa_ and dS_dVm_ to be used to fill J_ it requires that J_ is initialized, in compressed mode. **/ template -void BaseNRSolver::fill_value_map( +void BaseNRAlgo::fill_value_map( Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq, @@ -550,7 +550,7 @@ void BaseNRSolver::fill_value_map( { const int n_pvpq = static_cast(pvpq.size()); value_map_ = std::vector (); - value_map_.reserve(BaseNRSolver::J_.nonZeros()); + value_map_.reserve(BaseNRAlgo::J_.nonZeros()); // col_map_ = std::vector (J_.nonZeros()); // row_map_ = std::vector (J_.nonZeros()); @@ -620,7 +620,7 @@ void BaseNRSolver::fill_value_map( } template -void BaseNRSolver::fill_jacobian_matrix_kown_sparsity_pattern( +void BaseNRAlgo::fill_jacobian_matrix_kown_sparsity_pattern( Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq diff --git a/src/BaseNRSolverSingleSlack.h b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.h similarity index 86% rename from src/BaseNRSolverSingleSlack.h rename to src/powerflow_algorithm/BaseNRSingleSlackAlgo.h index 09f8042f..7ed9ccfd 100644 --- a/src/BaseNRSolverSingleSlack.h +++ b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.h @@ -6,21 +6,21 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASENRSOLVERSINGLESLACK_H -#define BASENRSOLVERSINGLESLACK_H +#ifndef BASE_NR_SINGLESLACK_ALGO_H +#define BASE_NR_SINGLESLACK_ALGO_H -#include "BaseNRSolver.h" +#include "BaseNRAlgo.h" /** Base class for Newton Raphson based solver (only interesting for single slack) **/ template -class BaseNRSolverSingleSlack : public BaseNRSolver +class BaseNRSingleSlackAlgo : public BaseNRAlgo { public: - BaseNRSolverSingleSlack():BaseNRSolver(){} + BaseNRSingleSlackAlgo():BaseNRAlgo(){} - ~BaseNRSolverSingleSlack(){} + ~BaseNRSingleSlackAlgo(){} virtual bool compute_pf(const Eigen::SparseMatrix & Ybus, @@ -63,6 +63,6 @@ class BaseNRSolverSingleSlack : public BaseNRSolver }; -#include "BaseNRSolverSingleSlack.tpp" +#include "BaseNRSingleSlackAlgo.tpp" -#endif // BASENRSOLVERSINGLESLACK_H +#endif // BASE_NR_SINGLESLACK_ALGO_H diff --git a/src/BaseNRSolverSingleSlack.tpp b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp similarity index 65% rename from src/BaseNRSolverSingleSlack.tpp rename to src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp index 7e39ba23..8c0a4299 100644 --- a/src/BaseNRSolverSingleSlack.tpp +++ b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp @@ -7,10 +7,10 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. // #include "BaseNRSolverSingleSlack.h" -// #include "BaseNRSolver.h" +// #include "BaseNRAlgo.h" template -bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix & Ybus, +bool BaseNRSingleSlackAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -30,28 +30,28 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix // TODO Ybus (nrow or ncol), pv and pq have value that are between 0 and nrow etc. if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){ std::ostringstream exc_; - exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; + exc_ << "BaseNRSingleSlackAlgo::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ std::ostringstream exc_; - exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; + exc_ << "BaseNRSingleSlackAlgo::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<<", "<::is_linear_solver_valid()){ + if(!BaseNRAlgo::is_linear_solver_valid()){ return false; } - BaseNRSolver::reset_timer(); - BaseNRSolver::reset_if_needed(); - BaseNRSolver::err_ = ErrorType::NoError; // reset the error if previous error happened + BaseNRAlgo::reset_timer(); + BaseNRAlgo::reset_if_needed(); + BaseNRAlgo::err_ = ErrorType::NoError; // reset the error if previous error happened auto timer = CustTimer(); // initialize once and for all the "inverse" of these vectors - // Eigen::VectorXi my_pv = BaseNRSolver::retrieve_pv_with_slack(slack_ids, pv); + // Eigen::VectorXi my_pv = BaseNRAlgo::retrieve_pv_with_slack(slack_ids, pv); Eigen::VectorXi my_pv = pv; - // Eigen::VectorXi my_pv = pv; // BaseNRSolver::retrieve_pv_with_slack(slack_ids, pv); + // Eigen::VectorXi my_pv = pv; // BaseNRAlgo::retrieve_pv_with_slack(slack_ids, pv); const int n_pv = static_cast(my_pv.size()); const int n_pq = static_cast(pq.size()); @@ -63,33 +63,33 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix std::vector pq_inv(V.size(), -1); for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; - BaseNRSolver::V_ = V; - BaseNRSolver::Vm_ = BaseNRSolver::V_.array().abs(); // update Vm and Va again in case - BaseNRSolver::Va_ = BaseNRSolver::V_.array().arg(); // we wrapped around with a negative Vm + BaseNRAlgo::V_ = V; + BaseNRAlgo::Vm_ = BaseNRAlgo::V_.array().abs(); // update Vm and Va again in case + BaseNRAlgo::Va_ = BaseNRAlgo::V_.array().arg(); // we wrapped around with a negative Vm // first check, if the problem is already solved, i stop there - RealVect F = BaseNRSolver::_evaluate_Fx(Ybus, V, Sbus, my_pv, pq); - bool converged = BaseNRSolver::_check_for_convergence(F, tol); - BaseNRSolver::nr_iter_ = 0; //current step + RealVect F = BaseNRAlgo::_evaluate_Fx(Ybus, V, Sbus, my_pv, pq); + bool converged = BaseNRAlgo::_check_for_convergence(F, tol); + BaseNRAlgo::nr_iter_ = 0; //current step bool res = true; // have i converged or not bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop - const cplx_type m_i = BaseNRSolver::my_i; // otherwise it does not compile - BaseNRSolver::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - BaseNRSolver::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - BaseNRSolver::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - // BaseNRSolver::J_.setZero(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed or ybus_some_coeffs_zero_ - // BaseNRSolver::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed - // BaseNRSolver::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed - while ((!converged) & (BaseNRSolver::nr_iter_ < max_iter)){ - BaseNRSolver::nr_iter_++; - // std::cout << "\tnr_iter_ " << BaseNRSolver::nr_iter_ << std::endl; - fill_jacobian_matrix(Ybus, BaseNRSolver::V_, pq, pvpq, pq_inv, pvpq_inv); - if(BaseNRSolver::need_factorize_){ - BaseNRSolver::initialize(); - if(BaseNRSolver::err_ != ErrorType::NoError){ + const cplx_type m_i = BaseNRAlgo::my_i; // otherwise it does not compile + BaseNRAlgo::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + BaseNRAlgo::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + BaseNRAlgo::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed + // BaseNRAlgo::J_.setZero(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed or ybus_some_coeffs_zero_ + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + while ((!converged) & (BaseNRAlgo::nr_iter_ < max_iter)){ + BaseNRAlgo::nr_iter_++; + // std::cout << "\tnr_iter_ " << BaseNRAlgo::nr_iter_ << std::endl; + fill_jacobian_matrix(Ybus, BaseNRAlgo::V_, pq, pvpq, pq_inv, pvpq_inv); + if(BaseNRAlgo::need_factorize_){ + BaseNRAlgo::initialize(); + if(BaseNRAlgo::err_ != ErrorType::NoError){ // I got an error during the initialization of the linear system, i need to stop here - // std::cout << BaseNRSolver::err_ << std::endl; + // std::cout << BaseNRAlgo::err_ << std::endl; res = false; break; } @@ -99,62 +99,62 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix // std::cout << "no need to factorize" << std::endl; } - BaseNRSolver::solve(F, has_just_been_initialized); + BaseNRAlgo::solve(F, has_just_been_initialized); has_just_been_initialized = false; - if(BaseNRSolver::err_ != ErrorType::NoError){ + if(BaseNRAlgo::err_ != ErrorType::NoError){ // I got an error during the solving of the linear system, i need to stop here - // std::cout << BaseNRSolver::err_ << std::endl; + // std::cout << BaseNRAlgo::err_ << std::endl; res = false; break; } // auto dx = -F; - BaseNRSolver::Vm_ = BaseNRSolver::V_.array().abs(); // update Vm and Va again in case - BaseNRSolver::Va_ = BaseNRSolver::V_.array().arg(); // we wrapped around with a negative Vm + BaseNRAlgo::Vm_ = BaseNRAlgo::V_.array().abs(); // update Vm and Va again in case + BaseNRAlgo::Va_ = BaseNRAlgo::V_.array().arg(); // we wrapped around with a negative Vm // update voltage (this should be done consistently with "klu_solver._evaluate_Fx") - if (n_pv > 0) BaseNRSolver::Va_(my_pv) -= F.segment(0, n_pv); + if (n_pv > 0) BaseNRAlgo::Va_(my_pv) -= F.segment(0, n_pv); if (n_pq > 0){ - BaseNRSolver::Va_(pq) -= F.segment(n_pv,n_pq); - BaseNRSolver::Vm_(pq) -= F.segment(n_pv+n_pq, n_pq); + BaseNRAlgo::Va_(pq) -= F.segment(n_pv,n_pq); + BaseNRAlgo::Vm_(pq) -= F.segment(n_pv+n_pq, n_pq); } // TODO change here for not having to cast all the time ... maybe - const RealVect & Vm = BaseNRSolver::Vm_; // I am forced to redefine the type for it to compile properly - const RealVect & Va = BaseNRSolver::Va_; - BaseNRSolver::V_ = Vm.array() * (Va.array().cos().cast() + m_i * Va.array().sin().cast() ); + const RealVect & Vm = BaseNRAlgo::Vm_; // I am forced to redefine the type for it to compile properly + const RealVect & Va = BaseNRAlgo::Va_; + BaseNRAlgo::V_ = Vm.array() * (Va.array().cos().cast() + m_i * Va.array().sin().cast() ); - F = BaseNRSolver::_evaluate_Fx(Ybus, BaseNRSolver::V_, Sbus, my_pv, pq); + F = BaseNRAlgo::_evaluate_Fx(Ybus, BaseNRAlgo::V_, Sbus, my_pv, pq); bool tmp = F.allFinite(); if(!tmp){ - BaseNRSolver::err_ = ErrorType::InifiniteValue; - // std::cout << BaseNRSolver::err_ << std::endl; + BaseNRAlgo::err_ = ErrorType::InifiniteValue; + // std::cout << BaseNRAlgo::err_ << std::endl; break; // divergence due to Nans } - converged = BaseNRSolver::_check_for_convergence(F, tol); + converged = BaseNRAlgo::_check_for_convergence(F, tol); } if(!converged){ - if (BaseNRSolver::err_ == ErrorType::NoError) BaseNRSolver::err_ = ErrorType::TooManyIterations; + if (BaseNRAlgo::err_ == ErrorType::NoError) BaseNRAlgo::err_ = ErrorType::TooManyIterations; res = false; } - BaseNRSolver::timer_total_nr_ += timer.duration(); + BaseNRAlgo::timer_total_nr_ += timer.duration(); #ifdef __COUT_TIMES - std::cout << "Computation time: " << "\n\t timer_initialize_: " << BaseNRSolver::timer_initialize_ - << "\n\t timer_dSbus_ (called in _fillJ_): " << BaseNRSolver::timer_dSbus_ - << "\n\t timer_fillJ_: " << BaseNRSolver::timer_fillJ_ - << "\n\t timer_Fx_: " << BaseNRSolver::timer_Fx_ - << "\n\t timer_check_: " << BaseNRSolver::timer_check_ - << "\n\t timer_solve_: " << BaseNRSolver::timer_solve_ - << "\n\t timer_total_nr_: " << BaseNRSolver::timer_total_nr_ + std::cout << "Computation time: " << "\n\t timer_initialize_: " << BaseNRAlgo::timer_initialize_ + << "\n\t timer_dSbus_ (called in _fillJ_): " << BaseNRAlgo::timer_dSbus_ + << "\n\t timer_fillJ_: " << BaseNRAlgo::timer_fillJ_ + << "\n\t timer_Fx_: " << BaseNRAlgo::timer_Fx_ + << "\n\t timer_check_: " << BaseNRAlgo::timer_check_ + << "\n\t timer_solve_: " << BaseNRAlgo::timer_solve_ + << "\n\t timer_total_nr_: " << BaseNRAlgo::timer_total_nr_ << "\n\n"; #endif // __COUT_TIMES - BaseNRSolver::_solver_control.tell_none_changed(); + BaseNRAlgo::_solver_control.tell_none_changed(); return res; } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, +void BaseNRSingleSlackAlgo::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq, @@ -175,7 +175,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp **/ auto timer = CustTimer(); - BaseNRSolver::_dSbus_dV(Ybus, V); + BaseNRAlgo::_dSbus_dV(Ybus, V); const int n_pvpq = static_cast(pvpq.size()); const int n_pq = static_cast(pq.size()); @@ -183,7 +183,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp // TODO to gain a bit more time below, try to compute directly, in _dSbus_dV(Ybus, V); // TODO the `dS_dVa_[pvpq, pvpq]` // TODO so that it's easier to retrieve in the next few lines ! - if(BaseNRSolver::J_.cols() != size_j) + if(BaseNRAlgo::J_.cols() != size_j) // if(true) { #ifdef __COUT_TIMES @@ -202,7 +202,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #ifdef __COUT_TIMES auto timer3 = CustTimer(); #endif // __COUT_TIMES - if (BaseNRSolver::value_map_.size() == 0){ + if (BaseNRAlgo::value_map_.size() == 0){ // std::cout << "\t\tfill_value_map called" << std::endl; fill_value_map(pq, pvpq, true); } @@ -212,11 +212,11 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp std::cout << "\t\t fill_jacobian_matrix_kown_sparsity_pattern : " << timer3.duration() << std::endl; #endif // __COUT_TIMES } - BaseNRSolver::timer_fillJ_ += timer.duration(); + BaseNRAlgo::timer_fillJ_ += timer.duration(); } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity_pattern( +void BaseNRSingleSlackAlgo::fill_jacobian_matrix_unkown_sparsity_pattern( const Eigen::SparseMatrix & Ybus, const CplxVect & V, const Eigen::VectorXi & pq, @@ -246,22 +246,22 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity const int n_pq = static_cast(pq.size()); const int size_j = n_pvpq + n_pq; - const Eigen::SparseMatrix dS_dVa_r = BaseNRSolver::dS_dVa_.real(); - const Eigen::SparseMatrix dS_dVa_i = BaseNRSolver::dS_dVa_.imag(); - const Eigen::SparseMatrix dS_dVm_r = BaseNRSolver::dS_dVm_.real(); - const Eigen::SparseMatrix dS_dVm_i = BaseNRSolver::dS_dVm_.imag(); + const Eigen::SparseMatrix dS_dVa_r = BaseNRAlgo::dS_dVa_.real(); + const Eigen::SparseMatrix dS_dVa_i = BaseNRAlgo::dS_dVa_.imag(); + const Eigen::SparseMatrix dS_dVm_r = BaseNRAlgo::dS_dVm_.real(); + const Eigen::SparseMatrix dS_dVm_i = BaseNRAlgo::dS_dVm_.imag(); // Method (1) seems to be faster than the others // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can // i can do that because the matrix will ALWAYS have the same non zero coefficients. // in this if, i allocate it in a "large enough" place to avoid copy when first filling it - if(BaseNRSolver::J_.cols() != size_j) + if(BaseNRAlgo::J_.cols() != size_j) { need_insert = true; - BaseNRSolver::J_ = Eigen::SparseMatrix(size_j, size_j); + BaseNRAlgo::J_ = Eigen::SparseMatrix(size_j, size_j); // pre allocate a large enough matrix - BaseNRSolver::J_.reserve(2*(BaseNRSolver::dS_dVa_.nonZeros() + BaseNRSolver::dS_dVm_.nonZeros())); + BaseNRAlgo::J_.reserve(2*(BaseNRAlgo::dS_dVa_.nonZeros() + BaseNRAlgo::dS_dVm_.nonZeros())); // from an experiment, outerIndexPtr is initialized, with the number of columns // innerIndexPtr and valuePtr are not. } @@ -284,14 +284,14 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // fill with the first column with the column of dS_dVa[:,pvpq[col_id]] // and check the row order ! - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVa_r, pvpq_inv, pvpq, col_id, 0, 0); // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]] - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVa_i, pq_inv, pvpq, col_id, @@ -301,8 +301,8 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // "efficient" insert of the element in the matrix for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){ int row_id = inner_index[in_ind]; - if(need_insert) BaseNRSolver::J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) - else BaseNRSolver::J_.coeffRef(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) + if(need_insert) BaseNRAlgo::J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) + else BaseNRAlgo::J_.coeffRef(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) // J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (2) // coeffs.push_back(Eigen::Triplet(row_id, col_id, values[in_ind])); // HERE FOR PERF OPTIM (3) } @@ -318,7 +318,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // fill with the first column with the column of dS_dVa[:,pvpq[col_id]] // and check the row order ! - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVm_r, pvpq_inv, pq, col_id, @@ -326,7 +326,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity 0); // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]] - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVm_i, pq_inv, pq, col_id, @@ -336,14 +336,14 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // "efficient" insert of the element in the matrix for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){ int row_id = inner_index[in_ind]; - if(need_insert) BaseNRSolver::J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) - else BaseNRSolver::J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) + if(need_insert) BaseNRAlgo::J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) + else BaseNRAlgo::J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) // J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (2) // coeffs.push_back(Eigen::Triplet(row_id, col_id + n_pvpq, values[in_ind])); // HERE FOR PERF OPTIM (3) } } // J_.setFromTriplets(coeffs.begin(), coeffs.end()); // HERE FOR PERF OPTIM (3) - BaseNRSolver::J_.makeCompressed(); + BaseNRAlgo::J_.makeCompressed(); } /** @@ -352,21 +352,21 @@ dS_dVa_ and dS_dVm_ to be used to fill J_ it requires that J_ is initialized, in compressed mode. **/ template -void BaseNRSolverSingleSlack::fill_value_map( +void BaseNRSingleSlackAlgo::fill_value_map( const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq, bool reset_J ) { const int n_pvpq = static_cast(pvpq.size()); - BaseNRSolver::value_map_.clear(); - // std::cout << "BaseNRSolver::J_.nonZeros(): " << BaseNRSolver::J_.nonZeros() << std::endl; - BaseNRSolver::value_map_.reserve(BaseNRSolver::J_.nonZeros()); + BaseNRAlgo::value_map_.clear(); + // std::cout << "BaseNRAlgo::J_.nonZeros(): " << BaseNRAlgo::J_.nonZeros() << std::endl; + BaseNRAlgo::value_map_.reserve(BaseNRAlgo::J_.nonZeros()); - const int n_col = static_cast(BaseNRSolver::J_.cols()); + const int n_col = static_cast(BaseNRAlgo::J_.cols()); unsigned int pos_el = 0; for (int col_=0; col_ < n_col; ++col_){ - for (Eigen::SparseMatrix::InnerIterator it(BaseNRSolver::J_, col_); it; ++it) + for (Eigen::SparseMatrix::InnerIterator it(BaseNRAlgo::J_, col_); it; ++it) { const int row_id = static_cast(it.row()); const int col_id = static_cast(it.col()); // it's equal to "col_" @@ -377,7 +377,7 @@ void BaseNRSolverSingleSlack::fill_value_map( const int row_id_dS_dVa_r = pvpq[row_id]; const int col_id_dS_dVa_r = pvpq[col_id]; // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r); - BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place // const int is_row_non_null = pq_inv[row_id_dS_dVa_r]; @@ -392,31 +392,31 @@ void BaseNRSolverSingleSlack::fill_value_map( const int row_id_dS_dVa_i = pq[row_id - n_pvpq]; const int col_id_dS_dVa_i = pvpq[col_id]; // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i); - BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){ // this is the J12 part (dS_dVm_r) const int row_id_dS_dVm_r = pvpq[row_id]; const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r); - BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){ // this is the J22 part (dS_dVm_i) const int row_id_dS_dVm_i = pq[row_id - n_pvpq]; const int col_id_dS_dVm_i = pq[col_id - n_pvpq]; // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i); - BaseNRSolver::value_map_.push_back(&BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); } // go to the next element ++pos_el; } } - // BaseNRSolver::dS_dVa_.makeCompressed(); - // BaseNRSolver::dS_dVm_.makeCompressed(); + // BaseNRAlgo::dS_dVa_.makeCompressed(); + // BaseNRAlgo::dS_dVm_.makeCompressed(); } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix_kown_sparsity_pattern( +void BaseNRSingleSlackAlgo::fill_jacobian_matrix_kown_sparsity_pattern( const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq ) @@ -443,15 +443,15 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_kown_sparsity_p const int n_pvpq = static_cast(pvpq.size()); // real_type * J_x_ptr = J_.valuePtr(); - const int n_cols = static_cast(BaseNRSolver::J_.cols()); // equal to nrow + const int n_cols = static_cast(BaseNRAlgo::J_.cols()); // equal to nrow unsigned int pos_el = 0; for (int col_id=0; col_id < n_cols; ++col_id){ - for (Eigen::SparseMatrix::InnerIterator it(BaseNRSolver::J_, col_id); it; ++it) + for (Eigen::SparseMatrix::InnerIterator it(BaseNRAlgo::J_, col_id); it; ++it) { const auto row_id = it.row(); // only one if is necessary (magic !) // top rows are "real" part and bottom rows are imaginary part (you can check) - it.valueRef() = row_id < n_pvpq ? std::real(*BaseNRSolver::value_map_[pos_el]) : std::imag(*BaseNRSolver::value_map_[pos_el]); + it.valueRef() = row_id < n_pvpq ? std::real(*BaseNRAlgo::value_map_[pos_el]) : std::imag(*BaseNRAlgo::value_map_[pos_el]); // go to the next element ++pos_el; } diff --git a/src/GaussSeidelSolver.cpp b/src/powerflow_algorithm/GaussSeidelAlgo.cpp similarity index 96% rename from src/GaussSeidelSolver.cpp rename to src/powerflow_algorithm/GaussSeidelAlgo.cpp index 365d02a1..448dc9f6 100644 --- a/src/GaussSeidelSolver.cpp +++ b/src/powerflow_algorithm/GaussSeidelAlgo.cpp @@ -6,9 +6,9 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "GaussSeidelSolver.h" +#include "GaussSeidelAlgo.h" -bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix & Ybus, +bool GaussSeidelAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -78,7 +78,7 @@ bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix & Ybus, return res; } -void GaussSeidelSolver::one_iter(CplxVect & tmp_Sbus, +void GaussSeidelAlgo::one_iter(CplxVect & tmp_Sbus, const Eigen::SparseMatrix & Ybus, const Eigen::VectorXi & pv, const Eigen::VectorXi & pq) diff --git a/src/GaussSeidelSolver.h b/src/powerflow_algorithm/GaussSeidelAlgo.h similarity index 81% rename from src/GaussSeidelSolver.h rename to src/powerflow_algorithm/GaussSeidelAlgo.h index 26e0a46f..2939966f 100644 --- a/src/GaussSeidelSolver.h +++ b/src/powerflow_algorithm/GaussSeidelAlgo.h @@ -6,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef GAUSSSEIDELSOLVER_H -#define GAUSSSEIDELSOLVER_H +#ifndef GAUSSSEIDEL_ALGO_H +#define GAUSSSEIDEL_ALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" -class GaussSeidelSolver : public BaseSolver +class GaussSeidelAlgo : public BaseAlgo { public: - GaussSeidelSolver():BaseSolver() {}; + GaussSeidelAlgo():BaseAlgo(true) {}; - ~GaussSeidelSolver(){} + ~GaussSeidelAlgo(){} // todo can be factorized Eigen::SparseMatrix get_J(){ @@ -47,9 +47,9 @@ class GaussSeidelSolver : public BaseSolver private: // no copy allowed - GaussSeidelSolver( const GaussSeidelSolver & ) ; - GaussSeidelSolver & operator=( const GaussSeidelSolver & ) ; + GaussSeidelAlgo( const GaussSeidelAlgo & ) =delete; + GaussSeidelAlgo & operator=( const GaussSeidelAlgo & ) =delete; }; -#endif // GAUSSSEIDELSOLVER_H +#endif // GAUSSSEIDEL_ALGO_H diff --git a/src/GaussSeidelSynchSolver.cpp b/src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp similarity index 95% rename from src/GaussSeidelSynchSolver.cpp rename to src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp index 9225709c..1a31bdbf 100644 --- a/src/GaussSeidelSynchSolver.cpp +++ b/src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp @@ -6,9 +6,9 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "GaussSeidelSynchSolver.h" +#include "GaussSeidelSynchAlgo.h" -void GaussSeidelSynchSolver::one_iter(CplxVect & tmp_Sbus, +void GaussSeidelSynchAlgo::one_iter(CplxVect & tmp_Sbus, const Eigen::SparseMatrix & Ybus, const Eigen::VectorXi & pv, const Eigen::VectorXi & pq) diff --git a/src/GaussSeidelSynchSolver.h b/src/powerflow_algorithm/GaussSeidelSynchAlgo.h similarity index 68% rename from src/GaussSeidelSynchSolver.h rename to src/powerflow_algorithm/GaussSeidelSynchAlgo.h index 57607ea3..7749f08f 100644 --- a/src/GaussSeidelSynchSolver.h +++ b/src/powerflow_algorithm/GaussSeidelSynchAlgo.h @@ -6,21 +6,21 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef GAUSSSEIDELSYNCHSOLVER_H -#define GAUSSSEIDELSYNCHSOLVER_H +#ifndef GAUSSSEIDELSYNCH_ALGO_H +#define GAUSSSEIDELSYNCH_ALGO_H -#include "GaussSeidelSolver.h" +#include "GaussSeidelAlgo.h" /** The gauss seidel method, where all the updates are happening in a synchronous way, instead of in a asynchronous way (like for standard gauss seidel) **/ -class GaussSeidelSynchSolver : public GaussSeidelSolver +class GaussSeidelSynchAlgo: public GaussSeidelAlgo { public: - GaussSeidelSynchSolver():GaussSeidelSolver() {}; + GaussSeidelSynchAlgo():GaussSeidelAlgo() {}; - ~GaussSeidelSynchSolver(){} + ~GaussSeidelSynchAlgo(){} protected: void one_iter(CplxVect & tmp_Sbus, @@ -31,9 +31,9 @@ class GaussSeidelSynchSolver : public GaussSeidelSolver private: // no copy allowed - GaussSeidelSynchSolver( const GaussSeidelSynchSolver & ) ; - GaussSeidelSynchSolver & operator=( const GaussSeidelSynchSolver & ) ; + GaussSeidelSynchAlgo( const GaussSeidelSynchAlgo & ) =delete; + GaussSeidelSynchAlgo & operator=( const GaussSeidelSynchAlgo & )=delete ; }; -#endif // GAUSSSEIDELSYNCHSOLVER_H +#endif // GAUSSSEIDELSYNCH_ALGO_H From 94f707d1ed21c0616b217e9c728bbe29c65707a1 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 18 Dec 2023 17:23:45 +0100 Subject: [PATCH 39/66] start systematic testing for solver control --- lightsim2grid/tests/test_solver_control.py | 198 +++++++++++++++++++++ lightsim2grid/tests/test_turnedoff_nopv.py | 4 +- src/powerflow_algorithm/BaseDCAlgo.tpp | 8 +- src/powerflow_algorithm/BaseNRAlgo.h | 1 + 4 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 lightsim2grid/tests/test_solver_control.py diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py new file mode 100644 index 00000000..447042d1 --- /dev/null +++ b/lightsim2grid/tests/test_solver_control.py @@ -0,0 +1,198 @@ +# TODO: test that "if I do something, then with or without the speed optim, I have the same thing" + +# use backend._grid.tell_solver_need_reset() to force the reset of the solver +# and by "something" do : +# - disconnect line X +# - reconnect line X +# - change load +# - change gen +# - change shunt +# - change slack +# - change slack weight +# - when it diverges (and then I can make it un converge normally) +# - test change bus to -1 and deactivate element has the same impact + +# TODO and do that for all solver type: NR, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX +# and for all solver check everything that can be check: final tolerance, number of iteration, etc. etc. + +import unittest +import warnings +import numpy as np +import grid2op +from grid2op.Action import CompleteAction +from lightsim2grid import LightSimBackend + + +class TestSolverControl(unittest.TestCase): + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make("educ_case14_storage", + test=True, + action_class=CompleteAction, + backend=LightSimBackend()) + self.gridmodel = self.env.backend._grid + self.v_init = 1.0 * self.env.backend.V + self.iter = 10 + self.tol_solver = 1e-8 # solver + self.tol_equal = 1e-10 # for comparing with and without the "smarter solver" things, and make sure everything is really equal! + + def test_pf_run_dc(self): + """test I have the same results if nothing is done with and without restarting from scratch when running dc powerflow""" + Vdc_init = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vdc_init), f"error: gridmodel should converge in DC" + self.gridmodel.unset_changes() + Vdc_init2 = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vdc_init2), f"error: gridmodel should converge in DC" + self.gridmodel.tell_solver_need_reset() + Vdc_init3 = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vdc_init3), f"error: gridmodel should converge in DC" + assert np.allclose(Vdc_init, Vdc_init2, rtol=self.tol_equal, atol=self.tol_equal) + assert np.allclose(Vdc_init2, Vdc_init3, rtol=self.tol_equal, atol=self.tol_equal) + + def test_pf_run_ac(self): + """test I have the same results if nothing is done with and without restarting from scratch when running ac powerflow""" + Vac_init = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vac_init), f"error: gridmodel should converge in AC" + self.gridmodel.unset_changes() + Vac_init2 = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vac_init2), f"error: gridmodel should converge in AC" + self.gridmodel.tell_solver_need_reset() + Vac_init3 = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver) + assert len(Vac_init3), f"error: gridmodel should converge in AC" + assert np.allclose(Vac_init, Vac_init2, rtol=self.tol_equal, atol=self.tol_equal) + assert np.allclose(Vac_init2, Vac_init3, rtol=self.tol_equal, atol=self.tol_equal) + + def _disco_line_action(self, gridmodel, el_id=0, el_val=0.): + gridmodel.deactivate_powerline(el_id) + + def _reco_line_action(self, gridmodel, el_id=0, el_val=0.): + gridmodel.reactivate_powerline(el_id) + + def _disco_trafo_action(self, gridmodel, el_id=0, el_val=0.): + gridmodel.deactivate_trafo(el_id) + + def _reco_trafo_action(self, gridmodel, el_id=0, el_val=0.): + gridmodel.reactivate_trafo(el_id) + + def _run_ac_pf(self, gridmodel): + return gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver) + + def _run_dc_pf(self, gridmodel): + return gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver) + + def aux_do_undo_ac(self, + runpf_fun="_run_ac_pf", + funname_do="_disco_line_action", + funname_undo="_reco_line_action", + el_id=0, + el_val=0., + expected_diff=0.1 + ): + pf_mode = "AC" if runpf_fun=="_run_ac_pf" else "DC" + print(f"Looking at {el_id} in {pf_mode}") + + # test "do the action" + V_init = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_init), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + + getattr(self, funname_do)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val) + V_disc = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + if len(V_disc) > 0: + # powerflow converges, all should converge + assert (np.abs(V_init - V_disc) >= expected_diff).any(), f"error for el_id={el_id}: at least one bus should have changed its result voltage in {pf_mode}" + self.gridmodel.unset_changes() + V_disc1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_disc1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + assert np.allclose(V_disc, V_disc1, rtol=self.tol_equal, atol=self.tol_equal) + self.gridmodel.tell_solver_need_reset() + V_disc2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_disc2), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + assert np.allclose(V_disc2, V_disc1, rtol=self.tol_equal, atol=self.tol_equal) + assert np.allclose(V_disc2, V_disc, rtol=self.tol_equal, atol=self.tol_equal) + else: + #powerflow diverges + self.gridmodel.unset_changes() + V_disc1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_disc1) == 0, f"error for el_id={el_id}: powerflow should diverge as it did initially in {pf_mode}" + self.gridmodel.tell_solver_need_reset() + V_disc2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_disc1) == 0, f"error for el_id={el_id}: powerflow should diverge as it did initially in {pf_mode}" + + # test "undo the action" + self.gridmodel.unset_changes() + getattr(self, funname_undo)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val) + V_reco = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_reco), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + assert np.allclose(V_reco, V_init, rtol=self.tol_equal, atol=self.tol_equal), f"error for el_id={el_id}: do an action and then undo it should not have any impact in {pf_mode}" + self.gridmodel.unset_changes() + V_reco1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_reco1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + assert np.allclose(V_reco1, V_reco, rtol=self.tol_equal, atol=self.tol_equal) + self.gridmodel.tell_solver_need_reset() + V_reco2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V_reco2), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" + assert np.allclose(V_reco2, V_reco1, rtol=self.tol_equal, atol=self.tol_equal) + assert np.allclose(V_reco2, V_reco, rtol=self.tol_equal, atol=self.tol_equal) + + def test_disco_reco_line_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I disconnect a line with and + without restarting from scratch when running ac powerflow""" + for el_id in range(len(self.gridmodel.get_lines())): + self.gridmodel.tell_solver_need_reset() + expected_diff = 3e-2 + if runpf_fun=="_run_ac_pf": + if el_id == 4: + expected_diff = 1e-2 + elif el_id == 10: + expected_diff = 1e-3 + elif el_id == 12: + expected_diff = 1e-2 + elif el_id == 13: + expected_diff = 3e-3 + elif runpf_fun=="_run_dc_pf": + if el_id == 4: + expected_diff = 1e-2 + elif el_id == 8: + expected_diff = 1e-2 + elif el_id == 10: + expected_diff = 1e-2 + elif el_id == 12: + expected_diff = 1e-2 + elif el_id == 13: + expected_diff = 3e-3 + elif el_id == 14: + expected_diff = 1e-2 + self.aux_do_undo_ac(funname_do="_disco_line_action", + funname_undo="_reco_line_action", + runpf_fun=runpf_fun, + el_id=el_id, + expected_diff=expected_diff + ) + + def test_disco_reco_line_dc(self): + """test I have the same results if I disconnect a line with and + without restarting from scratch when running DC powerflow""" + self.test_disco_reco_line_ac(runpf_fun="_run_dc_pf") + + def test_disco_reco_trafo_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I disconnect a trafo with and + without restarting from scratch when running ac powerflow""" + for el_id in range(len(self.gridmodel.get_trafos())): + self.gridmodel.tell_solver_need_reset() + expected_diff = 3e-2 + if runpf_fun=="_run_ac_pf": + if el_id == 1: + expected_diff = 1e-2 + self.aux_do_undo_ac(funname_do="_disco_trafo_action", + funname_undo="_reco_trafo_action", + runpf_fun=runpf_fun, + el_id=el_id, + expected_diff=expected_diff + ) + + def test_disco_reco_trafo_dc(self): + """test I have the same results if I disconnect a trafo with and + without restarting from scratch when running DC powerflow""" + self.test_disco_reco_trafo_ac(runpf_fun="_run_dc_pf") + \ No newline at end of file diff --git a/lightsim2grid/tests/test_turnedoff_nopv.py b/lightsim2grid/tests/test_turnedoff_nopv.py index 49fb8d33..0059d93a 100644 --- a/lightsim2grid/tests/test_turnedoff_nopv.py +++ b/lightsim2grid/tests/test_turnedoff_nopv.py @@ -100,8 +100,8 @@ def test_after_runner(self): """test I can use the runner""" runner_pv = Runner(**self.env_pv.get_params_for_runner()) runner_npv = Runner(**self.env_npv.get_params_for_runner()) - res_pv = runner_pv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) - res_npv = runner_npv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True) + res_pv = runner_pv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, env_seeds=[0]) + res_npv = runner_npv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, env_seeds=[0]) assert res_pv[0][3] == res_npv[0][3] # same number of steps survived assert res_pv[0][2] != res_npv[0][2] # not the same reward ep_pv = res_pv[0][-1] diff --git a/src/powerflow_algorithm/BaseDCAlgo.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp index ed7e8ab6..0f249cb3 100644 --- a/src/powerflow_algorithm/BaseDCAlgo.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -30,14 +30,17 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & // err_ = ErrorType::NotInitError; return false; } + BaseAlgo::reset_timer(); + + auto timer = CustTimer(); if(_solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || _solver_control.has_ybus_some_coeffs_zero()){ reset(); } - auto timer = CustTimer(); - BaseAlgo::reset_timer(); + if (_solver_control.ybus_change_sparsity_pattern()) need_factorize_ = true; + sizeYbus_with_slack_ = static_cast(Ybus.rows()); #ifdef __COUT_TIMES @@ -83,7 +86,6 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & #endif // __COUT_TIMES bool just_factorize = false; if(need_factorize_){ - // std::cout << "\t\t\t\t need_factorize_ \n"; ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; diff --git a/src/powerflow_algorithm/BaseNRAlgo.h b/src/powerflow_algorithm/BaseNRAlgo.h index 2b68155e..d593092b 100644 --- a/src/powerflow_algorithm/BaseNRAlgo.h +++ b/src/powerflow_algorithm/BaseNRAlgo.h @@ -142,6 +142,7 @@ class BaseNRAlgo : public BaseAlgo void reset_if_needed(){ if(_solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || + _solver_control.ybus_change_sparsity_pattern() || _solver_control.has_ybus_some_coeffs_zero() || _solver_control.has_slack_participate_changed() || _solver_control.has_pv_changed() || From ca7dc71c7888e988ad6bd132e72c1f174775a949 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 19 Dec 2023 17:28:29 +0100 Subject: [PATCH 40/66] adding more test for the solver control --- lightsim2grid/tests/test_solver_control.py | 246 ++++++++++++++++++++- src/main.cpp | 1 - 2 files changed, 238 insertions(+), 9 deletions(-) diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index 447042d1..2104eafc 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -3,12 +3,16 @@ # use backend._grid.tell_solver_need_reset() to force the reset of the solver # and by "something" do : # - disconnect line X +# - disconnect trafo X # - reconnect line X -# - change load -# - change gen -# - change shunt +# - reconnect trafo X +# - change load X +# - change gen X +# - change shunt X +# - change storage X # - change slack # - change slack weight +# - turnoff_gen_pv # - when it diverges (and then I can make it un converge normally) # - test change bus to -1 and deactivate element has the same impact @@ -21,9 +25,16 @@ import grid2op from grid2op.Action import CompleteAction from lightsim2grid import LightSimBackend +from lightsim2grid.solver import SolverType class TestSolverControl(unittest.TestCase): + def _aux_setup_grid(self): + self.need_dc = True # is it worth it to run DC powerflow ? + self.can_dist_slack = True + self.gridmodel.change_solver(SolverType.SparseLU) + self.gridmodel.change_solver(SolverType.DC) + def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") @@ -32,6 +43,7 @@ def setUp(self) -> None: action_class=CompleteAction, backend=LightSimBackend()) self.gridmodel = self.env.backend._grid + self._aux_setup_grid() self.v_init = 1.0 * self.env.backend.V self.iter = 10 self.tol_solver = 1e-8 # solver @@ -39,6 +51,8 @@ def setUp(self) -> None: def test_pf_run_dc(self): """test I have the same results if nothing is done with and without restarting from scratch when running dc powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") Vdc_init = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver) assert len(Vdc_init), f"error: gridmodel should converge in DC" self.gridmodel.unset_changes() @@ -87,20 +101,19 @@ def aux_do_undo_ac(self, funname_undo="_reco_line_action", el_id=0, el_val=0., + to_add_remove=0., expected_diff=0.1 ): pf_mode = "AC" if runpf_fun=="_run_ac_pf" else "DC" - print(f"Looking at {el_id} in {pf_mode}") - + # test "do the action" V_init = getattr(self, runpf_fun)(gridmodel=self.gridmodel) assert len(V_init), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" - - getattr(self, funname_do)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val) + getattr(self, funname_do)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val + to_add_remove) V_disc = getattr(self, runpf_fun)(gridmodel=self.gridmodel) if len(V_disc) > 0: # powerflow converges, all should converge - assert (np.abs(V_init - V_disc) >= expected_diff).any(), f"error for el_id={el_id}: at least one bus should have changed its result voltage in {pf_mode}" + assert (np.abs(V_init - V_disc) >= expected_diff).any(), f"error for el_id={el_id}: at least one bus should have changed its result voltage in {pf_mode}: max {np.abs(V_init - V_disc).max():.2e}" self.gridmodel.unset_changes() V_disc1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) assert len(V_disc1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" @@ -173,6 +186,8 @@ def test_disco_reco_line_ac(self, runpf_fun="_run_ac_pf"): def test_disco_reco_line_dc(self): """test I have the same results if I disconnect a line with and without restarting from scratch when running DC powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") self.test_disco_reco_line_ac(runpf_fun="_run_dc_pf") def test_disco_reco_trafo_ac(self, runpf_fun="_run_ac_pf"): @@ -194,5 +209,220 @@ def test_disco_reco_trafo_ac(self, runpf_fun="_run_ac_pf"): def test_disco_reco_trafo_dc(self): """test I have the same results if I disconnect a trafo with and without restarting from scratch when running DC powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") self.test_disco_reco_trafo_ac(runpf_fun="_run_dc_pf") + + def _change_load_p_action(self, gridmodel, el_id, el_val): + gridmodel.change_p_load(el_id, el_val) + + def test_change_load_p_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the load p with and + without restarting from scratch when running ac powerflow""" + for load in self.gridmodel.get_loads(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_load_p_action", + funname_undo="_change_load_p_action", + runpf_fun=runpf_fun, + el_id=load.id, + expected_diff=expected_diff, + el_val=load.target_p_mw, + to_add_remove=to_add_remove, + ) + + def test_change_load_p_dc(self): + """test I have the same results if I change the load p with and + without restarting from scratch when running dc powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_change_load_p_ac(runpf_fun="_run_dc_pf") + + def _change_load_q_action(self, gridmodel, el_id, el_val): + gridmodel.change_q_load(el_id, el_val) + + def _change_gen_p_action(self, gridmodel, el_id, el_val): + gridmodel.change_p_gen(el_id, el_val) + + def test_change_load_q_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the load q with and + without restarting from scratch when running ac powerflow + + NB: this test has no sense in DC + """ + gen_bus = [el.bus_id for el in self.gridmodel.get_generators()] + for load in self.gridmodel.get_loads(): + if load.bus_id in gen_bus: + # nothing will change if there is a gen connected to the + # same bus as the load + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_load_q_action", + funname_undo="_change_load_q_action", + runpf_fun=runpf_fun, + el_id=load.id, + expected_diff=expected_diff, + el_val=load.target_q_mvar, + to_add_remove=to_add_remove, + ) + + def test_change_gen_p_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the gen p with and + without restarting from scratch when running ac powerflow""" + for gen in self.gridmodel.get_generators(): + if gen.is_slack: + # nothing to do for the slack bus... + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_gen_p_action", + funname_undo="_change_gen_p_action", + runpf_fun=runpf_fun, + el_id=gen.id, + expected_diff=expected_diff, + el_val=gen.target_p_mw, + to_add_remove=to_add_remove, + ) + + def test_change_gen_p_dc(self): + """test I have the same results if I change the gen p with and + without restarting from scratch when running dc powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_change_gen_p_ac(runpf_fun="_run_dc_pf") + + def _change_gen_v_action(self, gridmodel, el_id, el_val): + gridmodel.change_v_gen(el_id, el_val) + + def test_change_gen_v_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the gen v with and + without restarting from scratch when running ac powerflow + + NB: this test has no sense in DC + """ + gen_bus = [el.bus_id for el in self.gridmodel.get_generators()] + vn_kv = self.gridmodel.get_bus_vn_kv() + for gen in self.gridmodel.get_generators(): + if gen.bus_id in gen_bus: + # nothing will change if there is another gen connected to the + # same bus as the gen (error if everything is coded normally + # which it might not) + continue + + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 0.1 * gen.target_vm_pu * vn_kv[gen.bus_id] + self.aux_do_undo_ac(funname_do="_change_gen_v_action", + funname_undo="_change_gen_v_action", + runpf_fun=runpf_fun, + el_id=gen.id, + expected_diff=expected_diff, + el_val=gen.target_vm_pu * vn_kv[gen.bus_id], + to_add_remove=to_add_remove, + ) + + def _change_shunt_p_action(self, gridmodel, el_id, el_val): + gridmodel.change_p_shunt(el_id, el_val) + + def test_change_shunt_p_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the shunt p with and + without restarting from scratch when running ac powerflow""" + for shunt in self.gridmodel.get_shunts(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_shunt_p_action", + funname_undo="_change_shunt_p_action", + runpf_fun=runpf_fun, + el_id=shunt.id, + expected_diff=expected_diff, + el_val=shunt.target_p_mw, + to_add_remove=to_add_remove, + ) + + def test_change_shunt_p_dc(self): + """test I have the same results if I change the shunt p with and + without restarting from scratch when running dc powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_change_shunt_p_ac(runpf_fun="_run_dc_pf") + + def _change_shunt_q_action(self, gridmodel, el_id, el_val): + gridmodel.change_q_shunt(el_id, el_val) + + def test_change_shunt_q_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the shunt q with and + without restarting from scratch when running ac powerflow + + NB dc is not needed here (and does not make sense)""" + for shunt in self.gridmodel.get_shunts(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_shunt_q_action", + funname_undo="_change_shunt_q_action", + runpf_fun=runpf_fun, + el_id=shunt.id, + expected_diff=expected_diff, + el_val=shunt.target_q_mvar, + to_add_remove=to_add_remove, + ) + + def _change_storage_p_action(self, gridmodel, el_id, el_val): + gridmodel.change_p_storage(el_id, el_val) + + def test_change_storage_p_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the storage p with and + without restarting from scratch when running ac powerflow""" + for storage in self.gridmodel.get_storages(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_storage_p_action", + funname_undo="_change_storage_p_action", + runpf_fun=runpf_fun, + el_id=storage.id, + expected_diff=expected_diff, + el_val=storage.target_p_mw, + to_add_remove=to_add_remove, + ) + + def test_change_storage_p_dc(self): + """test I have the same results if I change the storage p with and + without restarting from scratch when running dc powerflow""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_change_storage_p_ac(runpf_fun="_run_dc_pf") + + def _change_storage_q_action(self, gridmodel, el_id, el_val): + gridmodel.change_q_storage(el_id, el_val) + + def test_change_storage_q_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the storage q with and + without restarting from scratch when running ac powerflow""" + gen_bus = [el.bus_id for el in self.gridmodel.get_generators()] + for storage in self.gridmodel.get_storages(): + if storage.bus_id in gen_bus: + # nothing will change if there is a gen connected to the + # same bus as the load + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-3 + to_add_remove = 5. + self.aux_do_undo_ac(funname_do="_change_storage_q_action", + funname_undo="_change_storage_q_action", + runpf_fun=runpf_fun, + el_id=storage.id, + expected_diff=expected_diff, + el_val=storage.target_q_mvar, + to_add_remove=to_add_remove, + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 453e3d3a..6545d63f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -592,7 +592,6 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_line_param", &PandaPowerConverter::get_line_param) .def("get_trafo_param", &PandaPowerConverter::get_trafo_param); - py::class_(m, "SolverControl", "TODO") .def(py::init<>()) .def("has_dimension_changed", &SolverControl::has_dimension_changed, "TODO") From 1d80160a1a9f2eb4dbe6750c0148aa6fc3c89da9 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 21 Dec 2023 12:10:26 +0100 Subject: [PATCH 41/66] improve testing for solver control --- lightsim2grid/tests/test_solver_control.py | 173 ++++++++++++++++++++- src/GridModel.h | 4 +- src/element_container/GeneratorContainer.h | 12 +- 3 files changed, 180 insertions(+), 9 deletions(-) diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index 2104eafc..eb5eca5c 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -10,10 +10,10 @@ # - change gen X # - change shunt X # - change storage X -# - change slack -# - change slack weight -# - turnoff_gen_pv -# - when it diverges (and then I can make it un converge normally) +# - change slack X +# - change slack weight X +# - turnoff_gen_pv X +# - when it diverges (and then I can make it un converge normally) X # - test change bus to -1 and deactivate element has the same impact # TODO and do that for all solver type: NR, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX @@ -422,7 +422,170 @@ def test_change_storage_q_ac(self, runpf_fun="_run_ac_pf"): to_add_remove=to_add_remove, ) + def _change_unique_slack_id(self, gridmodel, el_id, el_val): + # el_id : new slack + # el_val : old slack + gridmodel.remove_gen_slackbus(el_val) # remove old slack + gridmodel.add_gen_slackbus(el_id, 1.0) # add new slack + def _unchange_unique_slack_id(self, gridmodel, el_id, el_val): + # el_id : new slack + # el_val : old slack + gridmodel.remove_gen_slackbus(el_id) # remove new slack + gridmodel.add_gen_slackbus(el_val, 1.0) # add back old slack + + def test_change_gen_slack_unique_ac(self, runpf_fun="_run_ac_pf"): + """test I have the same results if I change the (for this test) unique slack bus + This is done in AC""" + gen_is_slack = [el.is_slack for el in self.gridmodel.get_generators()] + gen_id_slack_init = np.where(gen_is_slack)[0][0] + + for gen in self.gridmodel.get_generators(): + if gen_is_slack[gen.id]: + # generator was already slack, don't expect any change + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-5 # change slack impact is low + self.aux_do_undo_ac(funname_do="_change_unique_slack_id", + funname_undo="_unchange_unique_slack_id", + runpf_fun=runpf_fun, + el_id=gen.id, # new slack + expected_diff=expected_diff, + el_val=gen_id_slack_init, # old slack + to_add_remove=0, + ) + + def test_change_gen_slack_unique_dc(self): + """test I have the same results if I change the (for this test) unique slack bus + This is done in DC""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_change_gen_slack_unique_ac(runpf_fun="_run_dc_pf") + + def _change_dist_slack_id(self, gridmodel, el_id, el_val): + # el_id : new slack + # el_val : old slack + gridmodel.add_gen_slackbus(el_val, 0.5) # set all slack a weight of 0.5 + gridmodel.add_gen_slackbus(el_id, 0.5) # add new slack with a weight of 0.5 + + def _unchange_dist_slack_id(self, gridmodel, el_id, el_val): + # el_id : new slack + # el_val : old slack + gridmodel.remove_gen_slackbus(el_id) # remove new slack + gridmodel.add_gen_slackbus(el_val, 1.0) # add back old slack with a weight of 1. (as originally) + + def test_distslack_weight_ac(self): + """test I have the same results if the slack weights (dist mode) are changed + + NB: not done in DC because as of now DC does not support dist slack + """ + if not self.can_dist_slack : + self.skipTest("This solver does not support distributed slack") + gen_is_slack = [el.is_slack for el in self.gridmodel.get_generators()] + gen_id_slack_init = np.where(gen_is_slack)[0][0] + runpf_fun = "_run_ac_pf" + for gen in self.gridmodel.get_generators(): + if gen_is_slack[gen.id]: + # generator was already slack, don't expect any change + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-5 # change slack impact is low + self.aux_do_undo_ac(funname_do="_change_dist_slack_id", + funname_undo="_unchange_dist_slack_id", + runpf_fun=runpf_fun, + el_id=gen.id, # new added slack + expected_diff=expected_diff, + el_val=gen_id_slack_init, # old slack + to_add_remove=0, + ) + + def _change_turnedoff_pv(self, gridmodel, el_id, el_val): + gridmodel.turnedoff_no_pv() + + def _unchange_turnedoff_pv(self, gridmodel, el_id, el_val): + gridmodel.turnedoff_pv() + + def test_turnedoff_pv_ac(self): + """test the `turnedoff_pv` functionality + """ + runpf_fun = "_run_ac_pf" + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 # change slack impact is low + self.aux_do_undo_ac(funname_do="_change_turnedoff_pv", + funname_undo="_unchange_turnedoff_pv", + runpf_fun=runpf_fun, + el_id=0, + expected_diff=expected_diff, + el_val=0, + to_add_remove=0, + ) + + def _change_for_divergence_sbus(self, gridmodel, el_id, el_val): + # el_id=load_p_init, # initial loads + # el_val=load_p_div, # final loads + [gridmodel.change_p_load(l_id, val) for l_id, val in enumerate(el_val)] + + def _unchange_for_divergence_sbus(self, gridmodel, el_id, el_val): + # el_id=load_p_init, # initial loads + # el_val=load_p_div, # final loads + [gridmodel.change_p_load(l_id, val) for l_id, val in enumerate(el_id)] + + def test_divergence_sbus_ac(self): + """test I can make the grid diverge and converge again using sbus (ac mode only) + + It never diverge in DC because of Sbus""" + runpf_fun = "_run_ac_pf" + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 # change slack impact is low + load_p_init = 1.0 * np.array([el.target_p_mw for el in self.gridmodel.get_loads()]) + load_p_div = 5. * load_p_init + + # check that this load makes it diverge too + tmp_grid = self.gridmodel.copy() + [tmp_grid.change_p_load(l_id, val) for l_id, val in enumerate(load_p_div)] + V_tmp = tmp_grid.ac_pf(self.v_init, self.iter, self.tol_solver) + assert len(V_tmp) == 0, "should have diverged !" + + self.aux_do_undo_ac(funname_do="_change_for_divergence_sbus", + funname_undo="_unchange_for_divergence_sbus", + runpf_fun=runpf_fun, + el_id=load_p_init, # initial loads + expected_diff=expected_diff, + el_val=load_p_div, # final loads + to_add_remove=0., + ) + + def _change_for_divergence_ybus(self, gridmodel, el_id, el_val): + [gridmodel.deactivate_trafo(tr_id) for tr_id in el_id] + + def _unchange_for_divergence_ybus(self, gridmodel, el_id, el_val): + [gridmodel.reactivate_trafo(tr_id) for tr_id in el_id] + + def test_divergence_ybus_ac(self, runpf_fun="_run_ac_pf"): + """test I can make the grid diverge and converge again using ybus (islanding) in AC""" + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 # change slack impact is low + trafo_to_disc = [0, 1, 2] + + # check that this load makes it diverge too + tmp_grid = self.gridmodel.copy() + [tmp_grid.deactivate_trafo(tr_id) for tr_id in trafo_to_disc] + V_tmp = tmp_grid.ac_pf(self.v_init, self.iter, self.tol_solver) + assert len(V_tmp) == 0, "should have diverged !" + + self.aux_do_undo_ac(funname_do="_change_for_divergence_ybus", + funname_undo="_unchange_for_divergence_ybus", + runpf_fun=runpf_fun, + el_id=trafo_to_disc, # trafo to disco + expected_diff=expected_diff, + el_val=0., + to_add_remove=0., + ) + + def test_divergence_ybus_dc(self): + """test I can make the grid diverge and converge again using ybus (islanding) in DC""" + self.test_divergence_ybus_ac("_run_dc_pf") + + if __name__ == "__main__": unittest.main() - \ No newline at end of file diff --git a/src/GridModel.h b/src/GridModel.h index eee6cc7c..b972d73e 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -102,8 +102,8 @@ class GridModel : public GenericContainer // retrieve the underlying data (raw class) const GeneratorContainer & get_generators_as_data() const {return generators_;} - void turnedoff_no_pv(){generators_.turnedoff_no_pv();} // turned off generators are not pv - void turnedoff_pv(){generators_.turnedoff_pv();} // turned off generators are pv + void turnedoff_no_pv(){generators_.turnedoff_no_pv(solver_control_);} // turned off generators are not pv + void turnedoff_pv(){generators_.turnedoff_pv(solver_control_);} // turned off generators are pv bool get_turnedoff_gen_pv() {return generators_.get_turnedoff_gen_pv();} void update_slack_weights(Eigen::Ref > could_be_slack){ generators_.update_slack_weights(could_be_slack, solver_control_); diff --git a/src/element_container/GeneratorContainer.h b/src/element_container/GeneratorContainer.h index d9b854d1..1c463271 100644 --- a/src/element_container/GeneratorContainer.h +++ b/src/element_container/GeneratorContainer.h @@ -224,8 +224,16 @@ class GeneratorContainer: public GenericContainer void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver); // modification - void turnedoff_no_pv(){turnedoff_gen_pv_=false;} // turned off generators are not pv - void turnedoff_pv(){turnedoff_gen_pv_=true;} // turned off generators are pv + void turnedoff_no_pv(SolverControl & solver_control){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + turnedoff_gen_pv_=false; // turned off generators are not pv. This is NOT the default. + } + void turnedoff_pv(SolverControl & solver_control){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + turnedoff_gen_pv_=true; // turned off generators are pv. This is the default. + } bool get_turnedoff_gen_pv() const {return turnedoff_gen_pv_;} void update_slack_weights(Eigen::Ref > could_be_slack, SolverControl & solver_control); From 4d13a263d8569ec93361d91d84a64fe03856620c Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 21 Dec 2023 15:17:39 +0100 Subject: [PATCH 42/66] completing the test suite --- lightsim2grid/tests/test_solver_control.py | 169 ++++++++++++++++++++- src/GridModel.h | 4 - src/element_container/ShuntContainer.h | 8 +- src/powerflow_algorithm/BaseFDPFAlgo.tpp | 16 +- 4 files changed, 183 insertions(+), 14 deletions(-) diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index eb5eca5c..aa1a441a 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -14,9 +14,10 @@ # - change slack weight X # - turnoff_gen_pv X # - when it diverges (and then I can make it un converge normally) X -# - test change bus to -1 and deactivate element has the same impact +# - test change bus to -1 and deactivate element has the same impact (irrelevant !) +# - test when set_bus to 2 -# TODO and do that for all solver type: NR, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX +# TODO and do that for all solver type: NR X, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX # and for all solver check everything that can be check: final tolerance, number of iteration, etc. etc. import unittest @@ -43,9 +44,9 @@ def setUp(self) -> None: action_class=CompleteAction, backend=LightSimBackend()) self.gridmodel = self.env.backend._grid - self._aux_setup_grid() - self.v_init = 1.0 * self.env.backend.V self.iter = 10 + self._aux_setup_grid() + self.v_init = 0.0 * self.env.backend.V + 1.04 # just to have a vector with the right dimension self.tol_solver = 1e-8 # solver self.tol_equal = 1e-10 # for comparing with and without the "smarter solver" things, and make sure everything is really equal! @@ -586,6 +587,166 @@ def test_divergence_ybus_dc(self): """test I can make the grid diverge and converge again using ybus (islanding) in DC""" self.test_divergence_ybus_ac("_run_dc_pf") + def _aux_disco_load(self, gridmodel, el_id, el_val): + gridmodel.deactivate_load(el_id) + + def _aux_reco_load(self, gridmodel, el_id, el_val): + gridmodel.reactivate_load(el_id) + + def test_disco_reco_load_ac(self, runpf_fun="_run_ac_pf"): + """test I can disconnect a load (AC)""" + for load in self.gridmodel.get_loads(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 + if load.id == 3: + expected_diff = 3e-3 + self.aux_do_undo_ac(funname_do="_aux_disco_load", + funname_undo="_aux_reco_load", + runpf_fun=runpf_fun, + el_id=load.id, + expected_diff=expected_diff, + el_val=0, + to_add_remove=0, + ) + + def test_disco_reco_load_dc(self): + """test I can disconnect a load (DC)""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_disco_reco_load_ac(runpf_fun="_run_dc_pf") + + def _aux_disco_gen(self, gridmodel, el_id, el_val): + gridmodel.deactivate_gen(el_id) + + def _aux_reco_gen(self, gridmodel, el_id, el_val): + gridmodel.reactivate_gen(el_id) + + def test_disco_reco_gen_ac(self, runpf_fun="_run_ac_pf"): + """test I can disconnect a gen (AC)""" + for gen in self.gridmodel.get_generators(): + if gen.is_slack: + # by default single slack, so I don't disconnect it + continue + if gen.target_p_mw == 0.: + # will have not impact as by default gen with p==0. are still pv + continue + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 + if gen.id == 3: + expected_diff = 3e-3 + self.aux_do_undo_ac(funname_do="_aux_disco_gen", + funname_undo="_aux_reco_gen", + runpf_fun=runpf_fun, + el_id=gen.id, + expected_diff=expected_diff, + el_val=0, + to_add_remove=0, + ) + + def test_disco_reco_gen_dc(self): + """test I can disconnect a shunt (DC)""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_disco_reco_gen_ac(runpf_fun="_run_dc_pf") + + def _aux_disco_shunt(self, gridmodel, el_id, el_val): + gridmodel.deactivate_shunt(el_id) + + def _aux_reco_shunt(self, gridmodel, el_id, el_val): + gridmodel.reactivate_shunt(el_id) + + def test_disco_reco_shunt_ac(self, runpf_fun="_run_ac_pf"): + """test I can disconnect a shunt (AC)""" + for shunt in self.gridmodel.get_shunts(): + self.gridmodel.tell_solver_need_reset() + expected_diff = 1e-2 + if runpf_fun == "_run_dc_pf": + # in dc q is not used, so i skipped if no target_p_mw + if shunt.target_p_mw == 0: + continue + self.aux_do_undo_ac(funname_do="_aux_disco_shunt", + funname_undo="_aux_reco_shunt", + runpf_fun=runpf_fun, + el_id=shunt.id, + expected_diff=expected_diff, + el_val=0, + to_add_remove=0, + ) + + def test_disco_reco_shunt_dc(self): + """test I can disconnect a shunt (DC)""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_disco_reco_shunt_ac(runpf_fun="_run_dc_pf") + + def _aux_disco_shunt(self, gridmodel, el_id, el_val): + gridmodel.deactivate_bus(0) + gridmodel.reactivate_bus(15) + gridmodel.change_bus_powerline_or(0, 15) + gridmodel.change_bus_powerline_or(1, 15) + gridmodel.change_bus_gen(5, 15) + + def _aux_reco_shunt(self, gridmodel, el_id, el_val): + gridmodel.reactivate_bus(0) + gridmodel.deactivate_bus(15) + gridmodel.change_bus_powerline_or(0, 0) + gridmodel.change_bus_powerline_or(1, 0) + gridmodel.change_bus_gen(5, 0) + def test_change_bus2_ac(self, runpf_fun="_run_ac_pf"): + """test for bus 2, basic test I don't do it for all kind of objects (AC pf)""" + expected_diff = 1e-2 + self.aux_do_undo_ac(funname_do="_aux_disco_shunt", + funname_undo="_aux_reco_shunt", + runpf_fun=runpf_fun, + el_id=0, + expected_diff=expected_diff, + el_val=0, + to_add_remove=0, + ) + + +class TestSolverControlNRSing(TestSolverControl): + def _aux_setup_grid(self): + self.need_dc = False # is it worth it to run DC powerflow ? + self.can_dist_slack = False + self.gridmodel.change_solver(SolverType.SparseLUSingleSlack) + self.gridmodel.change_solver(SolverType.DC) + + +class TestSolverControlFDPF_XB(TestSolverControl): + def _aux_setup_grid(self): + self.need_dc = False # is it worth it to run DC powerflow ? + self.can_dist_slack = False + self.gridmodel.change_solver(SolverType.FDPF_XB_SparseLU) + self.gridmodel.change_solver(SolverType.DC) + self.iter = 30 + + +class TestSolverControlFDPF_BX(TestSolverControl): + def _aux_setup_grid(self): + self.need_dc = False # is it worth it to run DC powerflow ? + self.can_dist_slack = False + self.gridmodel.change_solver(SolverType.FDPF_BX_SparseLU) + self.gridmodel.change_solver(SolverType.DC) + self.iter = 30 + + +class TestSolverControlGaussSeidel(TestSolverControl): + def _aux_setup_grid(self): + self.need_dc = False # is it worth it to run DC powerflow ? + self.can_dist_slack = False + self.gridmodel.change_solver(SolverType.GaussSeidel) + self.iter = 350 + + +class TestSolverControlGaussSeidelSynch(TestSolverControl): + def _aux_setup_grid(self): + self.need_dc = False # is it worth it to run DC powerflow ? + self.can_dist_slack = False + self.gridmodel.change_solver(SolverType.GaussSeidelSynch) + self.iter = 1000 + + if __name__ == "__main__": unittest.main() diff --git a/src/GridModel.h b/src/GridModel.h index b972d73e..123a711d 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -717,8 +717,6 @@ class GridModel : public GenericContainer { (this->*fun_react)(el_id); // eg reactivate_load(load_id); (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); - // topo_changed_ = true; - solver_control_.tell_dimension_changed(); } } else{ if(has_changed(el_pos)) @@ -728,8 +726,6 @@ class GridModel : public GenericContainer // bus_status_ is set to "false" in GridModel.update_topo // and a bus is activated if (and only if) one element is connected to it. // I must not set `bus_status_[new_bus_backend] = false;` in this case ! - // topo_changed_ = true; - solver_control_.tell_dimension_changed(); } } } diff --git a/src/element_container/ShuntContainer.h b/src/element_container/ShuntContainer.h index 2cbed982..4fe808c4 100644 --- a/src/element_container/ShuntContainer.h +++ b/src/element_container/ShuntContainer.h @@ -131,13 +131,15 @@ class ShuntContainer : public GenericContainer void deactivate(int shunt_id, SolverControl & solver_control) { if(status_[shunt_id]){ - solver_control.tell_recompute_sbus(); + solver_control.tell_recompute_sbus(); // DC + solver_control.tell_recompute_ybus(); // AC } _deactivate(shunt_id, status_); } void reactivate(int shunt_id, SolverControl & solver_control) { - if(status_[shunt_id]){ - solver_control.tell_recompute_sbus(); + if(!status_[shunt_id]){ + solver_control.tell_recompute_sbus(); // DC + solver_control.tell_recompute_ybus(); // AC } _reactivate(shunt_id, status_); } diff --git a/src/powerflow_algorithm/BaseFDPFAlgo.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp index fe13caf0..28a7c1b5 100644 --- a/src/powerflow_algorithm/BaseFDPFAlgo.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -33,20 +33,30 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix Date: Fri, 22 Dec 2023 11:57:34 +0100 Subject: [PATCH 43/66] fixing broken tests --- CHANGELOG.rst | 4 + lightsim2grid/tests/test_solver_control.py | 39 +++++++- src/GridModel.cpp | 98 +++++++++++++++++--- src/GridModel.h | 93 ++++++++++++------- src/element_container/DCLineContainer.h | 6 +- src/element_container/GeneratorContainer.cpp | 2 +- src/element_container/GeneratorContainer.h | 18 +++- src/element_container/GenericContainer.h | 3 +- src/element_container/LineContainer.h | 11 +++ src/element_container/LoadContainer.h | 8 ++ src/element_container/SGenContainer.h | 8 ++ src/element_container/ShuntContainer.h | 8 ++ src/element_container/TrafoContainer.h | 13 ++- src/main.cpp | 1 + src/powerflow_algorithm/BaseDCAlgo.tpp | 91 +++++++++--------- 15 files changed, 308 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 025c310d..4a4113e9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -32,11 +32,15 @@ Change Log these cases. - [FIXED] a bug leading to not propagate correctly the "compute_results" flag when the environment was copied (for example) +- [FIXED] a bug where copying a lightsim2grid `GridModel` did not fully copy it +- [FIXED] a bug in the "topo_vect" comprehension cpp side (sometimes some buses + might not be activated / deactivated correctly) - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. - [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side) - [ADDED] computation of PTPF (Power Transfer Distribution Factor) is now possible +- [ADDED] (not tested) support for more than 2 busbars per substation - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` - [IMPROVED] clean ce cpp side by refactoring: making clearer the difference (linear) solver diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index aa1a441a..4a1c4133 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -25,6 +25,8 @@ import numpy as np import grid2op from grid2op.Action import CompleteAction +from grid2op.Parameters import Parameters + from lightsim2grid import LightSimBackend from lightsim2grid.solver import SolverType @@ -50,6 +52,41 @@ def setUp(self) -> None: self.tol_solver = 1e-8 # solver self.tol_equal = 1e-10 # for comparing with and without the "smarter solver" things, and make sure everything is really equal! + def test_update_topo_ac(self, runpf_fun="_run_ac_pf"): + """test when I disconnect a line alone at one end: it changes the size of the ybus / sbus vector AC""" + LINE_ID = 2 + dim_topo = type(self.env).dim_topo + mask_changed = np.zeros(dim_topo, dtype=bool) + mask_val = np.zeros(dim_topo, dtype=np.int32) + mask_changed[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = True + mask_val[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = 2 + self.gridmodel.update_topo(mask_changed, mask_val) + V = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V), "it should not have diverged here" + self.gridmodel.unset_changes() + + mask_changed = np.zeros(dim_topo, dtype=bool) + mask_val = np.zeros(dim_topo, dtype=np.int32) + mask_changed[type(self.env).line_or_pos_topo_vect[LINE_ID]] = True + mask_val[type(self.env).line_or_pos_topo_vect[LINE_ID]] = -1 + # mask_changed[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = True + # mask_val[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = -1 + self.gridmodel.update_topo(mask_changed, mask_val) + solver = self.gridmodel.get_dc_solver() if runpf_fun == "_run_dc_pf" else self.gridmodel.get_solver() + V1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V1), f"it should not have diverged here. Error : {solver.get_error()}" + + self.gridmodel.tell_solver_need_reset() + V2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) + assert len(V2), f"it should not have diverged here. Error : {solver.get_error()}" + assert np.allclose(V1, V2, rtol=self.tol_equal, atol=self.tol_equal) + + def test_update_topo_dc(self): + """test when I disconnect a line alone at one end: it changes the size of the ybus / sbus vector AC""" + if not self.need_dc: + self.skipTest("Useless to run DC") + self.test_update_topo_ac("_run_dc_pf") + def test_pf_run_dc(self): """test I have the same results if nothing is done with and without restarting from scratch when running dc powerflow""" if not self.need_dc: @@ -138,7 +175,7 @@ def aux_do_undo_ac(self, getattr(self, funname_undo)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val) V_reco = getattr(self, runpf_fun)(gridmodel=self.gridmodel) assert len(V_reco), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" - assert np.allclose(V_reco, V_init, rtol=self.tol_equal, atol=self.tol_equal), f"error for el_id={el_id}: do an action and then undo it should not have any impact in {pf_mode}" + assert np.allclose(V_reco, V_init, rtol=self.tol_equal, atol=self.tol_equal), f"error for el_id={el_id}: do an action and then undo it should not have any impact in {pf_mode}: max {np.abs(V_init - V_reco).max():.2e}" self.gridmodel.unset_changes() V_reco1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel) assert len(V_reco1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}" diff --git a/src/GridModel.cpp b/src/GridModel.cpp index 5f4579a1..ba833363 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -14,6 +14,7 @@ GridModel::GridModel(const GridModel & other) { reset(true, true, true); + max_nb_bus_per_sub_ = other.max_nb_bus_per_sub_; init_vm_pu_ = other.init_vm_pu_; sn_mva_ = other.sn_mva_; @@ -97,6 +98,22 @@ GridModel::StateRes GridModel::get_state() const auto res_storage = storages_.get_state(); auto res_dc_line = dc_lines_.get_state(); + std::vector load_pos_topo_vect(load_pos_topo_vect_.begin(), load_pos_topo_vect_.end()); + std::vector gen_pos_topo_vect(gen_pos_topo_vect_.begin(), gen_pos_topo_vect_.end()); + std::vector line_or_pos_topo_vect(line_or_pos_topo_vect_.begin(), line_or_pos_topo_vect_.end()); + std::vector line_ex_pos_topo_vect(line_ex_pos_topo_vect_.begin(), line_ex_pos_topo_vect_.end()); + std::vector trafo_hv_pos_topo_vect(trafo_hv_pos_topo_vect_.begin(), trafo_hv_pos_topo_vect_.end()); + std::vector trafo_lv_pos_topo_vect(trafo_lv_pos_topo_vect_.begin(), trafo_lv_pos_topo_vect_.end()); + std::vector storage_pos_topo_vect(storage_pos_topo_vect_.begin(), storage_pos_topo_vect_.end()); + + std::vector load_to_subid(load_to_subid_.begin(), load_to_subid_.end()); + std::vector gen_to_subid(gen_to_subid_.begin(), gen_to_subid_.end()); + std::vector line_or_to_subid(line_or_to_subid_.begin(), line_or_to_subid_.end()); + std::vector line_ex_to_subid(line_ex_to_subid_.begin(), line_ex_to_subid_.end()); + std::vector trafo_hv_to_subid(trafo_hv_to_subid_.begin(), trafo_hv_to_subid_.end()); + std::vector trafo_lv_to_subid(trafo_lv_to_subid_.begin(), trafo_lv_to_subid_.end()); + std::vector storage_to_subid(storage_to_subid_.begin(), storage_to_subid_.end()); + GridModel::StateRes res(version_major, version_medium, version_minor, @@ -112,7 +129,23 @@ GridModel::StateRes GridModel::get_state() const res_load, res_sgen, res_storage, - res_dc_line + res_dc_line, + n_sub_, + max_nb_bus_per_sub_, + load_pos_topo_vect, + gen_pos_topo_vect, + line_or_pos_topo_vect, + line_ex_pos_topo_vect, + trafo_hv_pos_topo_vect, + trafo_lv_pos_topo_vect, + storage_pos_topo_vect, + load_to_subid, + gen_to_subid, + line_or_to_subid, + line_ex_to_subid, + trafo_hv_to_subid, + trafo_lv_to_subid, + storage_to_subid ); return res; }; @@ -139,11 +172,11 @@ void GridModel::set_state(GridModel::StateRes & my_state) exc_ << "It is not possible. Please reinstall it."; throw std::runtime_error(exc_.str()); } - std::vector ls_to_pp_ = std::get<3>(my_state); + const std::vector & ls_to_pp = std::get<3>(my_state); init_vm_pu_ = std::get<4>(my_state); sn_mva_ = std::get<5>(my_state); - std::vector & bus_vn_kv = std::get<6>(my_state); - std::vector & bus_status = std::get<7>(my_state); + const std::vector & bus_vn_kv = std::get<6>(my_state); + const std::vector & bus_status = std::get<7>(my_state); // powerlines LineContainer::StateRes & state_lines = std::get<8>(my_state); @@ -162,13 +195,45 @@ void GridModel::set_state(GridModel::StateRes & my_state) // dc lines DCLineContainer::StateRes & state_dc_lines = std::get<15>(my_state); - // assign it to this instance + // grid2op specific + n_sub_ = std::get<16>(my_state); + max_nb_bus_per_sub_ = std::get<17>(my_state); + const std::vector & load_pos_topo_vect = std::get<18>(my_state); + const std::vector & gen_pos_topo_vect = std::get<19>(my_state); + const std::vector & line_or_pos_topo_vect = std::get<20>(my_state); + const std::vector & line_ex_pos_topo_vect = std::get<21>(my_state); + const std::vector & trafo_hv_pos_topo_vect = std::get<22>(my_state); + const std::vector & trafo_lv_pos_topo_vect = std::get<23>(my_state); + const std::vector & storage_pos_topo_vect = std::get<24>(my_state); + const std::vector & load_to_subid = std::get<25>(my_state); + const std::vector & gen_to_subid = std::get<26>(my_state); + const std::vector & line_or_to_subid = std::get<27>(my_state); + const std::vector & line_ex_to_subid = std::get<28>(my_state); + const std::vector & trafo_hv_to_subid = std::get<29>(my_state); + const std::vector & trafo_lv_to_subid = std::get<30>(my_state); + const std::vector & storage_to_subid = std::get<31>(my_state); + + load_pos_topo_vect_ = IntVectRowMaj::Map(load_pos_topo_vect.data(), load_pos_topo_vect.size()); + gen_pos_topo_vect_ = IntVectRowMaj::Map(gen_pos_topo_vect.data(), gen_pos_topo_vect.size()); + line_or_pos_topo_vect_ = IntVectRowMaj::Map(line_or_pos_topo_vect.data(), line_or_pos_topo_vect.size()); + line_ex_pos_topo_vect_ = IntVectRowMaj::Map(line_ex_pos_topo_vect.data(), line_ex_pos_topo_vect.size()); + trafo_hv_pos_topo_vect_ = IntVectRowMaj::Map(trafo_hv_pos_topo_vect.data(), trafo_hv_pos_topo_vect.size()); + trafo_lv_pos_topo_vect_ = IntVectRowMaj::Map(trafo_lv_pos_topo_vect.data(), trafo_lv_pos_topo_vect.size()); + storage_pos_topo_vect_ = IntVectRowMaj::Map(storage_pos_topo_vect.data(), storage_pos_topo_vect.size()); + load_to_subid_ = IntVectRowMaj::Map(load_to_subid.data(), load_to_subid.size()); + gen_to_subid_ = IntVectRowMaj::Map(gen_to_subid.data(), gen_to_subid.size()); + line_or_to_subid_ = IntVectRowMaj::Map(line_or_to_subid.data(), line_or_to_subid.size()); + line_ex_to_subid_ = IntVectRowMaj::Map(line_ex_to_subid.data(), line_ex_to_subid.size()); + trafo_hv_to_subid_ = IntVectRowMaj::Map(trafo_hv_to_subid.data(), trafo_hv_to_subid.size()); + trafo_lv_to_subid_ = IntVectRowMaj::Map(trafo_lv_to_subid.data(), trafo_lv_to_subid.size()); + storage_to_subid_ = IntVectRowMaj::Map(storage_to_subid.data(), storage_to_subid.size()); - set_ls_to_orig(IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size())); // set also _orig_to_ls + // assign it to this instance + set_ls_to_orig(IntVect::Map(ls_to_pp.data(), ls_to_pp.size())); // set also _orig_to_ls // buses // 1. bus_vn_kv_ - bus_vn_kv_ = RealVect::Map(&bus_vn_kv[0], bus_vn_kv.size()); + bus_vn_kv_ = RealVect::Map(bus_vn_kv.data(), bus_vn_kv.size()); // 2. bus status bus_status_ = bus_status; @@ -240,7 +305,8 @@ void GridModel::init_bus(const RealVect & bus_vn_kv, int nb_line, int nb_trafo){ and initialize the Ybus_ matrix at the proper shape **/ - const int nb_bus = static_cast(bus_vn_kv.size()); + const int nb_bus = static_cast(bus_vn_kv.size()); // size of buses are checked in set_max_nb_bus_per_sub + bus_vn_kv_ = bus_vn_kv; // base_kv bus_status_ = std::vector(nb_bus, true); // by default everything is connected @@ -997,9 +1063,6 @@ void GridModel::update_storages_p(Eigen::Ref > has_changed, Eigen::Ref > new_values) { - const int nb_bus = static_cast(bus_status_.size()); - for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false; - update_topo_generic(has_changed, new_values, load_pos_topo_vect_, load_to_subid_, &GridModel::reactivate_load, @@ -1045,6 +1108,19 @@ void GridModel::update_topo(Eigen::Ref(bus_status_.size()); + for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false; + + powerlines_.update_bus_status(bus_status_); // TODO have a function to dispatch that to all type of elements + shunts_.update_bus_status(bus_status_); + trafos_.update_bus_status(bus_status_); + loads_.update_bus_status(bus_status_); + sgens_.update_bus_status(bus_status_); + storages_.update_bus_status(bus_status_); + generators_.update_bus_status(bus_status_); + dc_lines_.update_bus_status(bus_status_); } // for FDPF (implementation of the alg 2 method FDBX (FDXB will follow) // TODO FDPF diff --git a/src/GridModel.h b/src/GridModel.h index 123a711d..7a0a104e 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -45,6 +45,8 @@ class GridModel : public GenericContainer { public: + typedef Eigen::Array IntVectRowMaj; + typedef std::tuple< int, // version major int, // version medium @@ -69,14 +71,32 @@ class GridModel : public GenericContainer // storage units LoadContainer::StateRes, //dc lines - DCLineContainer::StateRes + DCLineContainer::StateRes, + // grid2op specific + int, // n_sub + int, // max_nb_bus_per_sub + std::vector, // load_pos_topo_vect_ + std::vector, + std::vector, + std::vector, + std::vector, + std::vector, + std::vector, + std::vector, // load_to_subid_ + std::vector, + std::vector, + std::vector, + std::vector, + std::vector, + std::vector > StateRes; GridModel(): solver_control_(), compute_results_(true), init_vm_pu_(1.04), - sn_mva_(1.0){ + sn_mva_(1.0), + max_nb_bus_per_sub_(2){ _solver.change_solver(SolverType::SparseLU); _dc_solver.change_solver(SolverType::DC); _solver.set_gridmodel(this); @@ -591,6 +611,20 @@ class GridModel : public GenericContainer { n_sub_ = n_sub; } + void set_max_nb_bus_per_sub(int max_nb_bus_per_sub) + { + max_nb_bus_per_sub_ = max_nb_bus_per_sub; + if(bus_vn_kv_.size() != n_sub_ * max_nb_bus_per_sub_){ + std::ostringstream exc_; + exc_ << "GridModel::set_max_nb_bus_per_sub: "; + exc_ << "your model counts "; + exc_ << bus_vn_kv_.size() << " buses according to `bus_vn_kv_` but "; + exc_ << n_sub_ * max_nb_bus_per_sub_ << " according to n_sub_ * max_nb_bus_per_sub_."; + exc_ << "Both should match: either reinit it with another call to `init_bus` or set properly the number of "; + exc_ << "substations / buses per substations with `set_n_sub` / `set_max_nb_bus_per_sub`"; + throw std::runtime_error(exc_.str()); + } + } void fillSbus_other(CplxVect & res, bool ac, const std::vector& id_me_to_solver){ fillSbus_me(res, ac, id_me_to_solver); @@ -706,27 +740,23 @@ class GridModel : public GenericContainer { for(int el_id = 0; el_id < vect_pos.rows(); ++el_id) { + int el_pos = vect_pos(el_id); + if(! has_changed(el_pos)) continue; int new_bus = new_values(el_pos); if(new_bus > 0){ // new bus is a real bus, so i need to make sure to have it turned on, and then change the bus int init_bus_me = vect_subid(el_id); - int new_bus_backend = new_bus == 1 ? init_bus_me : init_bus_me + n_sub_ ; + int new_bus_backend = new_bus == 1 ? init_bus_me : init_bus_me + n_sub_ * (max_nb_bus_per_sub_ - 1); bus_status_[new_bus_backend] = true; - if(has_changed(el_pos)) - { - (this->*fun_react)(el_id); // eg reactivate_load(load_id); - (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); - } + (this->*fun_react)(el_id); // eg reactivate_load(load_id); + (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); } else{ - if(has_changed(el_pos)) - { - // new bus is negative, we deactivate it - (this->*fun_deact)(el_id);// eg deactivate_load(load_id); - // bus_status_ is set to "false" in GridModel.update_topo - // and a bus is activated if (and only if) one element is connected to it. - // I must not set `bus_status_[new_bus_backend] = false;` in this case ! - } + // new bus is negative, we deactivate it + (this->*fun_deact)(el_id);// eg deactivate_load(load_id); + // bus_status_ is set to "false" in GridModel.update_topo + // and a bus is activated if (and only if) one element is connected to it. + // I must not set `bus_status_[new_bus_backend] = false;` in this case ! } } } @@ -824,21 +854,22 @@ class GridModel : public GenericContainer // specific grid2op int n_sub_; - Eigen::Array load_pos_topo_vect_; - Eigen::Array gen_pos_topo_vect_; - Eigen::Array line_or_pos_topo_vect_; - Eigen::Array line_ex_pos_topo_vect_; - Eigen::Array trafo_hv_pos_topo_vect_; - Eigen::Array trafo_lv_pos_topo_vect_; - Eigen::Array storage_pos_topo_vect_; - - Eigen::Array load_to_subid_; - Eigen::Array gen_to_subid_; - Eigen::Array line_or_to_subid_; - Eigen::Array line_ex_to_subid_; - Eigen::Array trafo_hv_to_subid_; - Eigen::Array trafo_lv_to_subid_; - Eigen::Array storage_to_subid_; + int max_nb_bus_per_sub_; + IntVectRowMaj load_pos_topo_vect_; + IntVectRowMaj gen_pos_topo_vect_; + IntVectRowMaj line_or_pos_topo_vect_; + IntVectRowMaj line_ex_pos_topo_vect_; + IntVectRowMaj trafo_hv_pos_topo_vect_; + IntVectRowMaj trafo_lv_pos_topo_vect_; + IntVectRowMaj storage_pos_topo_vect_; + + IntVectRowMaj load_to_subid_; + IntVectRowMaj gen_to_subid_; + IntVectRowMaj line_or_to_subid_; + IntVectRowMaj line_ex_to_subid_; + IntVectRowMaj trafo_hv_to_subid_; + IntVectRowMaj trafo_lv_to_subid_; + IntVectRowMaj storage_to_subid_; }; diff --git a/src/element_container/DCLineContainer.h b/src/element_container/DCLineContainer.h index 14abf214..5ee14dc9 100644 --- a/src/element_container/DCLineContainer.h +++ b/src/element_container/DCLineContainer.h @@ -189,7 +189,11 @@ class DCLineContainer : public GenericContainer virtual void get_graph(std::vector > & res) const {}; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void nb_line_end(std::vector & res) const; - + virtual void update_bus_status(std::vector & bus_status) const { + from_gen_.update_bus_status(bus_status); + to_gen_.update_bus_status(bus_status); + } + real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);} real_type get_qmax_or(int dcline_id) {return from_gen_.get_qmax(dcline_id);} real_type get_qmin_ex(int dcline_id) {return to_gen_.get_qmin(dcline_id);} diff --git a/src/element_container/GeneratorContainer.cpp b/src/element_container/GeneratorContainer.cpp index 254813f9..e45767b9 100644 --- a/src/element_container/GeneratorContainer.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -432,7 +432,7 @@ void GeneratorContainer::set_q(const RealVect & reactive_mismatch, void GeneratorContainer::update_slack_weights(Eigen::Ref > could_be_slack, - SolverControl & solver_control) + SolverControl & solver_control) { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) diff --git a/src/element_container/GeneratorContainer.h b/src/element_container/GeneratorContainer.h index 1c463271..2dbdb4c9 100644 --- a/src/element_container/GeneratorContainer.h +++ b/src/element_container/GeneratorContainer.h @@ -175,14 +175,15 @@ class GeneratorContainer: public GenericContainer **/ void add_slackbus(int gen_id, real_type weight, SolverControl & solver_control){ // TODO DEBUG MODE - if(weight <= 0.) throw std::runtime_error("GeneratorContainer::add_slackbus Cannot assign a negative weight to the slack bus."); + if(weight <= 0.) throw std::runtime_error("GeneratorContainer::add_slackbus Cannot assign a negative (<=0) weight to the slack bus."); if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); gen_slackbus_[gen_id] = true; if(gen_slack_weight_[gen_id] != weight) solver_control.tell_slack_weight_changed(); gen_slack_weight_[gen_id] = weight; } void remove_slackbus(int gen_id, SolverControl & solver_control){ - if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); + if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); + if(gen_slack_weight_[gen_id] != 0.) solver_control.tell_slack_weight_changed(); gen_slackbus_[gen_id] = false; gen_slack_weight_[gen_id] = 0.; } @@ -243,7 +244,7 @@ class GeneratorContainer: public GenericContainer solver_control.tell_recompute_sbus(); solver_control.tell_pq_changed(); // bus might now be pq if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); - if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); + solver_control.tell_pv_changed(); if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ solver_control.tell_slack_participate_changed(); solver_control.tell_slack_weight_changed(); @@ -256,7 +257,7 @@ class GeneratorContainer: public GenericContainer solver_control.tell_recompute_sbus(); solver_control.tell_pq_changed(); // bus might now be pv if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); - if(!turnedoff_gen_pv_) solver_control.tell_pv_changed(); + solver_control.tell_pv_changed(); if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ solver_control.tell_slack_participate_changed(); solver_control.tell_slack_weight_changed(); @@ -272,7 +273,14 @@ class GeneratorContainer: public GenericContainer int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); - + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + bus_status[bus_id_[gen_id]] = true; + } + } real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} void change_p(int gen_id, real_type new_p, SolverControl & solver_control); diff --git a/src/element_container/GenericContainer.h b/src/element_container/GenericContainer.h index c0e6da36..d48ef009 100644 --- a/src/element_container/GenericContainer.h +++ b/src/element_container/GenericContainer.h @@ -91,7 +91,8 @@ class GenericContainer : public BaseConstants const std::vector & id_grid_to_solver) const {}; virtual void get_q(std::vector& q_by_bus) {}; - + virtual void update_bus_status(std::vector & bus_status) const {}; + void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver) {}; static const int _deactivated_bus_id; diff --git a/src/element_container/LineContainer.h b/src/element_container/LineContainer.h index fb788177..7e6acf44 100644 --- a/src/element_container/LineContainer.h +++ b/src/element_container/LineContainer.h @@ -180,6 +180,15 @@ class LineContainer : public GenericContainer virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void nb_line_end(std::vector & res) const; virtual void get_graph(std::vector > & res) const; + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_or_id_[el_id]] = true; + bus_status[bus_ex_id_[el_id]] = true; + } + } void deactivate(int powerline_id, SolverControl & solver_control) { // std::cout << "line: deactivate called\n"; @@ -187,6 +196,7 @@ class LineContainer : public GenericContainer solver_control.tell_recompute_ybus(); // but sparsity pattern do not change here (possibly one more coeff at 0.) solver_control.tell_ybus_some_coeffs_zero(); + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... } _deactivate(powerline_id, status_); } @@ -194,6 +204,7 @@ class LineContainer : public GenericContainer if(!status_[powerline_id]){ solver_control.tell_recompute_ybus(); solver_control.tell_ybus_change_sparsity_pattern(); // sparsity pattern might change: a non zero coeff can pop up + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... } _reactivate(powerline_id, status_); } diff --git a/src/element_container/LoadContainer.h b/src/element_container/LoadContainer.h index 3786d522..0483f3f2 100644 --- a/src/element_container/LoadContainer.h +++ b/src/element_container/LoadContainer.h @@ -158,6 +158,14 @@ class LoadContainer : public GenericContainer virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, diff --git a/src/element_container/SGenContainer.h b/src/element_container/SGenContainer.h index ae05cdb4..b50a269b 100644 --- a/src/element_container/SGenContainer.h +++ b/src/element_container/SGenContainer.h @@ -179,6 +179,14 @@ class SGenContainer: public GenericContainer virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const ; virtual void gen_p_per_bus(std::vector & res) const; + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, diff --git a/src/element_container/ShuntContainer.h b/src/element_container/ShuntContainer.h index 4fe808c4..8458acf6 100644 --- a/src/element_container/ShuntContainer.h +++ b/src/element_container/ShuntContainer.h @@ -161,6 +161,14 @@ class ShuntContainer : public GenericContainer FDPFMethod xb_or_bx) const; virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; // in DC i need that + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, diff --git a/src/element_container/TrafoContainer.h b/src/element_container/TrafoContainer.h index 6e150255..fbb07f8a 100644 --- a/src/element_container/TrafoContainer.h +++ b/src/element_container/TrafoContainer.h @@ -167,7 +167,7 @@ class TrafoContainer : public GenericContainer } if(id >= nb()) { - throw std::range_error("Generator out of bound. Not enough transformers on the grid."); + throw std::range_error("Trafo out of bound. Not enough transformers on the grid."); } return TrafoInfo(*this, id); } @@ -178,6 +178,7 @@ class TrafoContainer : public GenericContainer solver_control.tell_recompute_ybus(); // but sparsity pattern do not change here (possibly one more coeff at 0.) solver_control.tell_ybus_some_coeffs_zero(); + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... } _deactivate(trafo_id, status_); } @@ -185,6 +186,7 @@ class TrafoContainer : public GenericContainer if(!status_[trafo_id]){ solver_control.tell_recompute_ybus(); solver_control.tell_ybus_change_sparsity_pattern(); // this might change + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... } _reactivate(trafo_id, status_); } @@ -214,6 +216,15 @@ class TrafoContainer : public GenericContainer int nb_powerline, bool transpose) const; virtual void hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver); // needed for dc mode + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_hv_id_[el_id]] = true; + bus_status[bus_lv_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, diff --git a/src/main.cpp b/src/main.cpp index 6545d63f..b7986131 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -837,6 +837,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) // auxiliary functions .def("set_n_sub", &GridModel::set_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()) .def("set_line_or_pos_topo_vect", &GridModel::set_line_or_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str()) diff --git a/src/powerflow_algorithm/BaseDCAlgo.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp index 0f249cb3..f56c9e7b 100644 --- a/src/powerflow_algorithm/BaseDCAlgo.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -11,15 +11,15 @@ // TODO SLACK !!! template bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { // max_iter is ignored // tol is ignored @@ -27,7 +27,6 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & // and for the slack bus both the magnitude and the angle are used. if(!is_linear_solver_valid()) { - // err_ = ErrorType::NotInitError; return false; } BaseAlgo::reset_timer(); @@ -35,11 +34,12 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & auto timer = CustTimer(); if(_solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || - _solver_control.has_ybus_some_coeffs_zero()){ + _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() + ){ reset(); } - - if (_solver_control.ybus_change_sparsity_pattern()) need_factorize_ = true; sizeYbus_with_slack_ = static_cast(Ybus.rows()); @@ -47,34 +47,32 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & auto timer_preproc = CustTimer(); #endif // __COUT_TIMES - const CplxVect & Sbus_tmp = Sbus; - // TODO SLACK (for now i put all slacks as PV, except the first one) // this should be handled in Sbus, because we know the amount of power absorbed by the slack // so we can compute it correctly ! - // std::cout << "\t\t\tretrieve_pv_with_slack \n"; - // std::cout << "slack_ids: "; - // for(auto el: slack_ids) std::cout << el << ", "; - // std::cout << std::endl; - my_pv_ = retrieve_pv_with_slack(slack_ids, pv); - // std::cout << "my_pv_: "; - // for(auto el: my_pv_) std::cout << el << ", "; - // std::cout << std::endl; - // const Eigen::VectorXi & my_pv = pv; - - // find the slack buses - // std::cout << "\t\t\textract_slack_bus_id \n"; - slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); - sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); - - // corresp bus -> solverbus - // std::cout << "\t\t\tfill_mat_bus_id \n"; - fill_mat_bus_id(sizeYbus_with_slack_); + if(need_factorize_ || + _solver_control.has_pv_changed()) { + my_pv_ = retrieve_pv_with_slack(slack_ids, pv); + } + + if(need_factorize_ || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed()) { + // find the slack buses + slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); + sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); + + // corresp bus -> solverbus + fill_mat_bus_id(sizeYbus_with_slack_); + } // remove the slack bus from Ybus - // and extract only real part - // std::cout << "\t\t\tfill_dcYbus_noslack \n"; - fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); + if(need_factorize_ || + _solver_control.need_recompute_ybus() || + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero()) { + fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); + } #ifdef __COUT_TIMES std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl; @@ -84,7 +82,14 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & #ifdef __COUT_TIMES auto timer_solve = CustTimer(); #endif // __COUT_TIMES + bool just_factorize = false; + if(_solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero()){ + need_factorize_ = true; + } + + // initialize the solver if needed if(need_factorize_){ ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ @@ -96,16 +101,16 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & } // remove the slack bus from Sbus - // std::cout << "\t\t\t dcSbus_noslack_ \n"; - dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); - for (int k=0; k < sizeYbus_with_slack_; ++k){ - if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus - const int col_res = mat_bus_id_(k); - dcSbus_noslack_(col_res) = std::real(Sbus_tmp(k)); + if(need_factorize_ || _solver_control.need_recompute_sbus()){ + dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); + for (int k=0; k < sizeYbus_with_slack_; ++k){ + if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus + const int col_res = mat_bus_id_(k); + dcSbus_noslack_(col_res) = std::real(Sbus(k)); + } } // solve for theta: Sbus = dcY . theta (make a copy to keep dcSbus_noslack_) - // std::cout << "\t\t\t Va_dc_without_slack \n"; RealVect Va_dc_without_slack = dcSbus_noslack_; ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, just_factorize); if(error != ErrorType::NoError){ From 202a589ac68c99cac88b12265c4ff1c838d27cc7 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 22 Dec 2023 15:52:07 +0100 Subject: [PATCH 44/66] fixing some bugs for the batch powerflows --- lightsim2grid/securityAnalysis.py | 2 + lightsim2grid/tests/test_issue_56.py | 11 ++--- src/BaseMultiplePowerflow.cpp | 1 + src/BaseMultiplePowerflow.h | 6 +++ src/Computers.cpp | 2 + src/SecurityAnalysis.cpp | 4 ++ src/linear_solvers/CKTSOSolver.cpp | 4 +- src/linear_solvers/CKTSOSolver.h | 2 +- src/linear_solvers/KLUSolver.cpp | 4 +- src/linear_solvers/KLUSolver.h | 2 +- src/linear_solvers/NICSLUSolver.cpp | 4 +- src/linear_solvers/NICSLUSolver.h | 2 +- src/linear_solvers/SparseLUSolver.cpp | 4 +- src/linear_solvers/SparseLUSolver.h | 2 +- src/powerflow_algorithm/BaseDCAlgo.tpp | 56 +++++++++++++------------- 15 files changed, 61 insertions(+), 45 deletions(-) diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py index a0cc7242..f95d45fd 100644 --- a/lightsim2grid/securityAnalysis.py +++ b/lightsim2grid/securityAnalysis.py @@ -300,9 +300,11 @@ def compute_V(self): """ v_init = self.grid2op_env.backend.V + print("self.computer.compute") self.computer.compute(v_init, self.grid2op_env.backend.max_it, self.grid2op_env.backend.tol) + print("self.computer.get_voltages()") self._vs = self.computer.get_voltages() self.__computed = True return self._vs diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py index 9aed2a08..7bddb14f 100644 --- a/lightsim2grid/tests/test_issue_56.py +++ b/lightsim2grid/tests/test_issue_56.py @@ -35,15 +35,16 @@ def test_dc(self): self.sa.add_all_n1_contingencies() res_p_dc, res_a_dc, res_v_dc = self.sa.get_flows() - assert np.any(res_p != res_p_dc) - assert np.any(res_a != res_a_dc) - assert np.any(res_v != res_v_dc) + assert np.any(res_p != res_p_dc), "DC and AC solver leads to same results" + assert np.any(res_a != res_a_dc), "DC and AC solver leads to same results" + assert np.any(res_v != res_v_dc), "DC and AC solver leads to same results" assert self.sa.computer.get_solver_type() == SolverType.DC + return nb_bus = self.env.n_sub nb_powerline = len(self.env.backend._grid.get_lines()) # now check with the DC computation - for l_id in range(self.env.n_line): + for l_id in range(type(self.env).n_line): grid_model = self.env.backend._grid.copy() if l_id < nb_powerline: grid_model.deactivate_powerline(l_id) @@ -55,7 +56,7 @@ def test_dc(self): if len(res): # model has converged, I check the results are the same # check voltages - assert np.allclose(res_v_dc[l_id, :nb_bus], res[:nb_bus]), f"error for contingency {l_id}" + assert np.allclose(res_v_dc[l_id, :nb_bus], res[:nb_bus]), f"error for contingency {l_id}: {np.abs(res_v_dc[l_id, :nb_bus]-res[:nb_bus]).max():.2e}" # now check the flows pl_dc, ql_dc, vl_dc, al_dc = grid_model.get_lineor_res() pt_dc, qt_dc, vt_dc, at_dc = grid_model.get_trafohv_res() diff --git a/src/BaseMultiplePowerflow.cpp b/src/BaseMultiplePowerflow.cpp index 48aac800..3c90661c 100644 --- a/src/BaseMultiplePowerflow.cpp +++ b/src/BaseMultiplePowerflow.cpp @@ -22,6 +22,7 @@ bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix gen_p, const Eigen::VectorXi & slack_ids = ac_solver_used ? _grid_model.get_slack_ids(): _grid_model.get_slack_ids_dc(); const RealVect & slack_weights = _grid_model.get_slack_weights(); _solver.reset(); + _solver_control.tell_none_changed(); + _solver_control.tell_recompute_sbus(); // now build the Sbus _Sbuses = CplxMat::Zero(nb_steps, nb_buses_solver); diff --git a/src/SecurityAnalysis.cpp b/src/SecurityAnalysis.cpp index ccab4068..17f9c3cd 100644 --- a/src/SecurityAnalysis.cpp +++ b/src/SecurityAnalysis.cpp @@ -165,6 +165,10 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t // reset the solver _solver.reset(); + _solver_control.tell_none_changed(); + _solver_control.tell_recompute_ybus(); + // _solver_control.tell_ybus_some_coeffs_zero(); + // ybus does not change sparsity pattern here // compute the right Vinit to send to the solver CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_solver); diff --git a/src/linear_solvers/CKTSOSolver.cpp b/src/linear_solvers/CKTSOSolver.cpp index 8d5b7349..4c5a01b6 100644 --- a/src/linear_solvers/CKTSOSolver.cpp +++ b/src/linear_solvers/CKTSOSolver.cpp @@ -87,7 +87,7 @@ ErrorType CKTSOLinearSolver::initialize(Eigen::SparseMatrix & J){ return err; } -ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // with standard use of lightsim2grid, the solver should have already been initialized // J is const even if it does not compile if said const @@ -95,7 +95,7 @@ ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect bool stop = false; RealVect x; ErrorType err = ErrorType::NoError; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ ret = solver_->Refactorize(J.valuePtr()); if (ret < 0) { // std::cout << "CKTSOLinearSolver::solve solver_->Refactorize error: " << ret << std::endl; diff --git a/src/linear_solvers/CKTSOSolver.h b/src/linear_solvers/CKTSOSolver.h index 2ed7e34a..af98c931 100644 --- a/src/linear_solvers/CKTSOSolver.h +++ b/src/linear_solvers/CKTSOSolver.h @@ -62,7 +62,7 @@ class CKTSOLinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix & J); - ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/linear_solvers/KLUSolver.cpp b/src/linear_solvers/KLUSolver.cpp index fe8efc4f..4e05361a 100644 --- a/src/linear_solvers/KLUSolver.cpp +++ b/src/linear_solvers/KLUSolver.cpp @@ -37,14 +37,14 @@ ErrorType KLULinearSolver::initialize(Eigen::SparseMatrix& J){ return res; } -ErrorType KLULinearSolver::solve(Eigen::SparseMatrix& J, RealVect & b, bool has_just_been_initialized){ +ErrorType KLULinearSolver::solve(Eigen::SparseMatrix& J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call klu_solver.analyze() before calling that) // J is const even if it does not compile if said const int ok; ErrorType err = ErrorType::NoError; bool stop = false; - if(!has_just_been_initialized){ + if(!doesnt_need_refactor){ // if the call to "klu_factor" has been made this iteration, there is no need // to re factor again the matrix // i'm in the case where it has not diff --git a/src/linear_solvers/KLUSolver.h b/src/linear_solvers/KLUSolver.h index 2eefe06e..f75b5540 100644 --- a/src/linear_solvers/KLUSolver.h +++ b/src/linear_solvers/KLUSolver.h @@ -46,7 +46,7 @@ class KLULinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix& J); - ErrorType solve(Eigen::SparseMatrix& J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix& J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/linear_solvers/NICSLUSolver.cpp b/src/linear_solvers/NICSLUSolver.cpp index 357a6726..a6f39a16 100644 --- a/src/linear_solvers/NICSLUSolver.cpp +++ b/src/linear_solvers/NICSLUSolver.cpp @@ -75,7 +75,7 @@ ErrorType NICSLULinearSolver::initialize(Eigen::SparseMatrix & J){ return err; } -ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call klu_solver.analyze() before calling that) // J is const even if it does not compile if said const @@ -83,7 +83,7 @@ ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect bool stop = false; RealVect x; ErrorType err = ErrorType::NoError; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ ret = solver_.FactorizeMatrix(J.valuePtr(), nb_thread_); // TODO maybe 0 instead of nb_thread_ here, see https://github.com/chenxm1986/nicslu/blob/master/nicslu202110/demo/demo2.cpp if (ret < 0) { // std::cout << "NICSLULinearSolver::solve solver_.FactorizeMatrix error: " << ret << std::endl; diff --git a/src/linear_solvers/NICSLUSolver.h b/src/linear_solvers/NICSLUSolver.h index 481b4a09..bb6412c7 100644 --- a/src/linear_solvers/NICSLUSolver.h +++ b/src/linear_solvers/NICSLUSolver.h @@ -54,7 +54,7 @@ class NICSLULinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix & J); - ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/linear_solvers/SparseLUSolver.cpp b/src/linear_solvers/SparseLUSolver.cpp index 98c31491..5a1b6ab0 100644 --- a/src/linear_solvers/SparseLUSolver.cpp +++ b/src/linear_solvers/SparseLUSolver.cpp @@ -23,13 +23,13 @@ ErrorType SparseLULinearSolver::initialize(const Eigen::SparseMatrix return res; } -ErrorType SparseLULinearSolver::solve(const Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType SparseLULinearSolver::solve(const Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call sparselu_solver.analyze() before calling that) // J is const even if it does not compile if said const ErrorType err = ErrorType::NoError; bool stop = false; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ // if the call to "klu_factor" has been made this iteration, there is no need // to re factor again the matrix // i'm in the case where it has not diff --git a/src/linear_solvers/SparseLUSolver.h b/src/linear_solvers/SparseLUSolver.h index 768fd67f..710abcc3 100644 --- a/src/linear_solvers/SparseLUSolver.h +++ b/src/linear_solvers/SparseLUSolver.h @@ -34,7 +34,7 @@ class SparseLULinearSolver // public api ErrorType initialize(const Eigen::SparseMatrix & J); - ErrorType solve(const Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(const Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); ErrorType reset(){ return ErrorType::NoError; } // can this linear solver solve problem where RHS is a matrix diff --git a/src/powerflow_algorithm/BaseDCAlgo.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp index f56c9e7b..bb594667 100644 --- a/src/powerflow_algorithm/BaseDCAlgo.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -30,9 +30,11 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & return false; } BaseAlgo::reset_timer(); - + bool doesnt_need_refactor = true; + auto timer = CustTimer(); - if(_solver_control.need_reset_solver() || + if(need_factorize_ || + _solver_control.need_reset_solver() || _solver_control.has_dimension_changed() || _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() _solver_control.ybus_change_sparsity_pattern() || @@ -47,17 +49,16 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & auto timer_preproc = CustTimer(); #endif // __COUT_TIMES - // TODO SLACK (for now i put all slacks as PV, except the first one) - // this should be handled in Sbus, because we know the amount of power absorbed by the slack - // so we can compute it correctly ! if(need_factorize_ || - _solver_control.has_pv_changed()) { + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed()) { + + // TODO SLACK (for now i put all slacks as PV, except the first one) + // this should be handled in Sbus, because we know the amount of power absorbed by the slack + // so we can compute it correctly ! + // std::cout << "\tneed to retrieve slack\n"; my_pv_ = retrieve_pv_with_slack(slack_ids, pv); - } - if(need_factorize_ || - _solver_control.has_pv_changed() || - _solver_control.has_pq_changed()) { // find the slack buses slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); @@ -71,48 +72,47 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & _solver_control.need_recompute_ybus() || _solver_control.ybus_change_sparsity_pattern() || _solver_control.has_ybus_some_coeffs_zero()) { + // std::cout << "\tneed to change Ybus\n"; fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); + doesnt_need_refactor = false; // force a call to "factor" the linear solver as the lhs (ybus) changed + // no need to refactor if ybus did not change } #ifdef __COUT_TIMES std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl; #endif // __COUT_TIMES - + // initialize the solver (only if needed) #ifdef __COUT_TIMES auto timer_solve = CustTimer(); #endif // __COUT_TIMES - bool just_factorize = false; - if(_solver_control.ybus_change_sparsity_pattern() || - _solver_control.has_ybus_some_coeffs_zero()){ - need_factorize_ = true; + // remove the slack bus from Sbus + if(need_factorize_ || _solver_control.need_recompute_sbus()){ + // std::cout << "\tneed to compute Sbus\n"; + dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); + for (int k=0; k < sizeYbus_with_slack_; ++k){ + if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus + const int col_res = mat_bus_id_(k); + dcSbus_noslack_(col_res) = std::real(Sbus(k)); + } } // initialize the solver if needed if(need_factorize_){ + // std::cout << "\tneed to factorize\n"; ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; return false; } need_factorize_ = false; - just_factorize = true; - } - - // remove the slack bus from Sbus - if(need_factorize_ || _solver_control.need_recompute_sbus()){ - dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); - for (int k=0; k < sizeYbus_with_slack_; ++k){ - if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus - const int col_res = mat_bus_id_(k); - dcSbus_noslack_(col_res) = std::real(Sbus(k)); - } + doesnt_need_refactor = true; } // solve for theta: Sbus = dcY . theta (make a copy to keep dcSbus_noslack_) RealVect Va_dc_without_slack = dcSbus_noslack_; - ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, just_factorize); + ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, doesnt_need_refactor); if(error != ErrorType::NoError){ err_ = error; timer_total_nr_ += timer.duration(); @@ -270,7 +270,7 @@ RealMat BaseDCAlgo::get_ptdf(const Eigen::SparseMatrix rhs.array() = 0.; // rhs = RealVect::Zero(sizeYbus_without_slack_); } - // TODO PTDF: if the solver can solve the directly, do that instead + // TODO PTDF: if the solver can solve the MAT directly, do that instead return PTDF; } From cbe0b1bd553221d44e5e0b3aa504fb764d405602 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 22 Dec 2023 16:56:34 +0100 Subject: [PATCH 45/66] refactor the MultiplePower to BatchPowerflow, ContingencyAnalysis and TimeSeries --- CHANGELOG.rst | 4 + examples/computers_with_grid2op.py | 6 +- .../computers_with_grid2op_multithreading.py | 6 +- examples/security_analysis.py | 4 +- lightsim2grid/__init__.py | 8 +- lightsim2grid/contingencyAnalysis.py | 347 ++++++++++++++++++ lightsim2grid/securityAnalysis.py | 343 +---------------- lightsim2grid/tests/test_Computers.py | 12 +- lightsim2grid/tests/test_issue_56.py | 9 +- lightsim2grid/tests/test_solver_control.py | 3 + lightsim2grid/timeSerie.py | 11 +- setup.py | 6 +- .../BaseBatchSolverSynch.cpp} | 6 +- .../BaseBatchSolverSynch.h} | 13 +- .../ContingencyAnalysis.cpp} | 16 +- .../ContingencyAnalysis.h} | 26 +- .../TimeSeries.cpp} | 20 +- .../TimeSeries.h} | 12 +- src/main.cpp | 97 ++--- 19 files changed, 489 insertions(+), 460 deletions(-) create mode 100644 lightsim2grid/contingencyAnalysis.py rename src/{BaseMultiplePowerflow.cpp => batch_algorithm/BaseBatchSolverSynch.cpp} (93%) rename src/{BaseMultiplePowerflow.h => batch_algorithm/BaseBatchSolverSynch.h} (96%) rename src/{SecurityAnalysis.cpp => batch_algorithm/ContingencyAnalysis.cpp} (94%) rename src/{SecurityAnalysis.h => batch_algorithm/ContingencyAnalysis.h} (89%) rename src/{Computers.cpp => batch_algorithm/TimeSeries.cpp} (87%) rename src/{Computers.h => batch_algorithm/TimeSeries.h} (94%) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a4113e9..d5c04d2b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,10 @@ Change Log - [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`). If you previously used `gridmodel.get_Subus()` to retrieve the Sbus used for DC powerflow, please use `gridmodel.get_dcSbus()` instead. +- [DEPRECATED] in the cpp class: the old `SecurityAnalysisCPP` has been renamed `ContingencyAnalysisCPP` + (you should not import it, but it you do you can `from lightsim2grid.securityAnalysis import ContingencyAnalysisCPP` now) +- [DEPRECATED] in the cpp class: the old `Computers` has been renamed `TimeSerieCPP` + (you should not import it, but it you do you can `from lightsim2grid.time_serie import TimeSerieCPP` now) - [FIXED] now voltage is properly set to 0. when shunts are disconnected - [FIXED] now voltage is properly set to 0. when storage units are disconnected - [FIXED] a bug where non connected grid were not spotted in DC diff --git a/examples/computers_with_grid2op.py b/examples/computers_with_grid2op.py index fbb930a3..ceff6cf7 100644 --- a/examples/computers_with_grid2op.py +++ b/examples/computers_with_grid2op.py @@ -7,7 +7,7 @@ # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. # ADVANCED USAGE -# This files explains how to use the Computers cpp class, for easier use +# This files explains how to use the TimeSeriesCPP cpp class, for easier use # please consult the documentation of TimeSeries or the # time_serie.py file ! @@ -16,7 +16,7 @@ import warnings import numpy as np from lightsim2grid import LightSimBackend -from lightsim2grid.timeSerie import Computers +from lightsim2grid.timeSerie import TimeSeriesCPP env_name = "l2rpn_neurips_2020_track2" test = True @@ -36,7 +36,7 @@ nb_sim = prod_p.shape[0] # now perform the computation -computer = Computers(grid) +computer = TimeSeriesCPP(grid) # print("start the computation") status = computer.compute_Vs(prod_p, np.zeros((nb_sim, 0)), # no static generators for now ! diff --git a/examples/computers_with_grid2op_multithreading.py b/examples/computers_with_grid2op_multithreading.py index ef9d80b8..8307056b 100644 --- a/examples/computers_with_grid2op_multithreading.py +++ b/examples/computers_with_grid2op_multithreading.py @@ -7,7 +7,7 @@ # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. # ADVANCED USAGE -# This files explains how to use the Computers cpp class, for easier use +# This files explains how to use the TimeSeriesCPP cpp class, for easier use # please consult the documentation of TimeSeries or the # time_serie.py file ! @@ -22,7 +22,7 @@ import grid2op from grid2op.Parameters import Parameters from lightsim2grid import LightSimBackend -from lightsim2grid.timeSerie import Computers +from lightsim2grid.timeSerie import TimeSeriesCPP NB_THREAD = 4 ENV_NAME = "l2rpn_neurips_2020_track2_small" @@ -51,7 +51,7 @@ def get_injs(env): def get_flows(grid, Vinit, prod_p, load_p, load_q, max_it=10, tol=1e-8): # now perform the computation - computer = Computers(grid) + computer = TimeSeriesCPP(grid) # print("start the computation") status = computer.compute_Vs(prod_p, np.zeros((prod_p.shape[0], 0)), # no static generators for now ! diff --git a/examples/security_analysis.py b/examples/security_analysis.py index dbdd5e67..ff137fa7 100644 --- a/examples/security_analysis.py +++ b/examples/security_analysis.py @@ -14,7 +14,7 @@ from grid2op.Action import BaseAction from grid2op.Chronics import ChangeNothing import warnings -from lightsim2grid import LightSimBackend, SecurityAnalysis +from lightsim2grid import LightSimBackend, ContingencyAnalysis env_name = "l2rpn_neurips_2020_track2_small" test = False @@ -43,7 +43,7 @@ env_pp = multi_mix_env_pp[key_env] # Run the environment on a scenario using the TimeSerie module -security_analysis = SecurityAnalysis(env) +security_analysis = ContingencyAnalysis(env) security_analysis.add_all_n1_contingencies() p_or, a_or, voltages = security_analysis.get_flows() # the 3 lines above are the only lines you need to do to perform a security analysis ! diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py index 929780f7..057b4d1b 100644 --- a/lightsim2grid/__init__.py +++ b/lightsim2grid/__init__.py @@ -39,10 +39,10 @@ print(f"TimeSerie import error: {exc_}") try: - from lightsim2grid.securityAnalysis import SecurityAnalysis - __all__.append("SecurityAnalysis") - __all__.append("securityAnalysis") + from lightsim2grid.contingencyAnalysis import ContingencyAnalysis + __all__.append("contingencyAnalysis") + __all__.append("ContingencyAnalysis") except ImportError as exc_: # grid2op is not installed, the SecurtiyAnalysis module will not be available pass - print(f"SecurityAnalysis import error: {exc_}") + print(f"ContingencyAnalysis import error: {exc_}") diff --git a/lightsim2grid/contingencyAnalysis.py b/lightsim2grid/contingencyAnalysis.py new file mode 100644 index 00000000..f16a504c --- /dev/null +++ b/lightsim2grid/contingencyAnalysis.py @@ -0,0 +1,347 @@ +# Copyright (c) 2020, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. + +__all__ = ["ContingencyAnalysisCPP", "ContingencyAnalysis", + # deprecated + "SecurityAnalysisCPP", "SecurityAnalysis", + ] + +import copy +import numpy as np +from collections.abc import Iterable + +from lightsim2grid.lightSimBackend import LightSimBackend +from lightsim2grid.solver import SolverType +from lightsim2grid_cpp import ContingencyAnalysisCPP + + +class ContingencyAnalysis(object): + """ + This class allows to perform a "security analysis" from a given grid state. + + For now, you cannot change the grid state, and it only computes the security analysis with + current flows at origin of powerlines. + + Feel free to post a feature request if you want to extend it. + + This class is used in 4 phases: + + 0) you create it from a grid2op environment (the grid topology will not be modified from this environment) + 1) you add some contingencies to simulate + 2) you start the simulation + 3) you read back the results + + + Examples + -------- + An example is given here + + .. code-block:: python + + import grid2op + from lightsim2grid import SecurityAnalysis + from lightsim2grid import LightSimBackend + env_name = ... + env = grid2op.make(env_name, backend=LightSimBackend()) + + 0) you create + security_analysis = SecurityAnalysis(env) + + 1) you add some contingencies to simulate + security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...) + + 2) you start the simulation (done automatically) + 3) you read back the results + res_p, res_a, res_v = security_analysis.get_flows() + + # in this results, then + # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency. + # you can retrieve it with `security_analysis.contingency_order[row_id]` + + Notes + ------ + + Sometimes, the behaviour might differ from grid2op. For example, if simulating a contingency + leads to a non connected grid, then this function will return "Nan" for the flows and 0. for + the voltages. + + In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages. + + """ + STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway + + def __init__(self, grid2op_env): + if not isinstance(grid2op_env.backend, LightSimBackend): + raise RuntimeError("This class only works with LightSimBackend") + self.grid2op_env = grid2op_env.copy() + self.computer = ContingencyAnalysisCPP(self.grid2op_env.backend._grid) + self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered + self._all_contingencies = [] + self.__computed = False + self._vs = None + self._ampss = None + + self.available_solvers = self.computer.available_solvers() + if SolverType.KLU in self.available_solvers: + # use the faster KLU if available + self.computer.change_solver(SolverType.KLU) + + @property + def all_contingencies(self): + return copy.deepcopy(self._all_contingencies) + + @all_contingencies.setter + def all_contingencies(self, val): + raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` " + "or `add_multiple_contingencies`.") + + def clear(self): + """ + Clear the list of contingencies to simulate + """ + self.computer.clear() + self._contingency_order = {} + self.__computed = False + self._vs = None + self._ampss = None + self._all_contingencies = [] + + def _single_cont_to_li_int(self, single_cont): + li_disc = [] + if isinstance(single_cont, int): + single_cont = [single_cont] + + for stuff in single_cont: + if isinstance(stuff, type(self).STR_TYPES): + stuff = np.where(self.grid2op_env.name_line == stuff) + stuff = stuff[0] + if stuff.size == 0: + # name is not found + raise RuntimeError(f"Impossible to find a powerline named \"{stuff}\" in the environment") + stuff = int(stuff[0]) + else: + stuff = int(stuff) + li_disc.append(stuff) + return li_disc + + def add_single_contingency(self, *args): + """ + This function allows to add a single contingency specified by either the powerlines names + (which should match env.name_line) or by their ID. + + The contingency added can be a "n-1" which will simulate a single powerline disconnection + or a "n-k" which will simulate the disconnection of multiple powerlines. + + It does not accept any keword arguments. + + Examples + -------- + + .. code-block:: python + + import grid2op + from lightsim2grid import SecurityAnalysis + from lightsim2grid import LightSimBackend + env_name = ... + env = grid2op.make(env_name, backend=LightSimBackend()) + + security_anlysis = SecurityAnalysis(env) + # the single (n-1) contingency "disconnect powerline 0" is added + security_anlysis.add_single_contingency(0) + + # add the single (n-1) contingency "disconnect line 1 + security_anlysis.add_single_contingency(env.name_line[1]) + + # add a single contingency that disconnect powerline 2 and 3 at the same time + security_anlysis.add_single_contingency(env.name_line[2], 3) + + Notes + ----- + If it raises an error for a given contingency, the object might be not properly initialized. + In this case, we recommend you to clear it (using the `clear()` method and to attempt to + add contingencies again.) + + """ + li_disc = self._single_cont_to_li_int(args) + li_disc_tup = tuple(li_disc) + if li_disc_tup not in self._contingency_order: + # this is really the first time this contingency is seen + try: + self.computer.add_nk(li_disc) + my_id = len(self._contingency_order) + self._contingency_order[li_disc_tup] = my_id + self._all_contingencies.append(li_disc_tup) + except Exception as exc_: + raise RuntimeError(f"Impossible to add the contingency {args}. The most likely cause " + f"is that you try to disconnect a powerline that is not present " + f"on the grid") from exc_ + + def add_multiple_contingencies(self, *args): + """ + This function will add multiple contingencies at the same time. + + This code is equivalent to: + + .. code-block:: python + + for single_cont in args: + self.add_single_contingency(single_cont) + + It does not accept any keword arguments. + + Examples + -------- + + .. code-block:: python + + import grid2op + from lightsim2grid import SecurityAnalysis + from lightsim2grid import LightSimBackend + env_name = ... + env = grid2op.make(env_name, backend=LightSimBackend()) + + security_anlysis = SecurityAnalysis(env) + + # add a single contingency that disconnect powerline 2 and 3 at the same time + security_anlysis.add_single_contingency(env.name_line[2], 3) + + # add a multiple contingencies the first one disconnect powerline 2 and + # and the second one disconnect powerline 3 + security_anlysis.add_multiple_contingencies(env.name_line[2], 3) + """ + for single_cont in args: + if isinstance(single_cont, Iterable) and not isinstance(single_cont, type(self).STR_TYPES): + # this is a contingency consisting in cutting multiple powerlines + self.add_single_contingency(*single_cont) + else: + # this is likely an int or a string representing a contingency + self.add_single_contingency(single_cont) + + def add_all_n1_contingencies(self): + """ + This method registers as the contingencies that will be computed all the contingencies that disconnects 1 powerline + + This is equivalent to: + + .. code-block:: python + + for single_cont_id in range(env.n_line): + self.add_single_contingency(single_cont_id) + """ + for single_cont_id in range(self.grid2op_env.n_line): + self.add_single_contingency(single_cont_id) + + def get_flows(self, *args): + """ + Retrieve the flows after each contingencies has been simulated. + + Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments. + + You can require only the result on some contingencies with the `args` argument, but in each case, all the results will + be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do) + + Examples + -------- + + .. code-block:: python + + import grid2op + from lightsim2grid import SecurityAnalysis + from lightsim2grid import LightSimBackend + env_name = ... + env = grid2op.make(env_name, backend=LightSimBackend()) + + security_analysis = SecurityAnalysis(env) + security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...) + res_p, res_a, res_v = security_analysis.get_flows() + + # in this results, then + # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency. + # you can retrieve it with `security_analysis.contingency_order[row_id]` + """ + + all_defaults = self.computer.my_defaults() + if len(args) == 0: + # default: i consider all contingencies + orders_ = np.zeros(len(all_defaults), dtype=int) + for id_cpp, cont_ in enumerate(all_defaults): + tup_ = tuple(cont_) + orders_[self._contingency_order[tup_]] = id_cpp + else: + # a list of interesting contingencies has been provided + orders_ = np.zeros(len(args), dtype=int) + all_defaults = [tuple(cont) for cont in all_defaults] + for id_me, cont_ in enumerate(args): + cont_li = self._single_cont_to_li_int(cont_) + tup_ = tuple(cont_li) + if tup_ not in self._contingency_order: + raise RuntimeError(f"Contingency {cont_} is not simulated by this class. Have you called " + f"`add_single_contingency` or `add_multiple_contingencies` ?") + id_cpp = all_defaults.index(tup_) + orders_[id_me] = id_cpp + + if not self.__computed: + self.compute_V() + self.compute_A() + self.compute_P() + + return self._mws[orders_], self._ampss[orders_], self._vs[orders_] + + def compute_V(self): + """ + This function allows to retrieve the complex voltage at each bus of the grid for each contingency. + + .. warning:: Order of the results + + The order in which the results are returned is NOT necessarily the order in which the contingencies have + been entered. Please use `get_flows()` method for easier reading back of the results + + """ + v_init = self.grid2op_env.backend.V + self.computer.compute(v_init, + self.grid2op_env.backend.max_it, + self.grid2op_env.backend.tol) + self._vs = self.computer.get_voltages() + self.__computed = True + return self._vs + + def compute_A(self): + """ + This function returns the current flows (in Amps, A) at the origin / high voltage side + + .. warning:: Order of the results + + The order in which the results are returned is NOT necessarily the order in which the contingencies have + been entered. Please use `get_flows()` method for easier reading back of the results ! + + """ + if not self.__computed: + raise RuntimeError("This function can only be used if compute_V has been sucessfully called") + self._ampss = 1e3 * self.computer.compute_flows() + return self._ampss + + def compute_P(self): + """ + This function returns the active power flows (in MW) at the origin / high voltage side + + .. warning:: Order of the results + + The order in which the results are returned is NOT necessarily the order in which the contingencies have + been entered. Please use `get_flows()` method for easier reading back of the results ! + + """ + if not self.__computed: + raise RuntimeError("This function can only be used if compute_V has been sucessfully called") + self._mws = 1.0 * self.computer.compute_power_flows() + return self._mws + + def close(self): + """permanently close the object""" + self.grid2op_env.close() + self.clear() + self.computer.close() diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py index f95d45fd..f9db2b1a 100644 --- a/lightsim2grid/securityAnalysis.py +++ b/lightsim2grid/securityAnalysis.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, RTE (https://www.rte-france.com) +# Copyright (c) 2023, RTE (https://www.rte-france.com) # See AUTHORS.txt # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,341 +6,8 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -__all__ = ["SecurityAnalysisCPP", "SecurityAnalysis"] +from lightsim2grid.contingencyAnalysis import ContingencyAnalysisCPP, ContingencyAnalysis -import copy -import numpy as np -from collections.abc import Iterable - -from lightsim2grid.lightSimBackend import LightSimBackend -from lightsim2grid.solver import SolverType -from lightsim2grid_cpp import SecurityAnalysisCPP - - -class SecurityAnalysis(object): - """ - This class allows to perform a "security analysis" from a given grid state. - - For now, you cannot change the grid state, and it only computes the security analysis with - current flows at origin of powerlines. - - Feel free to post a feature request if you want to extend it. - - This class is used in 4 phases: - - 0) you create it from a grid2op environment (the grid topology will not be modified from this environment) - 1) you add some contingencies to simulate - 2) you start the simulation - 3) you read back the results - - - Examples - -------- - An example is given here - - .. code-block:: python - - import grid2op - from lightsim2grid import SecurityAnalysis - from lightsim2grid import LightSimBackend - env_name = ... - env = grid2op.make(env_name, backend=LightSimBackend()) - - 0) you create - security_analysis = SecurityAnalysis(env) - - 1) you add some contingencies to simulate - security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...) - - 2) you start the simulation (done automatically) - 3) you read back the results - res_p, res_a, res_v = security_analysis.get_flows() - - # in this results, then - # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency. - # you can retrieve it with `security_analysis.contingency_order[row_id]` - - Notes - ------ - - Sometimes, the behaviour might differ from grid2op. For example, if simulating a contingency - leads to a non connected grid, then this function will return "Nan" for the flows and 0. for - the voltages. - - In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages. - - """ - STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway - - def __init__(self, grid2op_env): - if not isinstance(grid2op_env.backend, LightSimBackend): - raise RuntimeError("This class only works with LightSimBackend") - self.grid2op_env = grid2op_env.copy() - self.computer = SecurityAnalysisCPP(self.grid2op_env.backend._grid) - self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered - self._all_contingencies = [] - self.__computed = False - self._vs = None - self._ampss = None - - self.available_solvers = self.computer.available_solvers() - if SolverType.KLU in self.available_solvers: - # use the faster KLU if available - self.computer.change_solver(SolverType.KLU) - - @property - def all_contingencies(self): - return copy.deepcopy(self._all_contingencies) - - @all_contingencies.setter - def all_contingencies(self, val): - raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` " - "or `add_multiple_contingencies`.") - - def clear(self): - """ - Clear the list of contingencies to simulate - """ - self.computer.clear() - self._contingency_order = {} - self.__computed = False - self._vs = None - self._ampss = None - self._all_contingencies = [] - - def _single_cont_to_li_int(self, single_cont): - li_disc = [] - if isinstance(single_cont, int): - single_cont = [single_cont] - - for stuff in single_cont: - if isinstance(stuff, type(self).STR_TYPES): - stuff = np.where(self.grid2op_env.name_line == stuff) - stuff = stuff[0] - if stuff.size == 0: - # name is not found - raise RuntimeError(f"Impossible to find a powerline named \"{stuff}\" in the environment") - stuff = int(stuff[0]) - else: - stuff = int(stuff) - li_disc.append(stuff) - return li_disc - - def add_single_contingency(self, *args): - """ - This function allows to add a single contingency specified by either the powerlines names - (which should match env.name_line) or by their ID. - - The contingency added can be a "n-1" which will simulate a single powerline disconnection - or a "n-k" which will simulate the disconnection of multiple powerlines. - - It does not accept any keword arguments. - - Examples - -------- - - .. code-block:: python - - import grid2op - from lightsim2grid import SecurityAnalysis - from lightsim2grid import LightSimBackend - env_name = ... - env = grid2op.make(env_name, backend=LightSimBackend()) - - security_anlysis = SecurityAnalysis(env) - # the single (n-1) contingency "disconnect powerline 0" is added - security_anlysis.add_single_contingency(0) - - # add the single (n-1) contingency "disconnect line 1 - security_anlysis.add_single_contingency(env.name_line[1]) - - # add a single contingency that disconnect powerline 2 and 3 at the same time - security_anlysis.add_single_contingency(env.name_line[2], 3) - - Notes - ----- - If it raises an error for a given contingency, the object might be not properly initialized. - In this case, we recommend you to clear it (using the `clear()` method and to attempt to - add contingencies again.) - - """ - li_disc = self._single_cont_to_li_int(args) - li_disc_tup = tuple(li_disc) - if li_disc_tup not in self._contingency_order: - # this is really the first time this contingency is seen - try: - self.computer.add_nk(li_disc) - my_id = len(self._contingency_order) - self._contingency_order[li_disc_tup] = my_id - self._all_contingencies.append(li_disc_tup) - except Exception as exc_: - raise RuntimeError(f"Impossible to add the contingency {args}. The most likely cause " - f"is that you try to disconnect a powerline that is not present " - f"on the grid") from exc_ - - def add_multiple_contingencies(self, *args): - """ - This function will add multiple contingencies at the same time. - - This code is equivalent to: - - .. code-block:: python - - for single_cont in args: - self.add_single_contingency(single_cont) - - It does not accept any keword arguments. - - Examples - -------- - - .. code-block:: python - - import grid2op - from lightsim2grid import SecurityAnalysis - from lightsim2grid import LightSimBackend - env_name = ... - env = grid2op.make(env_name, backend=LightSimBackend()) - - security_anlysis = SecurityAnalysis(env) - - # add a single contingency that disconnect powerline 2 and 3 at the same time - security_anlysis.add_single_contingency(env.name_line[2], 3) - - # add a multiple contingencies the first one disconnect powerline 2 and - # and the second one disconnect powerline 3 - security_anlysis.add_multiple_contingencies(env.name_line[2], 3) - """ - for single_cont in args: - if isinstance(single_cont, Iterable) and not isinstance(single_cont, type(self).STR_TYPES): - # this is a contingency consisting in cutting multiple powerlines - self.add_single_contingency(*single_cont) - else: - # this is likely an int or a string representing a contingency - self.add_single_contingency(single_cont) - - def add_all_n1_contingencies(self): - """ - This method registers as the contingencies that will be computed all the contingencies that disconnects 1 powerline - - This is equivalent to: - - .. code-block:: python - - for single_cont_id in range(env.n_line): - self.add_single_contingency(single_cont_id) - """ - for single_cont_id in range(self.grid2op_env.n_line): - self.add_single_contingency(single_cont_id) - - def get_flows(self, *args): - """ - Retrieve the flows after each contingencies has been simulated. - - Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments. - - You can require only the result on some contingencies with the `args` argument, but in each case, all the results will - be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do) - - Examples - -------- - - .. code-block:: python - - import grid2op - from lightsim2grid import SecurityAnalysis - from lightsim2grid import LightSimBackend - env_name = ... - env = grid2op.make(env_name, backend=LightSimBackend()) - - security_analysis = SecurityAnalysis(env) - security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...) - res_p, res_a, res_v = security_analysis.get_flows() - - # in this results, then - # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency. - # you can retrieve it with `security_analysis.contingency_order[row_id]` - """ - - all_defaults = self.computer.my_defaults() - if len(args) == 0: - # default: i consider all contingencies - orders_ = np.zeros(len(all_defaults), dtype=int) - for id_cpp, cont_ in enumerate(all_defaults): - tup_ = tuple(cont_) - orders_[self._contingency_order[tup_]] = id_cpp - else: - # a list of interesting contingencies has been provided - orders_ = np.zeros(len(args), dtype=int) - all_defaults = [tuple(cont) for cont in all_defaults] - for id_me, cont_ in enumerate(args): - cont_li = self._single_cont_to_li_int(cont_) - tup_ = tuple(cont_li) - if tup_ not in self._contingency_order: - raise RuntimeError(f"Contingency {cont_} is not simulated by this class. Have you called " - f"`add_single_contingency` or `add_multiple_contingencies` ?") - id_cpp = all_defaults.index(tup_) - orders_[id_me] = id_cpp - - if not self.__computed: - self.compute_V() - self.compute_A() - self.compute_P() - - return self._mws[orders_], self._ampss[orders_], self._vs[orders_] - - def compute_V(self): - """ - This function allows to retrieve the complex voltage at each bus of the grid for each contingency. - - .. warning:: Order of the results - - The order in which the results are returned is NOT necessarily the order in which the contingencies have - been entered. Please use `get_flows()` method for easier reading back of the results - - """ - v_init = self.grid2op_env.backend.V - print("self.computer.compute") - self.computer.compute(v_init, - self.grid2op_env.backend.max_it, - self.grid2op_env.backend.tol) - print("self.computer.get_voltages()") - self._vs = self.computer.get_voltages() - self.__computed = True - return self._vs - - def compute_A(self): - """ - This function returns the current flows (in Amps, A) at the origin / high voltage side - - .. warning:: Order of the results - - The order in which the results are returned is NOT necessarily the order in which the contingencies have - been entered. Please use `get_flows()` method for easier reading back of the results ! - - """ - if not self.__computed: - raise RuntimeError("This function can only be used if compute_V has been sucessfully called") - self._ampss = 1e3 * self.computer.compute_flows() - return self._ampss - - def compute_P(self): - """ - This function returns the active power flows (in MW) at the origin / high voltage side - - .. warning:: Order of the results - - The order in which the results are returned is NOT necessarily the order in which the contingencies have - been entered. Please use `get_flows()` method for easier reading back of the results ! - - """ - if not self.__computed: - raise RuntimeError("This function can only be used if compute_V has been sucessfully called") - self._mws = 1.0 * self.computer.compute_power_flows() - return self._mws - - def close(self): - """permanently close the object""" - self.grid2op_env.close() - self.clear() - self.computer.close() +# Deprecated now, will be removed +SecurityAnalysisCPP = ContingencyAnalysisCPP +SecurityAnalysis = ContingencyAnalysis diff --git a/lightsim2grid/tests/test_Computers.py b/lightsim2grid/tests/test_Computers.py index 15940a94..afb6d825 100644 --- a/lightsim2grid/tests/test_Computers.py +++ b/lightsim2grid/tests/test_Computers.py @@ -11,13 +11,11 @@ from grid2op.Parameters import Parameters import warnings import numpy as np -from numpy.core.shape_base import stack -import lightsim2grid -import lightsim2grid_cpp from lightsim2grid import LightSimBackend -from lightsim2grid_cpp import Computers +from lightsim2grid_cpp import TimeSeriesCPP -class TestComputers(unittest.TestCase): + +class TestTimeSeriesCPP(unittest.TestCase): def test_basic(self): # print(f"{lightsim2grid_cpp.__file__}") env_name = "l2rpn_case14_sandbox" @@ -37,7 +35,7 @@ def test_basic(self): load_q = 1.0 * env.chronics_handler.real_data.data.load_q # now perform the computation - computer = Computers(grid) + computer = TimeSeriesCPP(grid) # print("start the computation") status = computer.compute_Vs(prod_p, np.zeros((prod_p.shape[0], 0)), # no static generators for now ! @@ -83,7 +81,7 @@ def test_amps(self): load_q = 1.0 * env.chronics_handler.real_data.data.load_q # now perform the computation - computer = Computers(grid) + computer = TimeSeriesCPP(grid) # print("start the computation") status = computer.compute_Vs(prod_p, np.zeros((prod_p.shape[0], 0)), # no static generators for now ! diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py index 7bddb14f..412615b8 100644 --- a/lightsim2grid/tests/test_issue_56.py +++ b/lightsim2grid/tests/test_issue_56.py @@ -15,7 +15,7 @@ import numpy as np import grid2op from lightsim2grid import LightSimBackend, SolverType -from lightsim2grid.securityAnalysis import SecurityAnalysis +from lightsim2grid.contingencyAnalysis import ContingencyAnalysis import pdb @@ -24,7 +24,7 @@ def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend()) - self.sa = SecurityAnalysis(self.env) + self.sa = ContingencyAnalysis(self.env) def test_dc(self): self.sa.add_all_n1_contingencies() @@ -39,7 +39,6 @@ def test_dc(self): assert np.any(res_a != res_a_dc), "DC and AC solver leads to same results" assert np.any(res_v != res_v_dc), "DC and AC solver leads to same results" assert self.sa.computer.get_solver_type() == SolverType.DC - return nb_bus = self.env.n_sub nb_powerline = len(self.env.backend._grid.get_lines()) @@ -78,7 +77,7 @@ def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("l2rpn_neurips_2020_track1", test=True, backend=LightSimBackend()) - self.sa = SecurityAnalysis(self.env) + self.sa = ContingencyAnalysis(self.env) class TestSADC_118(TestSADC_14): @@ -86,7 +85,7 @@ def setUp(self) -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("l2rpn_wcci_2022", test=True, backend=LightSimBackend()) - self.sa = SecurityAnalysis(self.env) + self.sa = ContingencyAnalysis(self.env) if __name__ == "__main__": diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index 4a1c4133..ec9a8976 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -30,6 +30,9 @@ from lightsim2grid import LightSimBackend from lightsim2grid.solver import SolverType +# TODO when line connected alone at one end, starts a Security Analysis +# to see if it works + class TestSolverControl(unittest.TestCase): def _aux_setup_grid(self): diff --git a/lightsim2grid/timeSerie.py b/lightsim2grid/timeSerie.py index 75bbaf18..2942441b 100644 --- a/lightsim2grid/timeSerie.py +++ b/lightsim2grid/timeSerie.py @@ -6,7 +6,9 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -__all__ = ["Computers", "TimeSerie"] +__all__ = ["TimeSerieCPP", "TimeSerie", + # deprecated + "Computers"] import numpy as np import warnings @@ -15,7 +17,10 @@ from lightsim2grid.lightSimBackend import LightSimBackend from lightsim2grid.solver import SolverType -from lightsim2grid_cpp import Computers +from lightsim2grid_cpp import TimeSeriesCPP + +# deprecated +Computers = TimeSeriesCPP class TimeSerie: @@ -79,7 +84,7 @@ def __init__(self, grid2op_env): raise RuntimeError("Please an environment of class \"Environment\", " "and not \"MultimixEnv\" or \"BaseMultiProcessEnv\"") self.grid2op_env = grid2op_env.copy() - self.computer = Computers(self.grid2op_env.backend._grid) + self.computer = TimeSeriesCPP(self.grid2op_env.backend._grid) self.prod_p = None self.load_p = None self.load_q = None diff --git a/setup.py b/setup.py index 8ddbbf5c..88b3f056 100644 --- a/setup.py +++ b/setup.py @@ -145,12 +145,12 @@ "src/BaseConstants.cpp", "src/GridModel.cpp", "src/ChooseSolver.cpp", - "src/BaseMultiplePowerflow.cpp", - "src/Computers.cpp", - "src/SecurityAnalysis.cpp", "src/Solvers.cpp", "src/Utils.cpp", "src/DataConverter.cpp", + "src/batch_algorithm/BaseBatchSolverSynch.cpp", + "src/batch_algorithm/TimeSeries.cpp", + "src/batch_algorithm/ContingencyAnalysis.cpp", "src/element_container/LineContainer.cpp", "src/element_container/GenericContainer.cpp", "src/element_container/ShuntContainer.cpp", diff --git a/src/BaseMultiplePowerflow.cpp b/src/batch_algorithm/BaseBatchSolverSynch.cpp similarity index 93% rename from src/BaseMultiplePowerflow.cpp rename to src/batch_algorithm/BaseBatchSolverSynch.cpp index 3c90661c..23cc9352 100644 --- a/src/BaseMultiplePowerflow.cpp +++ b/src/batch_algorithm/BaseBatchSolverSynch.cpp @@ -6,12 +6,12 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" /** V is modified at each call ! **/ -bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix & Ybus, +bool BaseBatchSolverSynch::compute_one_powerflow(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -32,7 +32,7 @@ bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix RealMat; typedef Eigen::Matrix CplxMat; - BaseMultiplePowerflow(const GridModel & init_grid_model): + BaseBatchSolverSynch(const GridModel & init_grid_model): _grid_model(init_grid_model), n_line_(init_grid_model.nb_powerline()), n_trafos_(init_grid_model.nb_trafo()), @@ -50,7 +52,7 @@ class BaseMultiplePowerflow _solver.tell_solver_control(_solver_control); } - BaseMultiplePowerflow(const BaseMultiplePowerflow&) = delete; + BaseBatchSolverSynch(const BaseBatchSolverSynch&) = delete; // solver "control" void change_solver(const SolverType & type){ @@ -242,4 +244,5 @@ class BaseMultiplePowerflow SolverControl _solver_control; }; + #endif // BASEMULTIPLEPOWERFLOW_H diff --git a/src/SecurityAnalysis.cpp b/src/batch_algorithm/ContingencyAnalysis.cpp similarity index 94% rename from src/SecurityAnalysis.cpp rename to src/batch_algorithm/ContingencyAnalysis.cpp index 17f9c3cd..11182891 100644 --- a/src/SecurityAnalysis.cpp +++ b/src/batch_algorithm/ContingencyAnalysis.cpp @@ -6,12 +6,12 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "SecurityAnalysis.h" +#include "ContingencyAnalysis.h" #include #include /* isfinite */ -bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Ybus) const{ +bool ContingencyAnalysis::check_invertible(const Eigen::SparseMatrix & Ybus) const{ std::vector visited(Ybus.cols(), false); std::vector already_added(Ybus.cols(), false); std::queue neighborhood; @@ -44,7 +44,7 @@ bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Y return ok; } -void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ +void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ _li_coeffs.clear(); _li_coeffs.reserve(_li_defaults.size()); const auto & powerlines = _grid_model.get_powerlines_as_data(); @@ -104,7 +104,7 @@ void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ } -bool SecurityAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, +bool ContingencyAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, const std::vector & coeffs) const { for(const auto & coeff_to_remove: coeffs){ @@ -112,7 +112,7 @@ bool SecurityAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, } return check_invertible(Ybus); } -void SecurityAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, +void ContingencyAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, const std::vector & coeffs) const { for(const auto & coeff_to_remove: coeffs){ @@ -120,7 +120,7 @@ void SecurityAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, } } -void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type tol) +void ContingencyAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type tol) { auto timer = CustTimer(); auto timer_preproc = CustTimer(); @@ -160,7 +160,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t Eigen::Index nb_steps = _li_defaults.size(); // init the results matrices - _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); + _voltages = BaseBatchSolverSynch::CplxMat::Zero(nb_steps, nb_total_bus); _amps_flows = RealMat::Zero(0, n_total_); // reset the solver @@ -215,7 +215,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t _timer_total = timer.duration(); } -void SecurityAnalysis::clean_flows(bool is_amps) +void ContingencyAnalysis::clean_flows(bool is_amps) { auto timer = CustTimer(); Eigen::Index cont_id = 0; diff --git a/src/SecurityAnalysis.h b/src/batch_algorithm/ContingencyAnalysis.h similarity index 89% rename from src/SecurityAnalysis.h rename to src/batch_algorithm/ContingencyAnalysis.h index ce7fe2d2..052722e4 100644 --- a/src/SecurityAnalysis.h +++ b/src/batch_algorithm/ContingencyAnalysis.h @@ -9,7 +9,7 @@ #ifndef SECURITYANALYSIS_H #define SECURITYANALYSIS_H -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" #include struct Coeff{ @@ -19,20 +19,22 @@ struct Coeff{ }; /** -Class to perform a security analysis, which consist of performing some powerflow after some powerlines +Class to perform a contingency analysis (security analysis), which consist of performing some powerflow after some powerlines have been disconnected **/ -class SecurityAnalysis: public BaseMultiplePowerflow +class ContingencyAnalysis: public BaseBatchSolverSynch { public: - SecurityAnalysis(const GridModel & init_grid_model): - BaseMultiplePowerflow(init_grid_model), - _li_defaults(), - _li_coeffs(), - _timer_total(0.), - _timer_modif_Ybus(0.), - _timer_pre_proc(0.) - { } + ContingencyAnalysis(const GridModel & init_grid_model): + BaseBatchSolverSynch(init_grid_model), + _li_defaults(), + _li_coeffs(), + _timer_total(0.), + _timer_modif_Ybus(0.), + _timer_pre_proc(0.) + { } + + ContingencyAnalysis(const ContingencyAnalysis&) = delete; // utilities to add defaults to simulate void add_all_n1(){ @@ -65,7 +67,7 @@ class SecurityAnalysis: public BaseMultiplePowerflow // utilities to remove defaults to simulate (TODO) virtual void clear(){ - BaseMultiplePowerflow::clear(); + BaseBatchSolverSynch::clear(); _li_defaults.clear(); _li_coeffs.clear(); _timer_total = 0.; diff --git a/src/Computers.cpp b/src/batch_algorithm/TimeSeries.cpp similarity index 87% rename from src/Computers.cpp rename to src/batch_algorithm/TimeSeries.cpp index af8da7e8..52b4edb1 100644 --- a/src/Computers.cpp +++ b/src/batch_algorithm/TimeSeries.cpp @@ -6,23 +6,23 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "Computers.h" +#include "TimeSeries.h" #include #include -int Computers::compute_Vs(Eigen::Ref gen_p, - Eigen::Ref sgen_p, - Eigen::Ref load_p, - Eigen::Ref load_q, - const CplxVect & Vinit, - const int max_iter, - const real_type tol) +int TimeSeries::compute_Vs(Eigen::Ref gen_p, + Eigen::Ref sgen_p, + Eigen::Ref load_p, + Eigen::Ref load_q, + const CplxVect & Vinit, + const int max_iter, + const real_type tol) { auto timer = CustTimer(); const Eigen::Index nb_total_bus = _grid_model.total_bus(); if(Vinit.size() != nb_total_bus){ std::ostringstream exc_; - exc_ << "Computers::compute_Sbuses: Size of the Vinit should be the same as the total number of buses. Currently: "; + exc_ << "TimeSeries::compute_Sbuses: Size of the Vinit should be the same as the total number of buses. Currently: "; exc_ << "Vinit: " << Vinit.size() << " and there are " << nb_total_bus << " buses."; exc_ << "(fyi: Components of Vinit corresponding to deactivated bus will be ignored anyway, so you can put whatever you want there)."; throw std::runtime_error(exc_.str()); @@ -72,7 +72,7 @@ int Computers::compute_Vs(Eigen::Ref gen_p, // TODO trafo hack for Sbus ! // init the results matrices - _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); + _voltages = BaseBatchSolverSynch::CplxMat::Zero(nb_steps, nb_total_bus); _amps_flows = RealMat::Zero(0, n_total_); // extract V solver from the given V diff --git a/src/Computers.h b/src/batch_algorithm/TimeSeries.h similarity index 94% rename from src/Computers.h rename to src/batch_algorithm/TimeSeries.h index 091583e3..6a20345d 100644 --- a/src/Computers.h +++ b/src/batch_algorithm/TimeSeries.h @@ -9,17 +9,17 @@ #ifndef COMPUTERS_H #define COMPUTERS_H -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" /** Allws the computation of time series, that is, the same grid topology is used along with time series of injections (productions and loads) to compute powerflows/ **/ -class Computers: public BaseMultiplePowerflow +class TimeSeries: public BaseBatchSolverSynch { public: - Computers(const GridModel & init_grid_model): - BaseMultiplePowerflow(init_grid_model), + TimeSeries(const GridModel & init_grid_model): + BaseBatchSolverSynch(init_grid_model), _Sbuses(), _status(1), // 1: success, 0: failure _compute_flows(true), @@ -27,7 +27,7 @@ class Computers: public BaseMultiplePowerflow _timer_pre_proc(0.) {} - Computers(const Computers&) = delete; + TimeSeries(const TimeSeries&) = delete; // control on whether I compute the flows or not void deactivate_flow_computations() {_compute_flows = false;} @@ -55,7 +55,7 @@ class Computers: public BaseMultiplePowerflow const real_type tol); virtual void clear(){ - BaseMultiplePowerflow::clear(); + BaseBatchSolverSynch::clear(); _Sbuses = CplxMat(); _status = 1; _compute_flows = true; diff --git a/src/main.cpp b/src/main.cpp index b7986131..a1c9a6bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,9 @@ #include "ChooseSolver.h" #include "DataConverter.h" #include "GridModel.h" -#include "Computers.h" -#include "SecurityAnalysis.h" + +#include "batch_algorithm/TimeSeries.h" +#include "batch_algorithm/ContingencyAnalysis.h" #include "help_fun_msg.h" @@ -858,78 +859,78 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("debug_get_Bpp_python", &GridModel::debug_get_Bpp_python, DocGridModel::_internal_do_not_use.c_str()) ; - py::class_(m, "Computers", DocComputers::Computers.c_str()) + py::class_(m, "TimeSeriesCPP", DocComputers::Computers.c_str()) .def(py::init()) // solver control - .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str()) - .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str()) - .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str()) + .def("change_solver", &TimeSeries::change_solver, DocGridModel::change_solver.c_str()) + .def("available_solvers", &TimeSeries::available_solvers, DocGridModel::available_solvers.c_str()) + .def("get_solver_type", &TimeSeries::get_solver_type, DocGridModel::get_solver_type.c_str()) // timers - .def("total_time", &Computers::total_time, DocComputers::total_time.c_str()) - .def("solver_time", &Computers::solver_time, DocComputers::solver_time.c_str()) - .def("preprocessing_time", &Computers::preprocessing_time, DocComputers::preprocessing_time.c_str()) - .def("amps_computation_time", &Computers::amps_computation_time, DocComputers::amps_computation_time.c_str()) - .def("nb_solved", &Computers::nb_solved, DocComputers::nb_solved.c_str()) + .def("total_time", &TimeSeries::total_time, DocComputers::total_time.c_str()) + .def("solver_time", &TimeSeries::solver_time, DocComputers::solver_time.c_str()) + .def("preprocessing_time", &TimeSeries::preprocessing_time, DocComputers::preprocessing_time.c_str()) + .def("amps_computation_time", &TimeSeries::amps_computation_time, DocComputers::amps_computation_time.c_str()) + .def("nb_solved", &TimeSeries::nb_solved, DocComputers::nb_solved.c_str()) // status - .def("get_status", &Computers::get_status, DocComputers::get_status.c_str()) - .def("clear", &Computers::clear, DocComputers::clear.c_str()) - .def("close", &Computers::clear, DocComputers::clear.c_str()) + .def("get_status", &TimeSeries::get_status, DocComputers::get_status.c_str()) + .def("clear", &TimeSeries::clear, DocComputers::clear.c_str()) + .def("close", &TimeSeries::clear, DocComputers::clear.c_str()) // perform the computations - .def("compute_Vs", &Computers::compute_Vs, py::call_guard(), DocComputers::compute_Vs.c_str()) - .def("compute_flows", &Computers::compute_flows, py::call_guard(), DocComputers::compute_flows.c_str()) - .def("compute_power_flows", &Computers::compute_power_flows, DocComputers::compute_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("compute_Vs", &TimeSeries::compute_Vs, py::call_guard(), DocComputers::compute_Vs.c_str()) + .def("compute_flows", &TimeSeries::compute_flows, py::call_guard(), DocComputers::compute_flows.c_str()) + .def("compute_power_flows", &TimeSeries::compute_power_flows, DocComputers::compute_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" // results (for now only flow (at each -line origin- or voltages -at each buses) - .def("get_flows", &Computers::get_flows, DocComputers::get_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" - .def("get_power_flows", &Computers::get_power_flows, DocComputers::get_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" - .def("get_voltages", &Computers::get_voltages, DocComputers::get_voltages.c_str()) // need to be done after "compute_Vs" - .def("get_sbuses", &Computers::get_sbuses, DocComputers::get_sbuses.c_str()) // need to be done after "compute_Vs" + .def("get_flows", &TimeSeries::get_flows, DocComputers::get_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("get_power_flows", &TimeSeries::get_power_flows, DocComputers::get_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("get_voltages", &TimeSeries::get_voltages, DocComputers::get_voltages.c_str()) // need to be done after "compute_Vs" + .def("get_sbuses", &TimeSeries::get_sbuses, DocComputers::get_sbuses.c_str()) // need to be done after "compute_Vs" ; - py::class_(m, "SecurityAnalysisCPP", DocSecurityAnalysis::SecurityAnalysis.c_str()) + py::class_(m, "ContingencyAnalysisCPP", DocSecurityAnalysis::SecurityAnalysis.c_str()) .def(py::init()) // solver control - .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str()) - .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str()) - .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str()) + .def("change_solver", &ContingencyAnalysis::change_solver, DocGridModel::change_solver.c_str()) + .def("available_solvers", &ContingencyAnalysis::available_solvers, DocGridModel::available_solvers.c_str()) + .def("get_solver_type", &ContingencyAnalysis::get_solver_type, DocGridModel::get_solver_type.c_str()) // add some defaults - .def("add_all_n1", &SecurityAnalysis::add_all_n1, DocSecurityAnalysis::add_all_n1.c_str()) - .def("add_n1", &SecurityAnalysis::add_n1, DocSecurityAnalysis::add_n1.c_str()) - .def("add_nk", &SecurityAnalysis::add_nk, DocSecurityAnalysis::add_nk.c_str()) - .def("add_multiple_n1", &SecurityAnalysis::add_multiple_n1, DocSecurityAnalysis::add_multiple_n1.c_str()) + .def("add_all_n1", &ContingencyAnalysis::add_all_n1, DocSecurityAnalysis::add_all_n1.c_str()) + .def("add_n1", &ContingencyAnalysis::add_n1, DocSecurityAnalysis::add_n1.c_str()) + .def("add_nk", &ContingencyAnalysis::add_nk, DocSecurityAnalysis::add_nk.c_str()) + .def("add_multiple_n1", &ContingencyAnalysis::add_multiple_n1, DocSecurityAnalysis::add_multiple_n1.c_str()) // remove some defaults (TODO) - .def("reset", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str()) - .def("clear", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str()) - .def("close", &SecurityAnalysis::clear, DocComputers::clear.c_str()) - .def("remove_n1", &SecurityAnalysis::remove_n1, DocSecurityAnalysis::remove_n1.c_str()) - .def("remove_nk", &SecurityAnalysis::remove_nk, DocSecurityAnalysis::remove_nk.c_str()) - .def("remove_multiple_n1", &SecurityAnalysis::remove_multiple_n1, DocSecurityAnalysis::remove_multiple_n1.c_str()) + .def("reset", &ContingencyAnalysis::clear, DocSecurityAnalysis::clear.c_str()) + .def("clear", &ContingencyAnalysis::clear, DocSecurityAnalysis::clear.c_str()) + .def("close", &ContingencyAnalysis::clear, DocComputers::clear.c_str()) + .def("remove_n1", &ContingencyAnalysis::remove_n1, DocSecurityAnalysis::remove_n1.c_str()) + .def("remove_nk", &ContingencyAnalysis::remove_nk, DocSecurityAnalysis::remove_nk.c_str()) + .def("remove_multiple_n1", &ContingencyAnalysis::remove_multiple_n1, DocSecurityAnalysis::remove_multiple_n1.c_str()) // inspect the class - .def("my_defaults", &SecurityAnalysis::my_defaults_vect, DocSecurityAnalysis::my_defaults_vect.c_str()) + .def("my_defaults", &ContingencyAnalysis::my_defaults_vect, DocSecurityAnalysis::my_defaults_vect.c_str()) // perform the computation - .def("compute", &SecurityAnalysis::compute, py::call_guard(), DocSecurityAnalysis::compute.c_str()) - .def("compute_flows", &SecurityAnalysis::compute_flows, py::call_guard(), DocSecurityAnalysis::compute_flows.c_str()) - .def("compute_power_flows", &SecurityAnalysis::compute_power_flows, DocSecurityAnalysis::compute_power_flows.c_str()) + .def("compute", &ContingencyAnalysis::compute, py::call_guard(), DocSecurityAnalysis::compute.c_str()) + .def("compute_flows", &ContingencyAnalysis::compute_flows, py::call_guard(), DocSecurityAnalysis::compute_flows.c_str()) + .def("compute_power_flows", &ContingencyAnalysis::compute_power_flows, DocSecurityAnalysis::compute_power_flows.c_str()) // results (for now only flow (at each -line origin- or voltages -at each buses) - .def("get_flows", &SecurityAnalysis::get_flows, DocSecurityAnalysis::get_flows.c_str()) - .def("get_voltages", &SecurityAnalysis::get_voltages, DocSecurityAnalysis::get_voltages.c_str()) - .def("get_power_flows", &SecurityAnalysis::get_power_flows, DocSecurityAnalysis::get_power_flows.c_str()) + .def("get_flows", &ContingencyAnalysis::get_flows, DocSecurityAnalysis::get_flows.c_str()) + .def("get_voltages", &ContingencyAnalysis::get_voltages, DocSecurityAnalysis::get_voltages.c_str()) + .def("get_power_flows", &ContingencyAnalysis::get_power_flows, DocSecurityAnalysis::get_power_flows.c_str()) // timers - .def("total_time", &SecurityAnalysis::total_time, DocComputers::total_time.c_str()) - .def("solver_time", &SecurityAnalysis::solver_time, DocComputers::solver_time.c_str()) - .def("preprocessing_time", &SecurityAnalysis::preprocessing_time, DocSecurityAnalysis::preprocessing_time.c_str()) - .def("amps_computation_time", &SecurityAnalysis::amps_computation_time, DocComputers::amps_computation_time.c_str()) - .def("modif_Ybus_time", &SecurityAnalysis::modif_Ybus_time, DocSecurityAnalysis::modif_Ybus_time.c_str()) - .def("nb_solved", &SecurityAnalysis::nb_solved, DocComputers::nb_solved.c_str()) + .def("total_time", &ContingencyAnalysis::total_time, DocComputers::total_time.c_str()) + .def("solver_time", &ContingencyAnalysis::solver_time, DocComputers::solver_time.c_str()) + .def("preprocessing_time", &ContingencyAnalysis::preprocessing_time, DocSecurityAnalysis::preprocessing_time.c_str()) + .def("amps_computation_time", &ContingencyAnalysis::amps_computation_time, DocComputers::amps_computation_time.c_str()) + .def("modif_Ybus_time", &ContingencyAnalysis::modif_Ybus_time, DocSecurityAnalysis::modif_Ybus_time.c_str()) + .def("nb_solved", &ContingencyAnalysis::nb_solved, DocComputers::nb_solved.c_str()) ; } From b5646ddb3bc156d47f4da759f8a31c1280b73898 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Wed, 10 Jan 2024 14:13:35 +0100 Subject: [PATCH 46/66] fixing broken imports after renaming --- lightsim2grid/tests/test_SecurityAnlysis.py | 30 +++++++++---------- .../tests/test_SecurityAnlysis_cpp.py | 24 +++++++-------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lightsim2grid/tests/test_SecurityAnlysis.py b/lightsim2grid/tests/test_SecurityAnlysis.py index 4927f6fe..1ab67daa 100644 --- a/lightsim2grid/tests/test_SecurityAnlysis.py +++ b/lightsim2grid/tests/test_SecurityAnlysis.py @@ -11,7 +11,7 @@ import numpy as np import grid2op -from lightsim2grid import SecurityAnalysis +from lightsim2grid import ContingencyAnalysis from lightsim2grid import LightSimBackend import warnings import pdb @@ -28,10 +28,10 @@ def tearDown(self) -> None: return super().tearDown() def test_can_create(self): - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) def test_clear(self): - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) # add simple contingencies sa.add_multiple_contingencies(0, 1, 2, 3) @@ -46,7 +46,7 @@ def test_clear(self): assert len(sa._contingency_order) == 0 def test_add_single_contingency(self): - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) with self.assertRaises(RuntimeError): sa.add_single_contingency("toto") with self.assertRaises(RuntimeError): @@ -64,7 +64,7 @@ def test_add_single_contingency(self): assert len(sa._contingency_order) == 4 def test_add_multiple_contingencies(self): - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) # add simple contingencies sa.add_multiple_contingencies(0, 1, 2, 3) all_conts = sa.computer.my_defaults() @@ -91,7 +91,7 @@ def test_add_multiple_contingencies(self): assert len(sa._contingency_order) == 4 def test_add_all_n1_contingencies(self): - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_all_n1_contingencies() all_conts = sa.computer.my_defaults() assert len(all_conts) == self.env.n_line @@ -100,7 +100,7 @@ def test_add_all_n1_contingencies(self): def test_get_flows_simple(self): """test the get_flows method in the most simplest way: ask for all contingencies, contingencies are given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 1, 2) res_p, res_a, res_v = sa.get_flows() assert res_a.shape == (3, self.env.n_line) @@ -111,7 +111,7 @@ def test_get_flows_simple(self): def test_get_flows_1(self): """test the get_flows method: ask for all contingencies , contingencies are NOT given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 2, 1) res_p, res_a, res_v = sa.get_flows() assert res_a.shape == (3, self.env.n_line) @@ -122,7 +122,7 @@ def test_get_flows_1(self): def test_get_flows_2(self): """test the get_flows method: don't ask for all contingencies (same order as given), contingencies are given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 1, 2) res_p, res_a, res_v = sa.get_flows(0, 1) assert res_a.shape == (2, self.env.n_line) @@ -132,7 +132,7 @@ def test_get_flows_2(self): def test_get_flows_3(self): """test the get_flows method in the most simplest way: not all contingencies (not same order as given), contingencies are given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 1, 2) res_p, res_a, res_v = sa.get_flows(0, 2) assert res_a.shape == (2, self.env.n_line) @@ -142,7 +142,7 @@ def test_get_flows_3(self): def test_get_flows_4(self): """test the get_flows method: don't ask for all contingencies (same order as given), contingencies are NOT given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 2, 1) res_p, res_a, res_v = sa.get_flows(0, 2) assert res_a.shape == (2, self.env.n_line) @@ -152,7 +152,7 @@ def test_get_flows_4(self): def test_get_flows_5(self): """test the get_flows method in the most simplest way: not all contingencies (not same order as given), contingencies are NOT given in the right order""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, 2, 1) res_p, res_a, res_v = sa.get_flows(0, 1) assert res_a.shape == (2, self.env.n_line) @@ -161,7 +161,7 @@ def test_get_flows_5(self): def test_get_flows_multiple(self): """test the get_flows function when multiple contingencies""" - sa = SecurityAnalysis(self.env) + sa = ContingencyAnalysis(self.env) sa.add_multiple_contingencies(0, [0, 4], [5, 7], 4) # everything @@ -197,11 +197,11 @@ def test_get_flows_multiple(self): def test_change_injection(self): """test the capacity of the things to handle different steps""" - sa1 = SecurityAnalysis(self.env) + sa1 = ContingencyAnalysis(self.env) conts = [0, [0, 4], [5, 7], 4] sa1.add_multiple_contingencies(*conts) self.env.reset() - sa2 = SecurityAnalysis(self.env) + sa2 = ContingencyAnalysis(self.env) sa2.add_multiple_contingencies(*conts) res_p1, res_a1, res_v1 = sa1.get_flows() diff --git a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py index bbf73ae7..15de1086 100644 --- a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py +++ b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py @@ -10,7 +10,7 @@ import numpy as np import grid2op -from lightsim2grid_cpp import SecurityAnalysisCPP +from lightsim2grid_cpp import ContingencyAnalysisCPP from lightsim2grid import LightSimBackend import warnings import pdb @@ -29,11 +29,11 @@ def tearDown(self) -> None: return super().tearDown() def test_can_create(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) assert len(SA.my_defaults()) == 0 def test_add_n1(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_n1(0) all_def = SA.my_defaults() assert len(all_def) == 1 @@ -64,7 +64,7 @@ def test_add_n1(self): SA.add_n1(20) def test_add_multiple_n1(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_multiple_n1([0]) all_def = SA.my_defaults() assert len(all_def) == 1 @@ -99,7 +99,7 @@ def test_add_multiple_n1(self): SA.add_multiple_n1([20]) def test_add_nk(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_nk([0]) all_def = SA.my_defaults() assert len(all_def) == 1 @@ -132,13 +132,13 @@ def test_add_nk(self): SA.add_nk([20]) def test_add_all_n1(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_all_n1() all_def = SA.my_defaults() assert len(all_def) == self.env.n_line def test_remove_n1(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_all_n1() assert SA.remove_n1(0) # this should remove it and return true (because the removing is a success) all_def = SA.my_defaults() @@ -148,7 +148,7 @@ def test_remove_n1(self): assert len(all_def) == self.env.n_line - 1 def test_clear(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_all_n1() all_def = SA.my_defaults() assert len(all_def) == self.env.n_line @@ -157,7 +157,7 @@ def test_clear(self): assert len(all_def) == 0 def test_remove_multiple_n1(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_all_n1() nb_removed = SA.remove_multiple_n1([0, 1, 2]) assert nb_removed == 3 @@ -169,7 +169,7 @@ def test_remove_multiple_n1(self): assert len(all_def) == self.env.n_line - 5 def test_remove_nk(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) SA.add_nk([0, 1]) SA.add_nk([0, 2]) SA.add_nk([0, 3]) @@ -188,7 +188,7 @@ def test_remove_nk(self): assert len(all_def) == 2 def test_compute(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) lid_cont = [0, 1, 2, 3] nb_sub = self.env.n_sub SA.add_multiple_n1(lid_cont) @@ -207,7 +207,7 @@ def test_compute(self): assert np.max(np.abs(res_flows[cont_id] - sim_obs.a_or*1e-3)) <= 1e-6, f"error in flows when disconnecting line {l_id} (contingency nb {cont_id})" def test_compute_nonconnected_graph(self): - SA = SecurityAnalysisCPP(self.env.backend._grid) + SA = ContingencyAnalysisCPP(self.env.backend._grid) lid_cont = [17, 18, 19] # 17 is ok, 18 lead to divergence, i need to check then that 19 is correct (no divergence) nb_sub = self.env.n_sub SA.add_multiple_n1(lid_cont) From 3ed4bad4aa4c126f5c42c964534b6d10cc078952 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 7 Mar 2024 11:32:50 +0100 Subject: [PATCH 47/66] adding support for more than 2 busbars per substation --- CHANGELOG.rst | 5 +- benchmarks/benchmark_grid_size.py | 51 ++- lightsim2grid/lightSimBackend.py | 134 ++++---- lightsim2grid/tests/test_n_busbar_per_sub.py | 307 +++++++++++++++++++ lightsim2grid/tests/test_solver_control.py | 1 - src/GridModel.cpp | 9 +- src/GridModel.h | 16 +- src/main.cpp | 6 + 8 files changed, 463 insertions(+), 66 deletions(-) create mode 100644 lightsim2grid/tests/test_n_busbar_per_sub.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d5c04d2b..666a2a4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,7 +21,7 @@ Change Log [0.7.6] 2023-xx-yy -------------------- - [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`). - If you previously used `gridmodel.get_Subus()` to retrieve the Sbus used for DC powerflow, please use + If you previously used `gridmodel.get_Sbus()` to retrieve the Sbus used for DC powerflow, please use `gridmodel.get_dcSbus()` instead. - [DEPRECATED] in the cpp class: the old `SecurityAnalysisCPP` has been renamed `ContingencyAnalysisCPP` (you should not import it, but it you do you can `from lightsim2grid.securityAnalysis import ContingencyAnalysisCPP` now) @@ -45,6 +45,9 @@ Change Log - [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side) - [ADDED] computation of PTPF (Power Transfer Distribution Factor) is now possible - [ADDED] (not tested) support for more than 2 busbars per substation +- [ADDED] a timer to get the time spent in the gridmodel for the powerflow (env.backend.timer_gridmodel_xx_pf) + which also include the time +- [ADDED] support for more than 2 busbars per substation (requires grid2op >= 1.10.0) - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` - [IMPROVED] clean ce cpp side by refactoring: making clearer the difference (linear) solver diff --git a/benchmarks/benchmark_grid_size.py b/benchmarks/benchmark_grid_size.py index b7a15adb..09d627eb 100644 --- a/benchmarks/benchmark_grid_size.py +++ b/benchmarks/benchmark_grid_size.py @@ -13,7 +13,12 @@ import matplotlib.pyplot as plt from grid2op import make, Parameters from grid2op.Chronics import FromNPY -from lightsim2grid import LightSimBackend, TimeSerie, SecurityAnalysis +from lightsim2grid import LightSimBackend, TimeSerie +try: + from lightsim2grid import ContingencyAnalysis +except ImportError: + from lightsim2grid import SecurityAnalysis as ContingencyAnalysis + from tqdm import tqdm import os from utils_benchmark import print_configuration, get_env_name_displayed @@ -157,6 +162,8 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): g2op_speeds = [] g2op_sizes = [] g2op_step_time = [] + ls_solver_time = [] + ls_gridmodel_time = [] ts_times = [] ts_speeds = [] @@ -227,13 +234,18 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): g2op_times.append(None) g2op_speeds.append(None) g2op_step_time.append(None) + ls_solver_time.append(None) + ls_gridmodel_time.append(None) g2op_sizes.append(env_lightsim.n_sub) else: total_time = env_lightsim.backend._timer_preproc + env_lightsim.backend._timer_solver # + env_lightsim.backend._timer_postproc + total_time = env_lightsim._time_step g2op_times.append(total_time) g2op_speeds.append(1.0 * nb_step / total_time) g2op_step_time.append(1.0 * env_lightsim._time_step / nb_step) - g2op_sizes.append(env_lightsim.n_sub) + ls_solver_time.append(env_lightsim.backend.comp_time) + ls_gridmodel_time.append(env_lightsim.backend.timer_gridmodel_xx_pf) + g2op_sizes.append(env_lightsim.n_sub) # Perform the computation using TimeSerie env_lightsim.reset() @@ -274,7 +286,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): # Perform a securtiy analysis (up to 1000 contingencies) env_lightsim.reset() - sa = SecurityAnalysis(env_lightsim) + sa = ContingencyAnalysis(env_lightsim) for i in range(env_lightsim.n_line): sa.add_single_contingency(i) if i >= 1000: @@ -297,11 +309,24 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): print("Results using grid2op.steps (288 consecutive steps, only measuring 'dc pf [init] + ac pf')") tab_g2op = [] for i, nm_ in enumerate(case_names_displayed): - tab_g2op.append((nm_, ts_sizes[i], 1000. / g2op_speeds[i] if g2op_speeds[i] else None, g2op_speeds[i], - 1000. * g2op_step_time[i] if g2op_step_time[i] else None)) + tab_g2op.append((nm_, + ts_sizes[i], + 1000. / g2op_speeds[i] if g2op_speeds[i] else None, + g2op_speeds[i], + 1000. * g2op_step_time[i] if g2op_step_time[i] else None, + 1000. * ls_gridmodel_time[i] / nb_step if ls_gridmodel_time[i] else None, + 1000. * ls_solver_time[i] / nb_step if ls_solver_time[i] else None, + )) if TABULATE_AVAIL: res_use_with_grid2op_2 = tabulate(tab_g2op, - headers=["grid", "size (nb bus)", "time (ms / pf)", "speed (pf / s)", "avg step duration (ms)"], + headers=["grid", + "size (nb bus)", + "step time (ms / pf)", + "speed (pf / s)", + "avg step duration (ms)", + "time in 'gridmodel' (ms / pf)", + "time in 'pf algo' (ms / pf)", + ], tablefmt="rst") print(res_use_with_grid2op_2) else: @@ -349,6 +374,20 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): plt.title(f"Computation speed using Grid2Op.step (dc pf [init] + ac pf)") plt.yscale("log") plt.show() + + plt.plot(g2op_sizes, ls_solver_time, linestyle='solid', marker='+', markersize=8) + plt.xlabel("Size (number of substation)") + plt.ylabel("Speed (solver time)") + plt.title(f"Computation speed for solving the powerflow only") + plt.yscale("log") + plt.show() + + plt.plot(g2op_sizes, ls_gridmodel_time, linestyle='solid', marker='+', markersize=8) + plt.xlabel("Size (number of substation)") + plt.ylabel("Speed (solver time)") + plt.title(f"Computation speed for solving the powerflow only") + plt.yscale("log") + plt.show() # make the plot summarizing all results plt.plot(ts_sizes, ts_times, linestyle='solid', marker='+', markersize=8) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 81732db0..bbecd58d 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -168,7 +168,17 @@ def __init__(self, # available solver in lightsim self.available_solvers = [] - self.comp_time = 0. # computation time of just the powerflow + + # computation time of just the powerflow (when the grid is formatted + # by the gridmodel already) + # it takes only into account the time spend in the powerflow algorithm + self.comp_time = 0. + + # computation time of the powerflow + # it takes into account everything in the gridmodel, including the mapping + # to the solver, building of Ybus and Sbus AND the time to solve the powerflow + self.timer_gridmodel_xx_pf = 0. + self._timer_postproc = 0. self._timer_preproc = 0. self._timer_solver = 0. @@ -195,22 +205,6 @@ def __init__(self, # add the static gen to the list of controlable gen in grid2Op self._use_static_gen = use_static_gen # TODO implement it - - # storage data for this object (otherwise it's in the class) - # self.n_storage = None - # self.storage_to_subid = None - # self.storage_pu_to_kv = None - # self.name_storage = None - # self.storage_to_sub_pos = None - # self.storage_type = None - # self.storage_Emin = None - # self.storage_Emax = None - # self.storage_max_p_prod = None - # self.storage_max_p_absorb = None - # self.storage_marginal_cost = None - # self.storage_loss = None - # self.storage_discharging_efficiency = None - # self.storage_charging_efficiency = None def turnedoff_no_pv(self): self._turned_off_pv = False @@ -411,6 +405,10 @@ def _assign_right_solver(self): self._grid.change_solver(SolverType.SparseLU) def load_grid(self, path=None, filename=None): + if hasattr(type(self), "can_handle_more_than_2_busbar"): + # grid2op version >= 1.10.0 then we use this + self.can_handle_more_than_2_busbar() + if self._loader_method == "pandapower": self._load_grid_pandapower(path, filename) elif self._loader_method == "pypowsybl": @@ -418,6 +416,42 @@ def load_grid(self, path=None, filename=None): else: raise BackendError(f"Impossible to initialize the backend with '{self._loader_method}'") + def _should_not_have_to_do_this(self, path=None, filename=None): + # included in grid2op now ! + # but before `make_complete_path` was introduced we need to still + # be able to use lightsim2grid + import os + from grid2op.Exceptions import Grid2OpException + if path is None and filename is None: + raise Grid2OpException( + "You must provide at least one of path or file to load a powergrid." + ) + if path is None: + full_path = filename + elif filename is None: + full_path = path + else: + full_path = os.path.join(path, filename) + if not os.path.exists(full_path): + raise Grid2OpException('There is no powergrid at "{}"'.format(full_path)) + return full_path + + def _aux_pypowsybl_init_substations(self, loader_kwargs): + # now handle the legacy "make as if there are 2 busbars per substation" + # as it is done with grid2Op simulated environment + if (("double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]) or + ("n_busbar_per_sub" in loader_kwargs and loader_kwargs["n_busbar_per_sub"])): + bus_init = self._grid.get_bus_vn_kv() + orig_to_ls = np.array(self._grid._orig_to_ls) + bus_doubled = np.concatenate([bus_init for _ in range(self.n_busbar_per_sub)]) + self._grid.init_bus(bus_doubled, 0, 0) + for i in range(self.__nb_bus_before): + self._grid.deactivate_bus(i + self.__nb_bus_before) + new_orig_to_ls = np.concatenate([orig_to_ls + i * self.__nb_bus_before + for i in range(self.n_busbar_per_sub)] + ) + self._grid._orig_to_ls = new_orig_to_ls + def _load_grid_pypowsybl(self, path=None, filename=None): from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow import pypowsybl.network as pypow_net @@ -429,28 +463,15 @@ def _load_grid_pypowsybl(self, path=None, filename=None): full_path = self.make_complete_path(path, filename) except AttributeError as exc_: warnings.warn("Please upgrade your grid2op version") - import os - from grid2op.Exceptions import Grid2OpException - def make_complete_path(path, filename): - if path is None and filename is None: - raise Grid2OpException( - "You must provide at least one of path or file to load a powergrid." - ) - if path is None: - full_path = filename - elif filename is None: - full_path = path - else: - full_path = os.path.join(path, filename) - if not os.path.exists(full_path): - raise Grid2OpException('There is no powergrid at "{}"'.format(full_path)) - return full_path - full_path = make_complete_path(path, filename) + full_path = self._should_not_have_to_do_this(path, filename) + grid_tmp = pypow_net.load(full_path) gen_slack_id = None if "gen_slack_id" in loader_kwargs: gen_slack_id = int(loader_kwargs["gen_slack_id"]) self._grid = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True) + self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) + self._aux_pypowsybl_init_substations(loader_kwargs) self._aux_setup_right_after_grid_init() # mandatory for the backend @@ -482,7 +503,11 @@ def make_complete_path(path, filename): self.shunt_to_subid = np.array([el.bus_id for el in self._grid.get_shunts()], dtype=dt_int) else: # TODO get back the sub id from the grid_tmp.get_substations() - raise NotImplementedError("TODO") + raise NotImplementedError("Today the only supported behaviour is to consider the 'buses' of the powsybl grid " + "are the 'substation' of this backend. " + "This will change in the future, but in the meantime please add " + "'use_buses_for_sub' = True in the `loader_kwargs` when loading " + "a lightsim2grid grid.") # the names self.name_load = np.array([f"load_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_loads())]) @@ -496,25 +521,10 @@ def make_complete_path(path, filename): self._compute_pos_big_topo() self.__nb_powerline = len(self._grid.get_lines()) - self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) # init this self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) self.next_prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float) - - # now handle the legacy "make as if there are 2 busbars per substation" - # as it is done with grid2Op simulated environment - if "double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]: - bus_init = self._grid.get_bus_vn_kv() - orig_to_ls = np.array(self._grid._orig_to_ls) - bus_doubled = np.concatenate((bus_init, bus_init)) - self._grid.init_bus(bus_doubled, 0, 0) - for i in range(self.__nb_bus_before): - self._grid.deactivate_bus(i + self.__nb_bus_before) - new_orig_to_ls = np.concatenate((orig_to_ls, - orig_to_ls + self.__nb_bus_before) - ) - self._grid._orig_to_ls = new_orig_to_ls self.nb_bus_total = len(self._grid.get_bus_vn_kv()) # and now things needed by the backend (legacy) @@ -531,6 +541,7 @@ def make_complete_path(path, filename): self._aux_finish_setup_after_reading() def _aux_setup_right_after_grid_init(self): + self._grid.set_n_sub(self.__nb_bus_before) self._handle_turnedoff_pv() self.available_solvers = self._grid.available_solvers() @@ -549,12 +560,19 @@ def _aux_setup_right_after_grid_init(self): # check that the solver type provided is installed with lightsim2grid self._check_suitable_solver_type(self.__current_solver_type) self._grid.change_solver(self.__current_solver_type) + + # handle multiple busbar per substations + if hasattr(type(self), "can_handle_more_than_2_busbar"): + # grid2op version >= 1.10.0 then we use this + self._grid._max_nb_bus_per_sub = self.n_busbar_per_sub def _load_grid_pandapower(self, path=None, filename=None): + type(self.init_pp_backend).n_busbar_per_sub = self.n_busbar_per_sub 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._grid = init(self.init_pp_backend._grid) + self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus() self._aux_setup_right_after_grid_init() self.n_line = self.init_pp_backend.n_line @@ -653,7 +671,6 @@ def _aux_finish_setup_after_reading(self): # set up the "lightsim grid" accordingly cls = type(self) - self._grid.set_n_sub(self.__nb_bus_before) self._grid.set_load_pos_topo_vect(cls.load_pos_topo_vect) self._grid.set_gen_pos_topo_vect(cls.gen_pos_topo_vect) self._grid.set_line_or_pos_topo_vect(cls.line_or_pos_topo_vect[:self.__nb_powerline]) @@ -913,8 +930,18 @@ def runpf(self, is_dc=False): beg_postroc = time.perf_counter() if is_dc: self.comp_time += self._grid.get_dc_computation_time() + self.timer_gridmodel_xx_pf += self._grid.timer_last_dc_pf else: self.comp_time += self._grid.get_computation_time() + # NB get_computation_time returns "time_total_nr", which is + # defined in the powerflow algorithm and not on the linear solver. + # it takes into account everything needed to solve the powerflow + # once everything is passed to the solver. + # It does not take into account the time to format the data in the + # from the GridModel + + self.timer_gridmodel_xx_pf += self._grid.timer_last_ac_pf + # timer_gridmodel_xx_pf takes all the time within the gridmodel "ac_pf" self.V[:] = V (self.p_or[:self.__nb_powerline], @@ -1046,6 +1073,8 @@ def copy(self): #################### # res = copy.deepcopy(self) # super slow res = type(self).__new__(type(self)) + res.comp_time = self.comp_time + res.timer_gridmodel_xx_pf = self.timer_gridmodel_xx_pf # copy the regular attribute res.__has_storage = self.__has_storage @@ -1197,6 +1226,7 @@ def reset(self, grid_path, grid_filename=None): self._handle_turnedoff_pv() self.topo_vect[:] = self.__init_topo_vect self.comp_time = 0. + self.timer_gridmodel_xx_pf = 0. self._timer_postproc = 0. self._timer_preproc = 0. self._timer_solver = 0. diff --git a/lightsim2grid/tests/test_n_busbar_per_sub.py b/lightsim2grid/tests/test_n_busbar_per_sub.py new file mode 100644 index 00000000..aa51bb0a --- /dev/null +++ b/lightsim2grid/tests/test_n_busbar_per_sub.py @@ -0,0 +1,307 @@ +# Copyright (c) 2020, RTE (https://www.rte-france.com) +# See AUTHORS.txt +# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. +# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, +# you can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform. +import unittest +import warnings +import numpy as np + +import grid2op +from grid2op.Action import CompleteAction + +from lightsim2grid import LightSimBackend + +class TestLightSimBackend_3busbars(unittest.TestCase): + def get_nb_bus(self): + return 3 + + def get_env_nm(self): + return "educ_case14_storage" + + def get_backend_kwargs(self): + return dict() + + def setUp(self) -> None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.get_env_nm(), + backend=LightSimBackend(**self.get_backend_kwargs()), + action_class=CompleteAction, + test=True, + n_busbar=self.get_nb_bus(), + _add_to_name=type(self).__name__ + f'_{self.get_nb_bus()}') + self.list_loc_bus = [-1] + list(range(1, type(self.env).n_busbar_per_sub + 1)) + return super().setUp() + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def test_right_bus_made(self): + assert len(self.env.backend._grid.get_bus_vn_kv()) == self.get_nb_bus() * type(self.env).n_sub + assert (~np.array(self.env.backend._grid.get_bus_status())[type(self.env).n_sub:]).all() + assert (np.array(self.env.backend._grid.get_bus_status())[:type(self.env).n_sub]).all() + + @staticmethod + def _aux_find_sub(env, obj_col): + """find a sub with 4 elements, the type of elements and at least 2 lines""" + cls = type(env) + res = None + for sub_id in range(cls.n_sub): + this_sub_mask = cls.grid_objects_types[:,cls.SUB_COL] == sub_id + this_sub = cls.grid_objects_types[this_sub_mask, :] + if this_sub.shape[0] <= 3: + # not enough element + continue + if (this_sub[:, obj_col] == -1).all(): + # no load + continue + if ((this_sub[:, cls.LOR_COL] != -1) | (this_sub[:, cls.LEX_COL] != -1)).sum() <= 1: + # only 1 line + continue + el_id = this_sub[this_sub[:, obj_col] != -1, obj_col][0] + if (this_sub[:, cls.LOR_COL] != -1).any(): + line_or_id = this_sub[this_sub[:, cls.LOR_COL] != -1, cls.LOR_COL][0] + line_ex_id = None + else: + line_or_id = None + line_ex_id = this_sub[this_sub[:, cls.LEX_COL] != -1, cls.LEX_COL][0] + res = (sub_id, el_id, line_or_id, line_ex_id) + break + return res + + @staticmethod + def _aux_find_sub_shunt(env): + """find a sub with 4 elements, the type of elements and at least 2 lines""" + cls = type(env) + res = None + for el_id in range(cls.n_shunt): + sub_id = cls.shunt_to_subid[el_id] + this_sub_mask = cls.grid_objects_types[:,cls.SUB_COL] == sub_id + this_sub = cls.grid_objects_types[this_sub_mask, :] + if this_sub.shape[0] <= 3: + # not enough element + continue + if ((this_sub[:, cls.LOR_COL] != -1) | (this_sub[:, cls.LEX_COL] != -1)).sum() <= 1: + # only 1 line + continue + if (this_sub[:, cls.LOR_COL] != -1).any(): + line_or_id = this_sub[this_sub[:, cls.LOR_COL] != -1, cls.LOR_COL][0] + line_ex_id = None + else: + line_or_id = None + line_ex_id = this_sub[this_sub[:, cls.LEX_COL] != -1, cls.LEX_COL][0] + res = (sub_id, el_id, line_or_id, line_ex_id) + break + return res + + def test_move_load(self): + cls = type(self.env) + res = self._aux_find_sub(self.env, cls.LOA_COL) + if res is None: + raise RuntimeError(f"Cannot carry the test 'test_move_load' as " + "there are no suitable subastation in your grid.") + (sub_id, el_id, line_or_id, line_ex_id) = res + for new_bus in self.list_loc_bus: + if line_or_id is not None: + act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}}) + else: + act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = sub_id + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_loads()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}" + if line_or_id is not None: + assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus + else: + assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_loads()[el_id].connected + if line_or_id is not None: + assert not self.env.backend._grid.get_lines()[line_or_id].connected + else: + assert not self.env.backend._grid.get_lines()[line_ex_id].connected + topo_vect = 1 * self.env.backend.get_topo_vect() + assert topo_vect[cls.load_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.load_pos_topo_vect[el_id]]} vs {new_bus}" + + def test_move_gen(self): + cls = type(self.env) + res = self._aux_find_sub(self.env, cls.GEN_COL) + if res is None: + raise RuntimeError(f"Cannot carry the test 'test_move_gen' as " + "there are no suitable subastation in your grid.") + (sub_id, el_id, line_or_id, line_ex_id) = res + for new_bus in self.list_loc_bus: + if line_or_id is not None: + act = self.env.action_space({"set_bus": {"generators_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}}) + else: + act = self.env.action_space({"set_bus": {"generators_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = sub_id + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_generators()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}" + if line_or_id is not None: + assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus + else: + assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_generators()[el_id].connected + if line_or_id is not None: + assert not self.env.backend._grid.get_lines()[line_or_id].connected + else: + assert not self.env.backend._grid.get_lines()[line_ex_id].connected + topo_vect = 1 * self.env.backend.get_topo_vect() + assert topo_vect[cls.gen_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.gen_pos_topo_vect[el_id]]} vs {new_bus}" + + def test_move_storage(self): + cls = type(self.env) + res = self._aux_find_sub(self.env, cls.STORAGE_COL) + if res is None: + raise RuntimeError(f"Cannot carry the test 'test_move_storage' as " + "there are no suitable subastation in your grid.") + (sub_id, el_id, line_or_id, line_ex_id) = res + for new_bus in self.list_loc_bus: + if line_or_id is not None: + act = self.env.action_space({"set_bus": {"storages_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}}) + else: + act = self.env.action_space({"set_bus": {"storages_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = sub_id + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_storages()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}" + if line_or_id is not None: + assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus + else: + assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_storages()[el_id].connected + if line_or_id is not None: + assert not self.env.backend._grid.get_lines()[line_or_id].connected + else: + assert not self.env.backend._grid.get_lines()[line_ex_id].connected + topo_vect = 1 * self.env.backend.get_topo_vect() + assert topo_vect[cls.storage_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.storage_pos_topo_vect[el_id]]} vs {new_bus}" + + def test_move_line_or(self): + cls = type(self.env) + line_id = 0 + for new_bus in self.list_loc_bus: + act = self.env.action_space({"set_bus": {"lines_or_id": [(line_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = cls.line_or_to_subid[line_id] + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_lines()[line_id].bus_or_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_lines()[line_id].connected + topo_vect = 1 * self.env.backend.get_topo_vect() + assert topo_vect[cls.line_or_pos_topo_vect[line_id]] == new_bus, f"{topo_vect[cls.line_or_pos_topo_vect[line_id]]} vs {new_bus}" + + def test_move_line_ex(self): + cls = type(self.env) + line_id = 0 + for new_bus in self.list_loc_bus: + act = self.env.action_space({"set_bus": {"lines_ex_id": [(line_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = cls.line_ex_to_subid[line_id] + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_lines()[line_id].bus_ex_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_lines()[line_id].connected + topo_vect = 1 * self.env.backend.get_topo_vect() + assert topo_vect[cls.line_ex_pos_topo_vect[line_id]] == new_bus, f"{topo_vect[cls.line_ex_pos_topo_vect[line_id]]} vs {new_bus}" + + def test_move_shunt(self): + cls = type(self.env) + res = self._aux_find_sub_shunt(self.env) + if res is None: + raise RuntimeError(f"Cannot carry the test 'test_move_load' as " + "there are no suitable subastation in your grid.") + (sub_id, el_id, line_or_id, line_ex_id) = res + for new_bus in self.list_loc_bus: + if line_or_id is not None: + act = self.env.action_space({"shunt": {"set_bus": [(el_id, new_bus)]}, "set_bus": {"lines_or_id": [(line_or_id, new_bus)]}}) + else: + act = self.env.action_space({"shunt": {"set_bus": [(el_id, new_bus)]}, "set_bus": {"lines_ex_id": [(line_ex_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + global_bus = sub_id + (new_bus -1) * cls.n_sub + if new_bus >= 1: + assert self.env.backend._grid.get_shunts()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}" + if line_or_id is not None: + assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus + else: + assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus + assert self.env.backend._grid.get_bus_status()[global_bus] + else: + assert not self.env.backend._grid.get_shunts()[el_id].connected + if line_or_id is not None: + assert not self.env.backend._grid.get_lines()[line_or_id].connected + else: + assert not self.env.backend._grid.get_lines()[line_ex_id].connected + + def test_check_kirchoff(self): + cls = type(self.env) + res = self._aux_find_sub(self.env, cls.LOA_COL) + if res is None: + raise RuntimeError("Cannot carry the test 'test_move_load' as " + "there are no suitable subastation in your grid.") + (sub_id, el_id, line_or_id, line_ex_id) = res + for new_bus in self.list_loc_bus: + if new_bus <= -1: + continue + if line_or_id is not None: + act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}}) + else: + act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}}) + bk_act = self.env._backend_action_class() + bk_act += act + self.env.backend.apply_action(bk_act) + conv, maybe_exc = self.env.backend.runpf() + assert conv, f"error : {maybe_exc}" + p_subs, q_subs, p_bus, q_bus, diff_v_bus = self.env.backend.check_kirchoff() + # assert laws are met + assert np.abs(p_subs).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(p_subs).max():.2e}" + assert np.abs(q_subs).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(q_subs).max():.2e}" + assert np.abs(p_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(p_bus).max():.2e}" + assert np.abs(q_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(q_bus).max():.2e}" + assert np.abs(diff_v_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(diff_v_bus).max():.2e}" + + +class TestLightSimBackend_1busbar(TestLightSimBackend_3busbars): + def get_nb_bus(self): + return 1 + + +class TestLightSimBackend_3busbars_iidm(TestLightSimBackend_3busbars): + def get_env_nm(self): + return "./case_14_storage_iidm" + + def get_backend_kwargs(self): + return dict(loader_method="pypowsybl", + loader_kwargs={"use_buses_for_sub": True, + "double_bus_per_sub": True, + "gen_slack_id": 5} + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index ec9a8976..ae2a8f12 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -25,7 +25,6 @@ import numpy as np import grid2op from grid2op.Action import CompleteAction -from grid2op.Parameters import Parameters from lightsim2grid import LightSimBackend from lightsim2grid.solver import SolverType diff --git a/src/GridModel.cpp b/src/GridModel.cpp index ba833363..67b48534 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -330,6 +330,9 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc) Ybus_dc_ = Eigen::SparseMatrix(); } + timer_last_ac_pf_= 0.; + timer_last_dc_pf_ = 0.; + acSbus_ = CplxVect(); dcSbus_ = CplxVect(); bus_pv_ = Eigen::VectorXi(); @@ -356,6 +359,7 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, int max_iter, real_type tol) { + auto timer = CustTimer(); const int nb_bus = static_cast(bus_vn_kv_.size()); if(Vinit.size() != nb_bus){ std::ostringstream exc_; @@ -399,6 +403,7 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit, // std::cout << "\tbefore process_results" << std::endl; process_results(conv, res, Vinit, true, id_me_to_ac_solver_); + timer_last_ac_pf_ = timer.duration(); // return the vector of complex voltage at each bus return res; }; @@ -882,6 +887,8 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, // the idea is to "mess" with the Sbus beforehand to split the "losses" // ie fake the action of generators to adjust Sbus such that sum(Sbus) = 0 // and the slack contribution factors are met. + auto timer = CustTimer(); + const int nb_bus = static_cast(bus_vn_kv_.size()); if(Vinit.size() != nb_bus){ //TODO DEBUG MODE: @@ -929,7 +936,7 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit, // std::cout << "\tprocess_results (dc) \n"; // store results (fase -> because I am in dc mode) process_results(conv, res, Vinit, is_ac, id_me_to_dc_solver_); - // std::cout << "\tafter compute_pf\n"; + timer_last_dc_pf_ = timer.duration(); return res; } diff --git a/src/GridModel.h b/src/GridModel.h index 7a0a104e..4fefb2fb 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -92,6 +92,8 @@ class GridModel : public GenericContainer > StateRes; GridModel(): + timer_last_ac_pf_(0.), + timer_last_dc_pf_(0.), solver_control_(), compute_results_(true), init_vm_pu_(1.04), @@ -113,6 +115,8 @@ class GridModel : public GenericContainer void set_orig_to_ls(const IntVect & orig_to_ls); // set both _orig_to_ls and _ls_to_orig const IntVect & get_ls_to_orig(void) const {return _ls_to_orig;} const IntVect & get_orig_to_ls(void) const {return _orig_to_ls;} + double timer_last_ac_pf() const {return timer_last_ac_pf_;} + double timer_last_dc_pf() const {return timer_last_dc_pf_;} Eigen::Index total_bus() const {return bus_vn_kv_.size();} const std::vector & id_me_to_ac_solver() const {return id_me_to_ac_solver_;} @@ -613,8 +617,7 @@ class GridModel : public GenericContainer } void set_max_nb_bus_per_sub(int max_nb_bus_per_sub) { - max_nb_bus_per_sub_ = max_nb_bus_per_sub; - if(bus_vn_kv_.size() != n_sub_ * max_nb_bus_per_sub_){ + if(bus_vn_kv_.size() != n_sub_ * max_nb_bus_per_sub){ std::ostringstream exc_; exc_ << "GridModel::set_max_nb_bus_per_sub: "; exc_ << "your model counts "; @@ -624,7 +627,9 @@ class GridModel : public GenericContainer exc_ << "substations / buses per substations with `set_n_sub` / `set_max_nb_bus_per_sub`"; throw std::runtime_error(exc_.str()); } + max_nb_bus_per_sub_ = max_nb_bus_per_sub; } + int get_max_nb_bus_per_sub() const { return max_nb_bus_per_sub_;} void fillSbus_other(CplxVect & res, bool ac, const std::vector& id_me_to_solver){ fillSbus_me(res, ac, id_me_to_solver); @@ -746,8 +751,8 @@ class GridModel : public GenericContainer int new_bus = new_values(el_pos); if(new_bus > 0){ // new bus is a real bus, so i need to make sure to have it turned on, and then change the bus - int init_bus_me = vect_subid(el_id); - int new_bus_backend = new_bus == 1 ? init_bus_me : init_bus_me + n_sub_ * (max_nb_bus_per_sub_ - 1); + int sub_id = vect_subid(el_id); + int new_bus_backend = sub_id + (new_bus - 1) * n_sub_; bus_status_[new_bus_backend] = true; (this->*fun_react)(el_id); // eg reactivate_load(load_id); (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); @@ -774,7 +779,8 @@ class GridModel : public GenericContainer IntVect _orig_to_ls; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) // member of the grid - // static const int _deactivated_bus_id; + double timer_last_ac_pf_; + double timer_last_dc_pf_; // bool need_reset_solver_; // some matrices change size, needs to be computed // bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it diff --git a/src/main.cpp b/src/main.cpp index a1c9a6bc..dd001e15 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -613,6 +613,12 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("copy", &GridModel::copy) .def_property("_ls_to_orig", &GridModel::get_ls_to_orig, &GridModel::set_ls_to_orig, "remember the conversion from bus index in lightsim2grid to bus index in original file format (*eg* pandapower of pypowsybl).") .def_property("_orig_to_ls", &GridModel::get_orig_to_ls, &GridModel::set_orig_to_ls, "remember the conversion from bus index in original file format (*eg* pandapower of pypowsybl) to bus index in lightsim2grid.") + .def_property("_max_nb_bus_per_sub", + &GridModel::get_max_nb_bus_per_sub, + &GridModel::set_max_nb_bus_per_sub, + "do not modify it after loading !") + .def_property_readonly("timer_last_ac_pf", &GridModel::timer_last_ac_pf, "TODO") + .def_property_readonly("timer_last_dc_pf", &GridModel::timer_last_dc_pf, "TODO") // pickle .def(py::pickle( From b465c2b26f2ddfe2011990ca11990d1b554ec723 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 7 Mar 2024 17:58:41 +0100 Subject: [PATCH 48/66] fixing bug in the benchmarking of grid size, improve solver control --- .circleci/config.yml | 15 ++- .readthedocs.yml | 11 +- CHANGELOG.rst | 1 + benchmarks/benchmark_grid_size.py | 113 +++++++++++++----- lightsim2grid/lightSimBackend.py | 2 + src/element_container/GeneratorContainer.cpp | 30 +++-- src/element_container/LoadContainer.cpp | 24 ++-- src/element_container/SGenContainer.cpp | 40 ++++--- src/element_container/ShuntContainer.cpp | 19 ++- src/powerflow_algorithm/BaseDCAlgo.tpp | 5 + src/powerflow_algorithm/BaseFDPFAlgo.tpp | 51 +++++--- src/powerflow_algorithm/BaseNRAlgo.tpp | 28 +++-- .../BaseNRSingleSlackAlgo.tpp | 45 ++++--- 13 files changed, 259 insertions(+), 125 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6973ecc9..4ea5c7cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -46,6 +46,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -64,6 +65,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -82,6 +84,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -100,6 +103,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -118,6 +122,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip python3-virtualenv -y + - run: python3 -m pip install --upgrade pip setuptools # - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -148,6 +153,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -165,6 +171,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip git -y + - run: python3 -m pip install --upgrade pip setuptools - run: command: | git submodule init @@ -184,6 +191,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip git -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -202,6 +210,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip git -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -220,6 +229,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip git -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -238,6 +248,7 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip git -y + - run: python3 -m pip install --upgrade pip setuptools - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -268,9 +279,7 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - # - run: - # name: "Install Python" - # command: choco install python --version=3.9 # use python 3.9 for windows test + - run: python3 -m pip install --upgrade pip setuptools - run: py -m pip install virtualenv - run: py -m virtualenv venv_test - run: diff --git a/.readthedocs.yml b/.readthedocs.yml index 97df3301..ab6ff9c4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,4 +1,4 @@ -version: 2 +version: "2" submodules: include: @@ -6,8 +6,15 @@ submodules: - eigen recursive: true +build: + os: "ubuntu-22.04" + tools: + python: "3.10" + +sphinx: + configuration: docs/conf.py + python: - version: 3.8 install: - method: pip path: . diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 666a2a4c..2fa05e16 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -39,6 +39,7 @@ Change Log - [FIXED] a bug where copying a lightsim2grid `GridModel` did not fully copy it - [FIXED] a bug in the "topo_vect" comprehension cpp side (sometimes some buses might not be activated / deactivated correctly) +- [FIXED] read the docs was broken - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. diff --git a/benchmarks/benchmark_grid_size.py b/benchmarks/benchmark_grid_size.py index 09d627eb..a2e26683 100644 --- a/benchmarks/benchmark_grid_size.py +++ b/benchmarks/benchmark_grid_size.py @@ -7,12 +7,15 @@ # This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform. import warnings +import copy import pandapower as pp -import numpy as np +import numpy as np +import hashlib from scipy.interpolate import interp1d import matplotlib.pyplot as plt from grid2op import make, Parameters from grid2op.Chronics import FromNPY +from grid2op.Backend import PandaPowerBackend from lightsim2grid import LightSimBackend, TimeSerie try: from lightsim2grid import ContingencyAnalysis @@ -33,6 +36,8 @@ VERBOSE = False MAKE_PLOT = False +WITH_PP = False +DEBUG = False case_names = [ "case14.json", @@ -69,7 +74,28 @@ def make_grid2op_env(pp_case, casse_name, load_p, load_q, gen_p, sgen_p): ) return env_lightsim -def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): + +def make_grid2op_env_pp(pp_case, casse_name, load_p, load_q, gen_p, sgen_p): + param = Parameters.Parameters() + param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True}) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env_pp = make("blank", + param=param, test=True, + backend=PandaPowerBackend(lightsim2grid=False), + chronics_class=FromNPY, + data_feeding_kwargs={"load_p": load_p, + "load_q": load_q, + "prod_p": gen_p + }, + grid_path=case_name, + _add_to_name=f"{case_name}", + ) + return env_pp + + +def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init, prng): # scale loads # use some French time series data for loads @@ -137,28 +163,31 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): vals = np.array(vals) * coeffs["month"]["oct"] * coeffs["day"]["mon"] x_interp = 12 * np.arange(len(vals)) coeffs = interp1d(x=x_interp, y=vals, kind="cubic") - all_vals = coeffs(x_final) + all_vals = coeffs(x_final).reshape(-1, 1) + if DEBUG: + all_vals[:] = 1 # compute the "smooth" loads matrix - load_p_smooth = all_vals.reshape(-1, 1) * load_p_init.reshape(1, -1) - load_q_smooth = all_vals.reshape(-1, 1) * load_q_init.reshape(1, -1) + load_p_smooth = all_vals * load_p_init.reshape(1, -1) + load_q_smooth = all_vals * load_q_init.reshape(1, -1) # add a bit of noise to it to get the "final" loads matrix - load_p = load_p_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_p_smooth.shape) - load_q = load_q_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_q_smooth.shape) - + load_p = load_p_smooth * prng.lognormal(mean=0., sigma=0.003, size=load_p_smooth.shape) + load_q = load_q_smooth * prng.lognormal(mean=0., sigma=0.003, size=load_q_smooth.shape) + if DEBUG: + load_p[:] = load_p_smooth + load_q[:] = load_q_smooth + # scale generators accordingly gen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * gen_p_init.reshape(1, -1) sgen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * sgen_p_init.reshape(1, -1) - return load_p, load_q, gen_p, sgen_p if __name__ == "__main__": - np.random.seed(42) - + prng = np.random.default_rng(42) case_names_displayed = [get_env_name_displayed(el) for el in case_names] - g2op_times = [] + solver_preproc_solver_time = [] g2op_speeds = [] g2op_sizes = [] g2op_step_time = [] @@ -197,8 +226,14 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): res_unit = "ms" # simulate the data - load_p, load_q, gen_p, sgen_p = get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init) - + load_p, load_q, gen_p, sgen_p = get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init, prng) + if DEBUG: + hash_fun = hashlib.blake2b(digest_size=16) + hash_fun.update(load_p.tobytes()) + hash_fun.update(load_q.tobytes()) + hash_fun.update(gen_p.tobytes()) + hash_fun.update(sgen_p.tobytes()) + print(hash_fun.hexdigest()) # create the grid2op env nb_ts = gen_p.shape[0] # add slack ! @@ -206,15 +241,33 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): if "res_ext_grid" in case: slack_gens += np.tile(case.res_ext_grid["p_mw"].values.reshape(1,-1), (nb_ts, 1)) gen_p_g2op = np.concatenate((gen_p, slack_gens), axis=1) - # get the env + # get the env + if WITH_PP: + env_pp = make_grid2op_env_pp(case, + case_name, + load_p, + load_q, + gen_p_g2op, + sgen_p) + _ = env_pp.reset() + done = False + nb_step_pp = 0 + changed_sgen = case.sgen["in_service"].values + while not done: + # hack for static gen... + env_pp.backend._grid.sgen["p_mw"] = sgen_p[nb_step_pp, :] + obs, reward, done, info = env_pp.step(env_pp.action_space()) + nb_step_pp += 1 + if nb_step_pp != nb_ts: + warnings.warn("Divergence even with pandapower !") + print("Pandapower stops, lightsim starts") + env_lightsim = make_grid2op_env(case, case_name, load_p, load_q, gen_p_g2op, sgen_p) - - # Perform the computation using grid2op _ = env_lightsim.reset() done = False @@ -222,16 +275,20 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): changed_sgen = case.sgen["in_service"].values while not done: # hack for static gen... - env_lightsim.backend._grid.update_sgens_p(changed_sgen, - sgen_p[nb_step, changed_sgen].astype(np.float32)) + changed_sgen = copy.deepcopy(case.sgen["in_service"].values) + this_sgen = sgen_p[nb_step, :].astype(np.float32) + # this_sgen = sgen_p_init[changed_sgen].astype(np.float32) + env_lightsim.backend._grid.update_sgens_p(changed_sgen, this_sgen) obs, reward, done, info = env_lightsim.step(env_lightsim.action_space()) nb_step += 1 # NB lightsim2grid does not handle "static gen" because I cannot set "p" in gen in grid2op # so results will vary between TimeSeries and grid2op ! - + # env_lightsim.backend._grid.tell_solver_need_reset() + # env_lightsim.backend._grid.dc_pf(env_lightsim.backend.V, 1, 1e-7) + # env_lightsim.backend._grid.get_bus_status() if nb_step != nb_ts: warnings.warn(f"only able to make {nb_step} (out of {nb_ts}) for {case_name} in grid2op. Results will not be availabe for grid2op step") - g2op_times.append(None) + solver_preproc_solver_time.append(None) g2op_speeds.append(None) g2op_step_time.append(None) ls_solver_time.append(None) @@ -239,8 +296,8 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): g2op_sizes.append(env_lightsim.n_sub) else: total_time = env_lightsim.backend._timer_preproc + env_lightsim.backend._timer_solver # + env_lightsim.backend._timer_postproc - total_time = env_lightsim._time_step - g2op_times.append(total_time) + # total_time = env_lightsim._time_step + solver_preproc_solver_time.append(total_time) g2op_speeds.append(1.0 * nb_step / total_time) g2op_step_time.append(1.0 * env_lightsim._time_step / nb_step) ls_solver_time.append(env_lightsim.backend.comp_time) @@ -262,7 +319,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): env_lightsim.backend.tol) time_serie._TimeSerie__computed = True a_or = time_serie.compute_A() - assert status, f"some powerflow diverge for {case_name}: {computer.nb_solved()} " + assert status, f"some powerflow diverge for Time Series for {case_name}: {computer.nb_solved()} " if VERBOSE: # print detailed results if needed @@ -311,9 +368,9 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): for i, nm_ in enumerate(case_names_displayed): tab_g2op.append((nm_, ts_sizes[i], + 1000. * g2op_step_time[i] if g2op_step_time[i] else None, 1000. / g2op_speeds[i] if g2op_speeds[i] else None, g2op_speeds[i], - 1000. * g2op_step_time[i] if g2op_step_time[i] else None, 1000. * ls_gridmodel_time[i] / nb_step if ls_gridmodel_time[i] else None, 1000. * ls_solver_time[i] / nb_step if ls_solver_time[i] else None, )) @@ -321,9 +378,9 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): res_use_with_grid2op_2 = tabulate(tab_g2op, headers=["grid", "size (nb bus)", - "step time (ms / pf)", - "speed (pf / s)", "avg step duration (ms)", + "time [DC + AC] (ms / pf)", + "speed (pf / s)", "time in 'gridmodel' (ms / pf)", "time in 'pf algo' (ms / pf)", ], @@ -362,7 +419,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init): if MAKE_PLOT: # make the plot summarizing all results - plt.plot(g2op_sizes, g2op_times, linestyle='solid', marker='+', markersize=8) + plt.plot(g2op_sizes, solver_preproc_solver_time, linestyle='solid', marker='+', markersize=8) plt.xlabel("Size (number of substation)") plt.ylabel("Time taken (s)") plt.title(f"Time to compute {g2op_sizes[0]} powerflows using Grid2Op.step (dc pf [init] + ac pf)") diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index bbecd58d..f0d57c55 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -415,6 +415,7 @@ def load_grid(self, path=None, filename=None): self._load_grid_pypowsybl(path, filename) else: raise BackendError(f"Impossible to initialize the backend with '{self._loader_method}'") + self._grid.tell_solver_need_reset() def _should_not_have_to_do_this(self, path=None, filename=None): # included in grid2op now ! @@ -1230,3 +1231,4 @@ def reset(self, grid_path, grid_filename=None): self._timer_postproc = 0. self._timer_preproc = 0. self._timer_solver = 0. + self._grid.tell_solver_need_reset() diff --git a/src/element_container/GeneratorContainer.cpp b/src/element_container/GeneratorContainer.cpp index e45767b9..b580c40e 100644 --- a/src/element_container/GeneratorContainer.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -167,9 +167,9 @@ void GeneratorContainer::fillSbus(CplxVect & Sbus, const std::vector & id_g } void GeneratorContainer::fillpv(std::vector & bus_pv, - std::vector & has_bus_been_added, - const Eigen::VectorXi & slack_bus_id_solver, - const std::vector & id_grid_to_solver) const + std::vector & has_bus_been_added, + const Eigen::VectorXi & slack_bus_id_solver, + const std::vector & id_grid_to_solver) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; @@ -198,12 +198,12 @@ void GeneratorContainer::fillpv(std::vector & bus_pv, } void GeneratorContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac) { const int nb_gen = nb(); v_kv_from_vpu(Va, Vm, status_, nb_gen, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); @@ -257,8 +257,10 @@ void GeneratorContainer::change_p(int gen_id, real_type new_p, SolverControl & s solver_control.tell_pv_changed(); } } - if (p_mw_(gen_id) != new_p) solver_control.tell_recompute_sbus(); - p_mw_(gen_id) = new_p; + if (p_mw_(gen_id) != new_p){ + solver_control.tell_recompute_sbus(); + p_mw_(gen_id) = new_p; + } } void GeneratorContainer::change_q(int gen_id, real_type new_q, SolverControl & solver_control) @@ -275,8 +277,10 @@ void GeneratorContainer::change_q(int gen_id, real_type new_q, SolverControl & s } // TODO DEBUG MODE : raise an error if generator is regulating voltage, maybe ? // this would have not effect - if (q_mvar_(gen_id) != new_q) solver_control.tell_recompute_sbus(); - q_mvar_(gen_id) = new_q; + if (q_mvar_(gen_id) != new_q){ + solver_control.tell_recompute_sbus(); + q_mvar_(gen_id) = new_q; + } } void GeneratorContainer::change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control) diff --git a/src/element_container/LoadContainer.cpp b/src/element_container/LoadContainer.cpp index 4a4de716..d7eb561b 100644 --- a/src/element_container/LoadContainer.cpp +++ b/src/element_container/LoadContainer.cpp @@ -71,12 +71,12 @@ void LoadContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_t } void LoadContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac) { int nb_load = nb(); v_kv_from_vpu(Va, Vm, status_, nb_load, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); @@ -102,8 +102,10 @@ void LoadContainer::change_p(int load_id, real_type new_p, SolverControl & solve exc_ << ")"; throw std::runtime_error(exc_.str()); } - if (p_mw_(load_id) != new_p) solver_control.tell_recompute_sbus(); - p_mw_(load_id) = new_p; + if (p_mw_(load_id) != new_p) { + solver_control.tell_recompute_sbus(); + p_mw_(load_id) = new_p; + } } void LoadContainer::change_q(int load_id, real_type new_q, SolverControl & solver_control) @@ -117,8 +119,10 @@ void LoadContainer::change_q(int load_id, real_type new_q, SolverControl & solve exc_ << ")"; throw std::runtime_error(exc_.str()); } - if (q_mvar_(load_id) != new_q) solver_control.tell_recompute_sbus(); - q_mvar_(load_id) = new_q; + if (q_mvar_(load_id) != new_q) { + solver_control.tell_recompute_sbus(); + q_mvar_(load_id) = new_q; + } } void LoadContainer::reconnect_connected_buses(std::vector & bus_status) const { diff --git a/src/element_container/SGenContainer.cpp b/src/element_container/SGenContainer.cpp index 48c1baa2..63007272 100644 --- a/src/element_container/SGenContainer.cpp +++ b/src/element_container/SGenContainer.cpp @@ -7,14 +7,15 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. #include "SGenContainer.h" +#include void SGenContainer::init(const RealVect & sgen_p, - const RealVect & sgen_q, - const RealVect & sgen_pmin, - const RealVect & sgen_pmax, - const RealVect & sgen_qmin, - const RealVect & sgen_qmax, - const Eigen::VectorXi & sgen_bus_id) + const RealVect & sgen_q, + const RealVect & sgen_pmin, + const RealVect & sgen_pmax, + const RealVect & sgen_qmin, + const RealVect & sgen_qmax, + const Eigen::VectorXi & sgen_bus_id) { int size = static_cast(sgen_p.size()); GenericContainer::check_size(sgen_p, size, "sgen_p"); @@ -100,19 +101,18 @@ void SGenContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_t exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); } - tmp = static_cast(p_mw_(sgen_id)); - tmp += my_i * q_mvar_(sgen_id); + tmp = {p_mw_(sgen_id), q_mvar_(sgen_id)}; Sbus.coeffRef(bus_id_solver) += tmp; } } void SGenContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac) { const int nb_sgen = nb(); v_kv_from_vpu(Va, Vm, status_, nb_sgen, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); @@ -139,8 +139,10 @@ void SGenContainer::change_p(int sgen_id, real_type new_p, SolverControl & solve exc_ << ")"; throw std::runtime_error(exc_.str()); } - if (p_mw_(sgen_id) != new_p) solver_control.tell_recompute_sbus(); - p_mw_(sgen_id) = new_p; + if (p_mw_(sgen_id) != new_p){ + solver_control.tell_recompute_sbus(); + p_mw_(sgen_id) = new_p; + } } void SGenContainer::change_q(int sgen_id, real_type new_q, SolverControl & solver_control) @@ -154,8 +156,10 @@ void SGenContainer::change_q(int sgen_id, real_type new_q, SolverControl & solve exc_ << ")"; throw std::runtime_error(exc_.str()); } - if (q_mvar_(sgen_id) != new_q) solver_control.tell_recompute_sbus(); - q_mvar_(sgen_id) = new_q; + if (q_mvar_(sgen_id) != new_q){ + solver_control.tell_recompute_sbus(); + q_mvar_(sgen_id) = new_q; + } } void SGenContainer::reconnect_connected_buses(std::vector & bus_status) const { diff --git a/src/element_container/ShuntContainer.cpp b/src/element_container/ShuntContainer.cpp index 58178348..76a6129e 100644 --- a/src/element_container/ShuntContainer.cpp +++ b/src/element_container/ShuntContainer.cpp @@ -48,9 +48,9 @@ void ShuntContainer::set_state(ShuntContainer::StateRes & my_state ) } void ShuntContainer::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { if(!ac) return; // no shunt in DC @@ -79,10 +79,10 @@ void ShuntContainer::fillYbus(std::vector > & res, } void ShuntContainer::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { const Eigen::Index nb_shunt = static_cast(q_mvar_.size()); real_type tmp; @@ -174,9 +174,8 @@ void ShuntContainer::change_p(int shunt_id, real_type new_p, SolverControl & sol if(p_mw_(shunt_id) != new_p){ solver_control.tell_recompute_ybus(); solver_control.tell_recompute_sbus(); // in dc mode sbus is modified + p_mw_(shunt_id) = new_p; } - p_mw_(shunt_id) = new_p; - } void ShuntContainer::change_q(int shunt_id, real_type new_q, SolverControl & solver_control) @@ -185,8 +184,8 @@ void ShuntContainer::change_q(int shunt_id, real_type new_q, SolverControl & sol if(!my_status) throw std::runtime_error("Impossible to change the reactive value of a disconnected shunt"); if(q_mvar_(shunt_id) != new_q){ solver_control.tell_recompute_ybus(); + q_mvar_(shunt_id) = new_q; } - q_mvar_(shunt_id) = new_q; } void ShuntContainer::reconnect_connected_buses(std::vector & bus_status) const { diff --git a/src/powerflow_algorithm/BaseDCAlgo.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp index bb594667..7de831f2 100644 --- a/src/powerflow_algorithm/BaseDCAlgo.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -27,6 +27,7 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & // and for the slack bus both the magnitude and the angle are used. if(!is_linear_solver_valid()) { + // std::cout << "!is_linear_solver_valid()\n"; return false; } BaseAlgo::reset_timer(); @@ -104,6 +105,7 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; + // std::cout << "_linear_solver.initialize\n"; return false; } need_factorize_ = false; @@ -116,6 +118,7 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & if(error != ErrorType::NoError){ err_ = error; timer_total_nr_ += timer.duration(); + // std::cout << "_linear_solver.solve\n"; return false; } @@ -128,8 +131,10 @@ bool BaseDCAlgo::compute_pf(const Eigen::SparseMatrix & Vm_ = RealVect(); Va_ = RealVect(); timer_total_nr_ += timer.duration(); + // std::cout << "_linear_solver.allFinite" << Va_dc_without_slack.array().allFinite() <<", " << Va_dc_without_slack.lpNorm() <<"\n"; return false; } + // std::cout << "\t " << Va_dc_without_slack.lpNorm() << "\n"; #ifdef __COUT_TIMES std::cout << "\t dc solve: " << 1000. * timer_solve.duration() << "ms" << std::endl; diff --git a/src/powerflow_algorithm/BaseFDPFAlgo.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp index 28a7c1b5..1c063b37 100644 --- a/src/powerflow_algorithm/BaseFDPFAlgo.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -67,22 +67,35 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix grid_Bp; - Eigen::SparseMatrix grid_Bpp; - fillBp_Bpp(grid_Bp, grid_Bpp); - - // fill the solver matrices Bp and Bpp : - // Bp_ = Bp[array([pvpq]).T, pvpq].tocsc() # splu requires a CSC matrix - // Bpp_ = Bpp[array([pq]).T, pq].tocsc() - const auto n_pvpq = pvpq.size(); - std::vector pvpq_inv(V.size(), -1); - for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id; - std::vector pq_inv(V.size(), -1); - for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; - fill_sparse_matrices(grid_Bp, grid_Bpp, pvpq_inv, pq_inv, n_pvpq, n_pq); + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.need_recompute_ybus() || + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ) + { + // extract Bp and Bpp for the whole grid + Eigen::SparseMatrix grid_Bp; + Eigen::SparseMatrix grid_Bpp; + fillBp_Bpp(grid_Bp, grid_Bpp); + + // fill the solver matrices Bp_ and Bpp_ + // Bp_ = Bp[array([pvpq]).T, pvpq].tocsc() + // Bpp_ = Bpp[array([pq]).T, pq].tocsc() + std::vector pvpq_inv(V.size(), -1); + for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id; + std::vector pq_inv(V.size(), -1); + for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; + fill_sparse_matrices(grid_Bp, grid_Bpp, pvpq_inv, pq_inv, n_pvpq, n_pq); + } V_ = V; // V = V0 Vm_ = V_.array().abs(); // Vm = abs(V) @@ -160,11 +173,11 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix void BaseFDPFAlgo::fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp, - const Eigen::SparseMatrix & grid_Bpp, - const std::vector & pvpq_inv, - const std::vector & pq_inv, - Eigen::Index n_pvpq, - Eigen::Index n_pq) + const Eigen::SparseMatrix & grid_Bpp, + const std::vector & pvpq_inv, + const std::vector & pq_inv, + Eigen::Index n_pvpq, + Eigen::Index n_pq) { /** Init Bp_ and Bpp_ such that: diff --git a/src/powerflow_algorithm/BaseNRAlgo.tpp b/src/powerflow_algorithm/BaseNRAlgo.tpp index 0977d7d6..33ae20e4 100644 --- a/src/powerflow_algorithm/BaseNRAlgo.tpp +++ b/src/powerflow_algorithm/BaseNRAlgo.tpp @@ -88,13 +88,27 @@ bool BaseNRAlgo::compute_pf(const Eigen::SparseMatrix & bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl; // std::cout << "slack_absorbed " << slack_absorbed << std::endl; - value_map_.clear(); // TODO smarter solver: only needed if ybus has changed - // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed - // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed - dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed - dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed - // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed - // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.need_recompute_ybus() || + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ) + { + value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + + } while ((!converged) & (nr_iter_ < max_iter)){ nr_iter_++; fill_jacobian_matrix(Ybus, V_, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); diff --git a/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp index 8c0a4299..612f963b 100644 --- a/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp +++ b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp @@ -11,15 +11,15 @@ template bool BaseNRSingleSlackAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, // unused here - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, // unused here + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -75,12 +75,27 @@ bool BaseNRSingleSlackAlgo::compute_pf(const Eigen::SparseMatrix::my_i; // otherwise it does not compile - BaseNRAlgo::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - BaseNRAlgo::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - BaseNRAlgo::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed - // BaseNRAlgo::J_.setZero(); // TODO smarter solver: only needed if ybus has changed or pq changed or pv changed or ybus_some_coeffs_zero_ - // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed - // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + if(BaseNRAlgo::need_factorize_ || + BaseNRAlgo::_solver_control.need_reset_solver() || + BaseNRAlgo::_solver_control.has_dimension_changed() || + BaseNRAlgo::_solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + BaseNRAlgo::_solver_control.ybus_change_sparsity_pattern() || + BaseNRAlgo::_solver_control.has_ybus_some_coeffs_zero() || + BaseNRAlgo::_solver_control.need_recompute_ybus() || + // BaseNRAlgo:: _solver_control.has_slack_participate_changed() || + BaseNRAlgo::_solver_control.has_pv_changed() || + BaseNRAlgo::_solver_control.has_pq_changed() + ) + { + BaseNRAlgo::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + BaseNRAlgo::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + BaseNRAlgo::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + + } while ((!converged) & (BaseNRAlgo::nr_iter_ < max_iter)){ BaseNRAlgo::nr_iter_++; // std::cout << "\tnr_iter_ " << BaseNRAlgo::nr_iter_ << std::endl; From 2bf578767ff142d06214b3182f33201664565b72 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 09:20:14 +0100 Subject: [PATCH 49/66] fixing some issues in the CI --- lightsim2grid/lightSimBackend.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index f0d57c55..410d2dab 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -568,7 +568,8 @@ def _aux_setup_right_after_grid_init(self): self._grid._max_nb_bus_per_sub = self.n_busbar_per_sub def _load_grid_pandapower(self, path=None, filename=None): - type(self.init_pp_backend).n_busbar_per_sub = self.n_busbar_per_sub + if hasattr(type(self), "can_handle_more_than_2_busbar"): + type(self.init_pp_backend).n_busbar_per_sub = self.n_busbar_per_sub self.init_pp_backend.load_grid(path, filename) self.can_output_theta = True # i can compute the "theta" and output it to grid2op @@ -1091,11 +1092,12 @@ def copy(self): "_timer_preproc", "_timer_postproc", "_timer_solver", "_my_kwargs", "supported_grid_format", "_turned_off_pv", "_dist_slack_non_renew", - "_loader_method", "_loader_kwargs" + "_loader_method", "_loader_kwargs", + "_missing_two_busbars_support_info", "n_busbar_per_sub" ] for attr_nm in li_regular_attr: if hasattr(self, attr_nm): - # this test is needed for backward compatibility with other grid2op version + # this test is needed for backward compatibility with older grid2op version setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm))) # copy the numpy array @@ -1114,7 +1116,7 @@ def copy(self): ] for attr_nm in li_attr_npy: if hasattr(self, attr_nm): - # this test is needed for backward compatibility with other grid2op version + # this test is needed for backward compatibility with older grid2op version setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm))) # copy class attribute for older grid2op version (did not use the class attribute) From ab0c89c65b49a7334f4a9b72fa08ea1ba7ea45be Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 15:30:56 +0100 Subject: [PATCH 50/66] some more fixes, and improved CI --- .circleci/config.yml | 157 +++++++++++++++---- .gitignore | 2 + CHANGELOG.rst | 8 + README.md | 5 +- lightsim2grid/gridmodel/from_pypowsybl.py | 59 +++++-- lightsim2grid/lightSimBackend.py | 96 +++++++++--- src/GridModel.h | 8 + src/element_container/DCLineContainer.cpp | 32 ++-- src/element_container/DCLineContainer.h | 2 +- src/element_container/GeneratorContainer.cpp | 50 +++--- src/element_container/GenericContainer.cpp | 32 ++-- src/element_container/LineContainer.cpp | 71 +++++---- src/element_container/LoadContainer.cpp | 15 +- src/element_container/ShuntContainer.cpp | 21 ++- src/element_container/TrafoContainer.cpp | 65 ++++---- 15 files changed, 434 insertions(+), 189 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4ea5c7cc..5721da7a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,9 @@ orbs: executors: + gcc_13: + docker: + - image: gcc:13 gcc_12: docker: - image: gcc:12 @@ -20,6 +23,18 @@ executors: gcc_8: docker: - image: gcc:8 + clang18: + docker: + - image: silkeh/clang:18 + clang17: + docker: + - image: silkeh/clang:17 + clang16: + docker: + - image: silkeh/clang:16 + clang15: + docker: + - image: silkeh/clang:15 clang14: docker: - image: silkeh/clang:14 @@ -40,13 +55,30 @@ executors: - image: silkeh/clang:9 # no c++ 11, does not work jobs: + compile_gcc12: + executor: gcc_12 + resource_class: small + steps: + - checkout + - run: apt-get update && apt-get install python3-pip python3-full -y + - run: python3 -m pip install virtualenv + - run: python3 -m virtualenv venv_test + - run: + command: | + source venv_test/bin/activate + pip install -U grid2op + pip install -U pybind11 + git submodule init + git submodule update + make + CC=gcc python setup.py build + python -m pip install -U . compile_gcc11: executor: gcc_11 resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -64,8 +96,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -83,8 +114,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -102,8 +132,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -116,13 +145,12 @@ jobs: make CC=gcc python setup.py build python -m pip install -U . - compile_gcc12: - executor: gcc_12 + compile_gcc13: + executor: gcc_13 resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-virtualenv -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full -y # - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -152,8 +180,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -170,8 +197,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip git -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: command: | git submodule init @@ -190,8 +216,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip git -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -209,8 +234,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip git -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -228,8 +252,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip git -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -247,8 +270,79 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip git -y - - run: python3 -m pip install --upgrade pip setuptools + - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: python3 -m pip install virtualenv + - run: python3 -m virtualenv venv_test + - run: + command: | + source venv_test/bin/activate + pip install -U grid2op + pip install -U pybind11 + git submodule init + git submodule update + make + CC=clang python setup.py build + CC=clang python -m pip install -U . + compile_clang15: + executor: clang15 + resource_class: small + steps: + - checkout + - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: python3 -m pip install virtualenv + - run: python3 -m virtualenv venv_test + - run: + command: | + source venv_test/bin/activate + pip install -U grid2op + pip install -U pybind11 + git submodule init + git submodule update + make + CC=clang python setup.py build + CC=clang python -m pip install -U . + compile_clang16: + executor: clang16 + resource_class: small + steps: + - checkout + - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: python3 -m pip install virtualenv + - run: python3 -m virtualenv venv_test + - run: + command: | + source venv_test/bin/activate + pip install -U grid2op + pip install -U pybind11 + git submodule init + git submodule update + make + CC=clang python setup.py build + CC=clang python -m pip install -U . + compile_clang17: + executor: clang17 + resource_class: small + steps: + - checkout + - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: python3 -m pip install virtualenv + - run: python3 -m virtualenv venv_test + - run: + command: | + source venv_test/bin/activate + pip install -U grid2op + pip install -U pybind11 + git submodule init + git submodule update + make + CC=clang python setup.py build + CC=clang python -m pip install -U . + compile_clang18: + executor: clang18 + resource_class: small + steps: + - checkout + - run: apt-get update && apt-get install python3-pip python3-full git -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -279,7 +373,7 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - - run: python3 -m pip install --upgrade pip setuptools + - run: py -m pip install --upgrade pip setuptools - run: py -m pip install virtualenv - run: py -m virtualenv venv_test - run: @@ -309,11 +403,16 @@ workflows: compile: jobs: - compile_gcc8 - - compile_gcc10 - - compile_gcc11 + # - compile_gcc10 + # - compile_gcc11 - compile_gcc12 + - compile_gcc13 # - compile_clang10 # does not work I don't know why, too lazy to check - compile_clang11 - - compile_clang13 - - compile_clang14 + # - compile_clang13 + # - compile_clang14 + # - compile_clang15 + # - compile_clang16 + - compile_clang17 + - compile_clang18 - compile_windows diff --git a/.gitignore b/.gitignore index e4e37371..5272312b 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,5 @@ lightsim2grid/tests/_grid2op_for_test/ bug_sparselu bug_sparselu_eigen.cpp test_segfault.sh +nohup.out +test_rte/ \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2fa05e16..a02c84ed 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -39,6 +39,8 @@ Change Log - [FIXED] a bug where copying a lightsim2grid `GridModel` did not fully copy it - [FIXED] a bug in the "topo_vect" comprehension cpp side (sometimes some buses might not be activated / deactivated correctly) +- [FIXED] a bug when reading a grid initialize from pypowsybl (trafo names where put in place + of shunt names) - [FIXED] read the docs was broken - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. @@ -49,11 +51,17 @@ Change Log - [ADDED] a timer to get the time spent in the gridmodel for the powerflow (env.backend.timer_gridmodel_xx_pf) which also include the time - [ADDED] support for more than 2 busbars per substation (requires grid2op >= 1.10.0) +- [ADDED] possibility to retrieve the bus id of the original iidm when initializing from pypowsybl + (`return_sub_id` kwargs). This is a "beta" feature and will be adressed in a better way + in a near future. +- [ADDED] possibility to continue the grid2op 'step' when the solver converges but a load or a + generator is disconnected from the grid. - [IMPROVED] now performing the new grid2op `create_test_suite` - [IMPROVED] now lightsim2grid properly throw `BackendError` - [IMPROVED] clean ce cpp side by refactoring: making clearer the difference (linear) solver vs powerflow algorithm and move same type of files in the same directory. This change does not really affect python side at the moment (but will in future versions) +- [IMPROVED] CI to test on gcc 13 and clang 18 (latest versions to date) [0.7.5] 2023-10-05 -------------------- diff --git a/README.md b/README.md index 303ef73f..cf1c078d 100644 --- a/README.md +++ b/README.md @@ -268,9 +268,10 @@ cd .. Some tests are performed automatically on standard platform each time modifications are made in the lightsim2grid code. -These tests include, for now, compilation on gcc (version 8, 10, 11 and 12) and clang (version 11, 13 and 14). +These tests include, for now, compilation on gcc (version 8, 12 and 13) and clang (version 11, 16 and 17). -**NB** Intermediate versions of clang and gcc (*eg* gcc 9 or clang 12) are not tested regularly, but lightsim2grid used to work on these. We suppose that if it works on *eg* clang 10 and clang 14 then it compiles also on all intermediate versions. +**NB** Intermediate versions of clang and gcc (*eg* gcc 9 or clang 12) are not tested regularly, but lightsim2grid used to work on these. +We suppose that if it works on *eg* clang 10 and clang 14 then it compiles also on all intermediate versions. **NB** Package might work (we never tested it) on earlier version of these compilers. The only "real" requirement for lightsim2grid is to have a compiler supporting c++11 diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py index ef7a856a..6501ead8 100644 --- a/lightsim2grid/gridmodel/from_pypowsybl.py +++ b/lightsim2grid/gridmodel/from_pypowsybl.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, RTE (https://www.rte-france.com) +# Copyright (c) 2023-2024, RTE (https://www.rte-france.com) # See AUTHORS.txt # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -6,7 +6,9 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. +import warnings import numpy as np +import pandas as pd import pypowsybl as pypo from lightsim2grid_cpp import GridModel @@ -39,7 +41,8 @@ def init(net : pypo.network, sn_mva = 100., sort_index=True, f_hz = 50., # unused - only_main_component=True): + only_main_component=True, + return_sub_id=False): model = GridModel() # model.set_f_hz(f_hz) @@ -66,7 +69,7 @@ def init(net : pypo.network, 0, 0 # unused ) model._orig_to_ls = 1 * bus_df_orig["bus_id"].values - + # do the generators if sort_index: df_gen = net.get_generators().sort_index() @@ -148,7 +151,7 @@ def init(net : pypo.network, for line_id, (is_or_disc, is_ex_disc) in enumerate(zip(lor_disco, lex_disco)): if is_or_disc or is_ex_disc: model.deactivate_powerline(line_id) - model.set_line_names(df_line.index) + model.set_line_names(df_line.index) # for trafo if sort_index: @@ -201,19 +204,23 @@ def init(net : pypo.network, for shunt_id, disco in enumerate(sh_disco): if disco: model.deactivate_shunt(shunt_id) - model.set_shunt_names(df_trafo.index) + model.set_shunt_names(df_shunt.index) # for hvdc (TODO not tested yet) - df_dc = net.get_hvdc_lines().sort_index() - df_sations = net.get_vsc_converter_stations().sort_index() + if sort_index: + df_dc = net.get_hvdc_lines().sort_index() + df_sations = net.get_vsc_converter_stations().sort_index() + else: + df_dc = net.get_hvdc_lines() + df_sations = net.get_vsc_converter_stations() # bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values # bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values - bus_from_id, hvdc_from_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station1_id"].values]) - bus_to_id, hvdc_to_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station2_id"].values]) + hvdc_bus_from_id, hvdc_from_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station1_id"].values]) + hvdc_bus_to_id, hvdc_to_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station2_id"].values]) loss_percent = np.zeros(df_dc.shape[0]) # TODO loss_mw = np.zeros(df_dc.shape[0]) # TODO - model.init_dclines(bus_from_id, - bus_to_id, + model.init_dclines(hvdc_bus_from_id, + hvdc_bus_to_id, df_dc["target_p"].values, loss_percent, loss_mw, @@ -270,10 +277,34 @@ def init(net : pypo.network, # TODO checks # no 3windings trafo and other exotic stuff if net.get_phase_tap_changers().shape[0] > 0: - pass - # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") + warnings.warn("There are tap changers in the iidm grid which are not taken " + "into account in the lightsim2grid at the moment. " + "NB: lightsim2grid gridmodel can handle tap changer, it is just not " + "handled by the 'from_pypowsybl` function at the moment.") # and now deactivate all elements and nodes not in the main component if only_main_component: model.consider_only_main_component() - return model + if not return_sub_id: + # for backward compatibility + return model + else: + # voltage_level_id is kind of what I call "substation" in grid2op + vl_unique = bus_df["voltage_level_id"].unique() + sub_df = pd.DataFrame(index=np.sort(vl_unique), data={"sub_id": np.arange(vl_unique.size)}) + buses_sub_id = pd.merge(left=bus_df, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["bus_id", "sub_id"]] + gen_sub = pd.merge(left=df_gen, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]] + load_sub = pd.merge(left=df_load, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]] + lor_sub = pd.merge(left=df_line, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]] + lex_sub = pd.merge(left=df_line, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]] + tor_sub = pd.merge(left=df_trafo, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]] + tex_sub = pd.merge(left=df_trafo, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]] + batt_sub = pd.merge(left=df_batt, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]] + sh_sub = pd.merge(left=df_shunt, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]] + hvdc_vl_info = pd.DataFrame(index=df_dc.index, + data={"voltage_level1_id": df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values, + "voltage_level2_id": df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values + }) + hvdc_sub_from_id = pd.merge(left=hvdc_vl_info, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]] + hvdc_sub_to_id = pd.merge(left=hvdc_vl_info, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]] + return model, (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) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 410d2dab..f91d869c 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -10,6 +10,7 @@ from typing import Optional, Union import warnings import numpy as np +import pandas as pd import time from grid2op.Action import CompleteAction @@ -49,6 +50,8 @@ def __init__(self, use_static_gen: bool=False, # add the static generators as generator gri2dop side loader_method: Literal["pandapower", "pypowsybl"] = "pandapower", loader_kwargs : Optional[dict] = None, + stop_if_load_disco : Optional[bool] = True, + stop_if_gen_disco : Optional[bool] = True, ): try: # for grid2Op >= 1.7.1 @@ -62,7 +65,10 @@ def __init__(self, dist_slack_non_renew=dist_slack_non_renew, use_static_gen=use_static_gen, loader_method=loader_method, - loader_kwargs=loader_kwargs) + loader_kwargs=loader_kwargs, + stop_if_load_disco=stop_if_load_disco, + stop_if_gen_disco=stop_if_gen_disco, + ) except TypeError as exc_: warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, " "you cannot set max_iter, tol nor solver_type arguments.") @@ -79,6 +85,16 @@ def __init__(self, self._loader_method = loader_method self._loader_kwargs = loader_kwargs + #: .. versionadded:: 0.7.6 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected load + self._stop_if_load_disco = stop_if_load_disco + + #: .. versionadded:: 0.7.6 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected generator + self._stop_if_gen_disco = stop_if_gen_disco + if loader_method == "pandapower": self.supported_grid_format = ("json", ) # new in 1.9.6 elif loader_method == "pypowsybl": @@ -452,7 +468,14 @@ def _aux_pypowsybl_init_substations(self, loader_kwargs): for i in range(self.n_busbar_per_sub)] ) self._grid._orig_to_ls = new_orig_to_ls - + + def _get_subid_from_buses_legacy(self, buses_sub_id, el_sub_df): + # buses_sub_id is the first element as returned by from_pypowsybl / init function + # el_sub_df is an element dataframe returned by the same function + tmp = pd.merge(el_sub_df.reset_index(), buses_sub_id, how="left", right_on="sub_id", left_on="sub_id") + res = tmp.drop_duplicates(subset='id').set_index("id").sort_index()["bus_id"].values + return res + def _load_grid_pypowsybl(self, path=None, filename=None): from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow import pypowsybl.network as pypow_net @@ -470,7 +493,8 @@ def _load_grid_pypowsybl(self, path=None, filename=None): gen_slack_id = None if "gen_slack_id" in loader_kwargs: gen_slack_id = int(loader_kwargs["gen_slack_id"]) - self._grid = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True) + self._grid, subs_id = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True, return_sub_id=True) + (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 self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) self._aux_pypowsybl_init_substations(loader_kwargs) self._aux_setup_right_after_grid_init() @@ -492,18 +516,28 @@ def _load_grid_pypowsybl(self, path=None, filename=None): self.name_sub = ["sub_{}".format(i) for i, _ in enumerate(df.iterrows())] if not from_sub: - self.load_to_subid = np.array([el.bus_id for el in self._grid.get_loads()], dtype=dt_int) - self.gen_to_subid = np.array([el.bus_id for el in self._grid.get_generators()], dtype=dt_int) - self.line_or_to_subid = np.array([el.bus_or_id for el in self._grid.get_lines()] + - [el.bus_hv_id for el in self._grid.get_trafos()], - dtype=dt_int) - self.line_ex_to_subid = np.array([el.bus_ex_id for el in self._grid.get_lines()] + - [el.bus_lv_id for el in self._grid.get_trafos()], - dtype=dt_int) - self.storage_to_subid = np.array([el.bus_id for el in self._grid.get_storages()], dtype=dt_int) - self.shunt_to_subid = np.array([el.bus_id for el in self._grid.get_shunts()], dtype=dt_int) + # consider that each "bus" in the powsybl grid is a substation + # this is the "standard" behaviour for IEEE grid in grid2op + # but can be considered "legacy" behaviour for more realistic grid + this_load_sub = self._get_subid_from_buses_legacy(buses_sub_id, load_sub) + this_gen_sub = self._get_subid_from_buses_legacy(buses_sub_id, gen_sub) + this_lor_sub = self._get_subid_from_buses_legacy(buses_sub_id, lor_sub) + this_tor_sub = self._get_subid_from_buses_legacy(buses_sub_id, tor_sub) + this_lex_sub = self._get_subid_from_buses_legacy(buses_sub_id, lex_sub) + this_tex_sub = self._get_subid_from_buses_legacy(buses_sub_id, tex_sub) + this_batt_sub = self._get_subid_from_buses_legacy(buses_sub_id, batt_sub) + this_sh_sub = self._get_subid_from_buses_legacy(buses_sub_id, sh_sub) + + self.load_to_subid = np.array(this_load_sub, dtype=dt_int) + self.gen_to_subid = np.array(this_gen_sub, dtype=dt_int) + self.line_or_to_subid = np.concatenate((this_lor_sub, this_tor_sub)).astype(dt_int) + self.line_ex_to_subid = np.concatenate((this_lex_sub, this_tex_sub)).astype(dt_int) + self.storage_to_subid = np.array(this_batt_sub, dtype=dt_int) + self.shunt_to_subid = np.array(this_sh_sub, dtype=dt_int) else: # TODO get back the sub id from the grid_tmp.get_substations() + # need to work on that grid2op side: different make sure the labelling of the buses are correct ! + (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 raise NotImplementedError("Today the only supported behaviour is to consider the 'buses' of the powsybl grid " "are the 'substation' of this backend. " "This will change in the future, but in the meantime please add " @@ -511,12 +545,23 @@ def _load_grid_pypowsybl(self, path=None, filename=None): "a lightsim2grid grid.") # the names - self.name_load = np.array([f"load_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_loads())]) - self.name_gen = np.array([f"gen_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_generators())]) - self.name_line = np.array([f"{el.bus_or_id}_{el.bus_ex_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_lines())] + - [f"{el.bus_hv_id}_{el.bus_lv_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_trafos())]) - self.name_storage = np.array([f"storage_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_storages())]) - self.name_shunt = np.array([f"shunt_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_shunts())]) + use_grid2op_default_names = True + if "use_grid2op_default_names" in loader_kwargs and not loader_kwargs["use_grid2op_default_names"]: + use_grid2op_default_names = False + + if use_grid2op_default_names: + self.name_load = np.array([f"load_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_loads())]) + self.name_gen = np.array([f"gen_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_generators())]) + self.name_line = np.array([f"{el.bus_or_id}_{el.bus_ex_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_lines())] + + [f"{el.bus_hv_id}_{el.bus_lv_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_trafos())]) + self.name_storage = np.array([f"storage_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_storages())]) + self.name_shunt = np.array([f"shunt_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_shunts())]) + else: + self.name_load = np.array(load_sub.index) + self.name_gen = np.array(gen_sub.index) + self.name_line = np.concatenate((lor_sub.index, tor_sub.index)) + self.name_storage = np.array(batt_sub.index) + self.name_shunt = np.array(sh_sub.index) # complete the other vectors self._compute_pos_big_topo() @@ -537,8 +582,8 @@ def _load_grid_pypowsybl(self, path=None, filename=None): max_not_too_max = (np.finfo(dt_float).max * 0.5 - 1.) self.thermal_limit_a = max_not_too_max * np.ones(self.n_line, dtype=dt_float) bus_vn_kv = np.array(self._grid.get_bus_vn_kv()) - shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()]) - self._sh_vnkv = bus_vn_kv[shunt_bus_id] + # shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()]) + self._sh_vnkv = bus_vn_kv[self.shunt_to_subid] self._aux_finish_setup_after_reading() def _aux_setup_right_after_grid_init(self): @@ -567,6 +612,8 @@ def _aux_setup_right_after_grid_init(self): # grid2op version >= 1.10.0 then we use this self._grid._max_nb_bus_per_sub = self.n_busbar_per_sub + self._grid.tell_solver_need_reset() + def _load_grid_pandapower(self, path=None, filename=None): if hasattr(type(self), "can_handle_more_than_2_busbar"): type(self.init_pp_backend).n_busbar_per_sub = self.n_busbar_per_sub @@ -979,12 +1026,12 @@ def runpf(self, is_dc=False): self.next_prod_p[:] = self.prod_p - if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.): + if self._stop_if_load_disco and ((~np.isfinite(self.load_v)).any() or (self.load_v <= 0.).any()): disco = (~np.isfinite(self.load_v)) | (self.load_v <= 0.) load_disco = np.where(disco)[0] self._timer_postproc += time.perf_counter() - beg_postroc raise BackendError(f"At least one load is disconnected (check loads {load_disco})") - if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.): + if self._stop_if_gen_disco and ((~np.isfinite(self.prod_v)).any() or (self.prod_v <= 0.).any()): disco = (~np.isfinite(self.prod_v)) | (self.prod_v <= 0.) gen_disco = np.where(disco)[0] self._timer_postproc += time.perf_counter() - beg_postroc @@ -1093,7 +1140,8 @@ def copy(self): "_my_kwargs", "supported_grid_format", "_turned_off_pv", "_dist_slack_non_renew", "_loader_method", "_loader_kwargs", - "_missing_two_busbars_support_info", "n_busbar_per_sub" + "_missing_two_busbars_support_info", "n_busbar_per_sub", + "_use_static_gen", "_stop_if_load_disco", "_stop_if_gen_disco" ] for attr_nm in li_regular_attr: if hasattr(self, attr_nm): diff --git a/src/GridModel.h b/src/GridModel.h index 4fefb2fb..7a4b69c5 100644 --- a/src/GridModel.h +++ b/src/GridModel.h @@ -352,27 +352,35 @@ class GridModel : public GenericContainer const std::vector & get_bus_status() const {return bus_status_;} void set_line_names(const std::vector & names){ + GenericContainer::check_size(names, powerlines_.nb(), "set_line_names"); powerlines_.set_names(names); } void set_dcline_names(const std::vector & names){ + GenericContainer::check_size(names, dc_lines_.nb(), "set_dcline_names"); dc_lines_.set_names(names); } void set_trafo_names(const std::vector & names){ + GenericContainer::check_size(names, trafos_.nb(), "set_trafo_names"); trafos_.set_names(names); } void set_gen_names(const std::vector & names){ + GenericContainer::check_size(names, generators_.nb(), "set_gen_names"); generators_.set_names(names); } void set_load_names(const std::vector & names){ + GenericContainer::check_size(names, loads_.nb(), "set_load_names"); loads_.set_names(names); } void set_storage_names(const std::vector & names){ + GenericContainer::check_size(names, storages_.nb(), "set_storage_names"); storages_.set_names(names); } void set_sgen_names(const std::vector & names){ + GenericContainer::check_size(names, sgens_.nb(), "set_sgen_names"); sgens_.set_names(names); } void set_shunt_names(const std::vector & names){ + GenericContainer::check_size(names, shunts_.nb(), "set_shunt_names"); shunts_.set_names(names); } diff --git a/src/element_container/DCLineContainer.cpp b/src/element_container/DCLineContainer.cpp index 716229b0..01a683b7 100644 --- a/src/element_container/DCLineContainer.cpp +++ b/src/element_container/DCLineContainer.cpp @@ -15,13 +15,13 @@ DCLineContainer::StateRes DCLineContainer::get_state() const { std::vector loss_percent(loss_percent_.begin(), loss_percent_.end()); std::vector loss_mw(loss_mw_.begin(), loss_mw_.end()); - std::vector status = status_; + std::vector status = status_; DCLineContainer::StateRes res(names_, - from_gen_.get_state(), - to_gen_.get_state(), - loss_percent, - loss_mw, - status); + from_gen_.get_state(), + to_gen_.get_state(), + loss_percent, + loss_mw, + status); return res; } @@ -39,16 +39,16 @@ void DCLineContainer::set_state(DCLineContainer::StateRes & my_state){ } void DCLineContainer::init(const Eigen::VectorXi & branch_from_id, - const Eigen::VectorXi & branch_to_id, - const RealVect & p_mw, - const RealVect & loss_percent, - const RealVect & loss_mw, - const RealVect & vm_or_pu, - const RealVect & vm_ex_pu, - const RealVect & min_q_or, - const RealVect & max_q_or, - const RealVect & min_q_ex, - const RealVect & max_q_ex){ + const Eigen::VectorXi & branch_to_id, + const RealVect & p_mw, + const RealVect & loss_percent, + const RealVect & loss_mw, + const RealVect & vm_or_pu, + const RealVect & vm_ex_pu, + const RealVect & min_q_or, + const RealVect & max_q_or, + const RealVect & min_q_ex, + const RealVect & max_q_ex){ loss_percent_ = loss_percent; loss_mw_ = loss_mw; status_ = std::vector(branch_from_id.size(), true); diff --git a/src/element_container/DCLineContainer.h b/src/element_container/DCLineContainer.h index 5ee14dc9..a64fb1fe 100644 --- a/src/element_container/DCLineContainer.h +++ b/src/element_container/DCLineContainer.h @@ -186,7 +186,7 @@ class DCLineContainer : public GenericContainer } // for buses only connected through dc line, i don't add them // they are not in the same "connected component" - virtual void get_graph(std::vector > & res) const {}; + virtual void get_graph(std::vector > & res) const {}; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void nb_line_end(std::vector & res) const; virtual void update_bus_status(std::vector & bus_status) const { diff --git a/src/element_container/GeneratorContainer.cpp b/src/element_container/GeneratorContainer.cpp index b580c40e..25159972 100644 --- a/src/element_container/GeneratorContainer.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -11,11 +11,18 @@ #include void GeneratorContainer::init(const RealVect & generators_p, - const RealVect & generators_v, - const RealVect & generators_min_q, - const RealVect & generators_max_q, - const Eigen::VectorXi & generators_bus_id) + const RealVect & generators_v, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id) { + int size = static_cast(generators_p.size()); + GenericContainer::check_size(generators_p, size, "generators_p"); + GenericContainer::check_size(generators_v, size, "generators_v"); + GenericContainer::check_size(generators_min_q, size, "generators_min_q"); + GenericContainer::check_size(generators_max_q, size, "generators_max_q"); + GenericContainer::check_size(generators_bus_id, size, "generators_bus_id"); + p_mw_ = generators_p; vm_pu_ = generators_v; bus_id_ = generators_bus_id; @@ -50,15 +57,18 @@ void GeneratorContainer::init(const RealVect & generators_p, } void GeneratorContainer::init_full(const RealVect & generators_p, - const RealVect & generators_v, - const RealVect & generators_q, - const std::vector & voltage_regulator_on, - const RealVect & generators_min_q, - const RealVect & generators_max_q, - const Eigen::VectorXi & generators_bus_id - ) + const RealVect & generators_v, + const RealVect & generators_q, + const std::vector & voltage_regulator_on, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id + ) { init(generators_p, generators_v, generators_min_q, generators_max_q, generators_bus_id); + int size = static_cast(generators_p.size()); + GenericContainer::check_size(generators_q, size, "generators_q"); + GenericContainer::check_size(voltage_regulator_on, size, "voltage_regulator_on"); voltage_regulator_on_ = voltage_regulator_on; q_mvar_ = generators_q; } @@ -353,7 +363,7 @@ Eigen::VectorXi GeneratorContainer::get_slack_bus_id() const{ } void GeneratorContainer::set_p_slack(const RealVect& node_mismatch, - const std::vector & id_grid_to_solver) + const std::vector & id_grid_to_solver) { if(bus_slack_weight_.size() == 0){ // TODO DEBUG MODE: perform this check only in debug mode @@ -377,9 +387,9 @@ void GeneratorContainer::set_p_slack(const RealVect& node_mismatch, } void GeneratorContainer::init_q_vector(int nb_bus, - Eigen::VectorXi & total_gen_per_bus, - RealVect & total_q_min_per_bus, - RealVect & total_q_max_per_bus) const // delta_q_per_gen_) // total number of bus on the grid + Eigen::VectorXi & total_gen_per_bus, + RealVect & total_q_min_per_bus, + RealVect & total_q_max_per_bus) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) @@ -397,11 +407,11 @@ void GeneratorContainer::init_q_vector(int nb_bus, } void GeneratorContainer::set_q(const RealVect & reactive_mismatch, - const std::vector & id_grid_to_solver, - bool ac, - const Eigen::VectorXi & total_gen_per_bus, - const RealVect & total_q_min_per_bus, - const RealVect & total_q_max_per_bus) + const std::vector & id_grid_to_solver, + bool ac, + const Eigen::VectorXi & total_gen_per_bus, + const RealVect & total_q_min_per_bus, + const RealVect & total_q_max_per_bus) { const int nb_gen = nb(); res_q_ = RealVect::Constant(nb_gen, 0.); diff --git a/src/element_container/GenericContainer.cpp b/src/element_container/GenericContainer.cpp index 6daa4fe7..cf6dc51c 100644 --- a/src/element_container/GenericContainer.cpp +++ b/src/element_container/GenericContainer.cpp @@ -27,14 +27,17 @@ void GenericContainer::_get_amps(RealVect & a, const RealVect & p, const RealVec } a = p2q2.array() * _1_sqrt_3 / v_tmp.array(); } + void GenericContainer::_reactivate(int el_id, std::vector & status){ bool val = status.at(el_id); status.at(el_id) = true; //TODO why it's needed to do that again } + void GenericContainer::_deactivate(int el_id, std::vector & status){ bool val = status.at(el_id); status.at(el_id) = false; //TODO why it's needed to do that again } + void GenericContainer::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus){ // bus id here "me_id" and NOT "solver_id" // throw error: object id does not exist @@ -105,13 +108,14 @@ int GenericContainer::_get_bus(int el_id, const std::vector & status_, con } void GenericContainer::v_kv_from_vpu(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const std::vector & status, - int nb_element, - const Eigen::VectorXi & bus_me_id, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - RealVect & v){ + const Eigen::Ref & Vm, + const std::vector & status, + int nb_element, + const Eigen::VectorXi & bus_me_id, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + RealVect & v) +{ v = RealVect::Constant(nb_element, -1.0); for(int el_id = 0; el_id < nb_element; ++el_id){ // if the element is disconnected, i leave it like that @@ -133,13 +137,13 @@ void GenericContainer::v_kv_from_vpu(const Eigen::Ref & Va, void GenericContainer::v_deg_from_va(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const std::vector & status, - int nb_element, - const Eigen::VectorXi & bus_me_id, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - RealVect & theta){ + const Eigen::Ref & Vm, + const std::vector & status, + int nb_element, + const Eigen::VectorXi & bus_me_id, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + RealVect & theta){ theta = RealVect::Constant(nb_element, 0.0); for(int el_id = 0; el_id < nb_element; ++el_id){ // if the element is disconnected, i leave it like that diff --git a/src/element_container/LineContainer.cpp b/src/element_container/LineContainer.cpp index 610aa458..efeac7dd 100644 --- a/src/element_container/LineContainer.cpp +++ b/src/element_container/LineContainer.cpp @@ -11,11 +11,11 @@ #include void LineContainer::init(const RealVect & branch_r, - const RealVect & branch_x, - const CplxVect & branch_h, - const Eigen::VectorXi & branch_from_id, - const Eigen::VectorXi & branch_to_id - ) + const RealVect & branch_x, + const CplxVect & branch_h, + const Eigen::VectorXi & branch_from_id, + const Eigen::VectorXi & branch_to_id + ) { /** This method initialize the Ybus matrix from the branch matrix. @@ -29,6 +29,13 @@ void LineContainer::init(const RealVect & branch_r, //TODO consistency with trafo: have a converter methods to convert this value into pu, and store the pu // in this method + int size = static_cast(branch_r.size()); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h, size, "branch_h"); + GenericContainer::check_size(branch_from_id, size, "branch_from_id"); + GenericContainer::check_size(branch_to_id, size, "branch_to_id"); + bus_or_id_ = branch_from_id; bus_ex_id_ = branch_to_id; powerlines_h_or_ = 0.5 * branch_h; @@ -40,12 +47,12 @@ void LineContainer::init(const RealVect & branch_r, } void LineContainer::init(const RealVect & branch_r, - const RealVect & branch_x, - const CplxVect & branch_h_or, - const CplxVect & branch_h_ex, - const Eigen::VectorXi & branch_from_id, - const Eigen::VectorXi & branch_to_id - ) + const RealVect & branch_x, + const CplxVect & branch_h_or, + const CplxVect & branch_h_ex, + const Eigen::VectorXi & branch_from_id, + const Eigen::VectorXi & branch_to_id + ) { /** This method initialize the Ybus matrix from the branch matrix. @@ -59,6 +66,14 @@ void LineContainer::init(const RealVect & branch_r, //TODO consistency with trafo: have a converter methods to convert this value into pu, and store the pu // in this method + int size = static_cast(branch_r.size()); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h_or, size, "branch_h_or"); + GenericContainer::check_size(branch_h_ex, size, "branch_h_ex"); + GenericContainer::check_size(branch_from_id, size, "branch_from_id"); + GenericContainer::check_size(branch_to_id, size, "branch_to_id"); + bus_or_id_ = branch_from_id; bus_ex_id_ = branch_to_id; powerlines_h_or_ = branch_h_or; @@ -150,9 +165,9 @@ void LineContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac } void LineContainer::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { // fill the matrix //TODO template here instead of "if" for ac / dc @@ -206,10 +221,10 @@ void LineContainer::fillYbus(std::vector > & res, } void LineContainer::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { // For Bp @@ -291,10 +306,10 @@ void LineContainer::fillBp_Bpp(std::vector > & Bp, void LineContainer::fillBf_for_PTDF(std::vector > & Bf, - const std::vector & id_grid_to_solver, - real_type sn_mva, - int nb_powerline, - bool transpose) const + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_powerline, + bool transpose) const { const Eigen::Index nb_line = powerlines_r_.size(); @@ -351,12 +366,12 @@ void LineContainer::reset_results() } void LineContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac) { // it needs to be initialized at 0. Eigen::Index nb_element = nb(); diff --git a/src/element_container/LoadContainer.cpp b/src/element_container/LoadContainer.cpp index d7eb561b..d9d0a1bd 100644 --- a/src/element_container/LoadContainer.cpp +++ b/src/element_container/LoadContainer.cpp @@ -10,9 +10,14 @@ #include void LoadContainer::init(const RealVect & loads_p, - const RealVect & loads_q, - const Eigen::VectorXi & loads_bus_id) + const RealVect & loads_q, + const Eigen::VectorXi & loads_bus_id) { + int size = static_cast(loads_p.size()); + GenericContainer::check_size(loads_p, size, "loads_p"); + GenericContainer::check_size(loads_q, size, "loads_q"); + GenericContainer::check_size(loads_bus_id, size, "loads_bus_id"); + p_mw_ = loads_p; q_mvar_ = loads_q; bus_id_ = loads_bus_id; @@ -29,6 +34,7 @@ LoadContainer::StateRes LoadContainer::get_state() const LoadContainer::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } + void LoadContainer::set_state(LoadContainer::StateRes & my_state ) { reset_results(); @@ -47,7 +53,10 @@ void LoadContainer::set_state(LoadContainer::StateRes & my_state ) } -void LoadContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void LoadContainer::fillSbus(CplxVect & Sbus, + const std::vector & id_grid_to_solver, + bool ac) const +{ int nb_load = nb(); int bus_id_me, bus_id_solver; cplx_type tmp; diff --git a/src/element_container/ShuntContainer.cpp b/src/element_container/ShuntContainer.cpp index 76a6129e..4b5687b7 100644 --- a/src/element_container/ShuntContainer.cpp +++ b/src/element_container/ShuntContainer.cpp @@ -11,9 +11,14 @@ #include void ShuntContainer::init(const RealVect & shunt_p_mw, - const RealVect & shunt_q_mvar, - const Eigen::VectorXi & shunt_bus_id) + const RealVect & shunt_q_mvar, + const Eigen::VectorXi & shunt_bus_id) { + int size = static_cast(shunt_p_mw.size()); + GenericContainer::check_size(shunt_p_mw, size, "shunt_p_mw"); + GenericContainer::check_size(shunt_q_mvar, size, "shunt_q_mvar"); + GenericContainer::check_size(shunt_bus_id, size, "shunt_bus_id"); + p_mw_ = shunt_p_mw; q_mvar_ = shunt_q_mvar; bus_id_ = shunt_bus_id; @@ -132,12 +137,12 @@ void ShuntContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool a } void ShuntContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac) { const int nb_shunt = static_cast(p_mw_.size()); v_kv_from_vpu(Va, Vm, status_, nb_shunt, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); diff --git a/src/element_container/TrafoContainer.cpp b/src/element_container/TrafoContainer.cpp index ac4c3461..f33e1cd1 100644 --- a/src/element_container/TrafoContainer.cpp +++ b/src/element_container/TrafoContainer.cpp @@ -12,16 +12,16 @@ #include void TrafoContainer::init(const RealVect & trafo_r, - const RealVect & trafo_x, - const CplxVect & trafo_b, - const RealVect & trafo_tap_step_pct, - // const RealVect & trafo_tap_step_degree, - const RealVect & trafo_tap_pos, - const RealVect & trafo_shift_degree, - const std::vector & trafo_tap_hv, // is tap on high voltage (true) or low voltate - const Eigen::VectorXi & trafo_hv_id, - const Eigen::VectorXi & trafo_lv_id - ) + const RealVect & trafo_x, + const CplxVect & trafo_b, + const RealVect & trafo_tap_step_pct, + // const RealVect & trafo_tap_step_degree, + const RealVect & trafo_tap_pos, + const RealVect & trafo_shift_degree, + const std::vector & trafo_tap_hv, // is tap on high voltage (true) or low voltate + const Eigen::VectorXi & trafo_hv_id, + const Eigen::VectorXi & trafo_lv_id + ) { /** INPUT DATA ARE ALREADY PAIR UNIT !! @@ -164,15 +164,17 @@ void TrafoContainer::_update_model_coeffs() } } -void TrafoContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) +void TrafoContainer::fillYbus_spmat(Eigen::SparseMatrix & res, + bool ac, + const std::vector & id_grid_to_solver) { throw std::runtime_error("You should not use that!"); } void TrafoContainer::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { //TODO merge that with fillYbusBranch! //TODO template here instead of "if" for ac / dc @@ -222,7 +224,10 @@ void TrafoContainer::fillYbus(std::vector > & res, } } -void TrafoContainer::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver){ +void TrafoContainer::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, + bool ac, + const std::vector & id_grid_to_solver) +{ if(ac) return; // return; const int nb_trafo = nb(); @@ -256,13 +261,13 @@ void TrafoContainer::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, co } void TrafoContainer::compute_results(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const Eigen::Ref & V, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - real_type sn_mva, - bool ac - ) + const Eigen::Ref & Vm, + const Eigen::Ref & V, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + real_type sn_mva, + bool ac + ) { // it needs to be initialized at 0. const int nb_element = nb(); @@ -360,10 +365,10 @@ void TrafoContainer::reset_results(){ void TrafoContainer::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { // For Bp @@ -461,10 +466,10 @@ void TrafoContainer::fillBp_Bpp(std::vector > & Bp, void TrafoContainer::fillBf_for_PTDF(std::vector > & Bf, - const std::vector & id_grid_to_solver, - real_type sn_mva, - int nb_powerline, - bool transpose) const + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_powerline, + bool transpose) const { const Eigen::Index nb_trafo = r_.size(); From b95bd0cc3e364d6c68c34948b70a424af9a3e514 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 15:48:11 +0100 Subject: [PATCH 51/66] debuging CI, a passion --- .circleci/config.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5721da7a..4cae81ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,11 +61,11 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip python3-full -y - - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -132,7 +132,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full -y + - run: apt-get update && apt-get install python3-pip -y - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -157,6 +157,7 @@ jobs: name: "Install grid2op from source" command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel git clone https://github.com/rte-france/grid2op.git _grid2op pip install -e _grid2op - run: @@ -325,7 +326,6 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip python3-full git -y - - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: command: | @@ -343,7 +343,6 @@ jobs: steps: - checkout - run: apt-get update && apt-get install python3-pip python3-full git -y - - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: name: "Install grid2op from source" @@ -373,7 +372,7 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - - run: py -m pip install --upgrade pip setuptools + - run: py -m pip install --upgrade pip setuptools wheel distribute - run: py -m pip install virtualenv - run: py -m virtualenv venv_test - run: From 8734cbf7abea553aa62aaa8eb2359fe29f936aff Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 15:57:29 +0100 Subject: [PATCH 52/66] debuging CI, a passion --- .circleci/config.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4cae81ab..250a285d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -60,7 +60,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full -y + - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y - run: python3 -m virtualenv venv_test - run: command: | @@ -84,6 +84,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -102,6 +103,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -120,6 +122,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -138,6 +141,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -150,7 +154,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full -y + - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y # - run: python3 -m pip install virtualenv - run: python3 -m virtualenv venv_test - run: @@ -187,6 +191,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U pybind11 git submodule init git submodule update @@ -209,6 +214,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U pybind11 CC=clang python setup.py build python -m pip install . @@ -223,6 +229,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -241,6 +248,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -259,6 +267,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -277,6 +286,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -295,6 +305,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -313,6 +324,7 @@ jobs: - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -325,11 +337,12 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y - run: python3 -m virtualenv venv_test - run: command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute pip install -U grid2op pip install -U pybind11 git submodule init @@ -342,12 +355,13 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full git -y + - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y - run: python3 -m virtualenv venv_test - run: name: "Install grid2op from source" command: | source venv_test/bin/activate + pip install --upgrade pip setuptools wheel distribute git clone https://github.com/rte-france/grid2op.git _grid2op pip install -e _grid2op - run: @@ -372,7 +386,9 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - - run: py -m pip install --upgrade pip setuptools wheel distribute + - run: choco install python --version=3.10 + - run: py --version + - run: py -m pip install --upgrade pip setuptools wheel - run: py -m pip install virtualenv - run: py -m virtualenv venv_test - run: From 6fa83dd900573f6767484fb39d8430518ae5388d Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 16:01:56 +0100 Subject: [PATCH 53/66] debuging CI, a passion --- .circleci/config.yml | 53 ++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 250a285d..542bd2dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,9 +23,9 @@ executors: gcc_8: docker: - image: gcc:8 - clang18: - docker: - - image: silkeh/clang:18 + # clang18: + # docker: + # - image: silkeh/clang:18 clang17: docker: - image: silkeh/clang:17 @@ -84,7 +84,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -103,7 +103,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -122,7 +122,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -141,7 +141,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -191,7 +191,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U pybind11 git submodule init git submodule update @@ -214,7 +214,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U pybind11 CC=clang python setup.py build python -m pip install . @@ -229,7 +229,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -248,7 +248,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -267,7 +267,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -286,7 +286,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -305,7 +305,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -324,7 +324,7 @@ jobs: - run: command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel pip install -U grid2op pip install -U pybind11 git submodule init @@ -335,24 +335,6 @@ jobs: compile_clang17: executor: clang17 resource_class: small - steps: - - checkout - - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y - - run: python3 -m virtualenv venv_test - - run: - command: | - source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute - pip install -U grid2op - pip install -U pybind11 - git submodule init - git submodule update - make - CC=clang python setup.py build - CC=clang python -m pip install -U . - compile_clang18: - executor: clang18 - resource_class: small steps: - checkout - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y @@ -361,7 +343,7 @@ jobs: name: "Install grid2op from source" command: | source venv_test/bin/activate - pip install --upgrade pip setuptools wheel distribute + pip install --upgrade pip setuptools wheel git clone https://github.com/rte-france/grid2op.git _grid2op pip install -e _grid2op - run: @@ -427,7 +409,6 @@ workflows: # - compile_clang13 # - compile_clang14 # - compile_clang15 - # - compile_clang16 + - compile_clang16 - compile_clang17 - - compile_clang18 - compile_windows From 85852a1ea41093a9d7d7fb987dfca9ad8b000caf Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 16:46:27 +0100 Subject: [PATCH 54/66] trying to fix CI yet again --- .circleci/config.yml | 5 ++-- CHANGELOG.rst | 2 ++ lightsim2grid/gridmodel/_aux_add_slack.py | 4 +-- lightsim2grid/lightSimBackend.py | 22 +++++++++----- .../tests/test_init_from_pypowsybl.py | 30 +++++++++++++------ 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 542bd2dc..a476e0b2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -318,8 +318,7 @@ jobs: resource_class: small steps: - checkout - - run: apt-get update && apt-get install python3-pip python3-full git -y - - run: python3 -m pip install virtualenv + - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y - run: python3 -m virtualenv venv_test - run: command: | @@ -368,7 +367,7 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - - run: choco install python --version=3.10 + - run: choco install python --version=3.10 --force - run: py --version - run: py -m pip install --upgrade pip setuptools wheel - run: py -m pip install virtualenv diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a02c84ed..bfa3cd8c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -42,6 +42,8 @@ Change Log - [FIXED] a bug when reading a grid initialize from pypowsybl (trafo names where put in place of shunt names) - [FIXED] read the docs was broken +- [FIXED] a bug when reading a grid from pandapower for multiple slacks when slack + are given by the "ext_grid" information. - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. diff --git a/lightsim2grid/gridmodel/_aux_add_slack.py b/lightsim2grid/gridmodel/_aux_add_slack.py index 17b9c838..cfa66f08 100644 --- a/lightsim2grid/gridmodel/_aux_add_slack.py +++ b/lightsim2grid/gridmodel/_aux_add_slack.py @@ -103,8 +103,8 @@ def _aux_add_slack(model, pp_net, pp_to_ls): gen_p = np.concatenate((pp_net.gen["p_mw"].values, slack_contrib)) gen_v = np.concatenate((pp_net.gen["vm_pu"].values, vm_pu)) gen_bus = np.concatenate((pp_bus_to_ls(pp_net.gen["bus"].values, pp_to_ls), slack_bus_ids)) - gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999.])) - gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999.])) + gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999. for _ in range(nb_slack)])) + gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999. for _ in range(nb_slack)])) model.init_generators(gen_p, gen_v, gen_min_q, gen_max_q, gen_bus) # handle the possible distributed slack bus diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index f91d869c..cd38e072 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -462,8 +462,12 @@ def _aux_pypowsybl_init_substations(self, loader_kwargs): orig_to_ls = np.array(self._grid._orig_to_ls) bus_doubled = np.concatenate([bus_init for _ in range(self.n_busbar_per_sub)]) self._grid.init_bus(bus_doubled, 0, 0) - for i in range(self.__nb_bus_before): - self._grid.deactivate_bus(i + self.__nb_bus_before) + if hasattr(type(self), "can_handle_more_than_2_busbar"): + for i in range(self.__nb_bus_before * (self.n_busbar_per_sub - 1)): + self._grid.deactivate_bus(i + self.__nb_bus_before) + else: + for i in range(self.__nb_bus_before): + self._grid.deactivate_bus(i + self.__nb_bus_before) new_orig_to_ls = np.concatenate([orig_to_ls + i * self.__nb_bus_before for i in range(self.n_busbar_per_sub)] ) @@ -622,7 +626,14 @@ def _load_grid_pandapower(self, path=None, filename=None): self._grid = init(self.init_pp_backend._grid) self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus() - self._aux_setup_right_after_grid_init() + self._aux_setup_right_after_grid_init() + + # deactive the buses that have been added + for bus_id, bus_status in enumerate(self.init_pp_backend._grid.bus["in_service"].values): + if bus_status: + self._grid.reactivate_bus(bus_id) + else: + self._grid.deactivate_bus(bus_id) self.n_line = self.init_pp_backend.n_line self.n_gen = self.init_pp_backend.n_gen @@ -670,11 +681,6 @@ def _load_grid_pandapower(self, path=None, filename=None): 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 diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 7e008be7..6621ed61 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -175,15 +175,27 @@ def test_ac_pf(self): v_ls_ref = self.ref_samecase.ac_pf(1.0 * self.V_init_ac, 10, self.tol) assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for ac: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}" - param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, - transformer_voltage_control_on=False, - no_generator_reactive_limits=True, - phase_shifter_regulation_on=False, - simul_shunt=False, - distributed_slack=False, - provider_parameters={"slackBusSelectionMode": "NAME", - "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} - ) + try: + param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, + transformer_voltage_control_on=False, + no_generator_reactive_limits=True, + phase_shifter_regulation_on=False, + simul_shunt=False, + distributed_slack=False, + provider_parameters={"slackBusSelectionMode": "NAME", + "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} + ) + except TypeError: + param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, + transformer_voltage_control_on=False, + # no_generator_reactive_limits=True, # documented in the doc but apparently fails + phase_shifter_regulation_on=False, + simul_shunt=False, + distributed_slack=False, + provider_parameters={"slackBusSelectionMode": "NAME", + "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} + ) + res_pypow = lf.run_ac(self.network_ref, parameters=param) bus_ref_kv = self.network_ref.get_voltage_levels().loc[self.network_ref.get_buses()["voltage_level_id"].values]["nominal_v"].values v_mag_pypo = self.network_ref.get_buses()["v_mag"].values / bus_ref_kv From c02545c8d877ecfdfe45d2bff41677b2416299f3 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 8 Mar 2024 17:38:52 +0100 Subject: [PATCH 55/66] trying to fix CI yet again --- lightsim2grid/tests/test_init_from_pypowsybl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 6621ed61..17707545 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -190,10 +190,10 @@ def test_ac_pf(self): transformer_voltage_control_on=False, # no_generator_reactive_limits=True, # documented in the doc but apparently fails phase_shifter_regulation_on=False, - simul_shunt=False, + # simul_shunt=False, # documented in the doc but apparently fails distributed_slack=False, provider_parameters={"slackBusSelectionMode": "NAME", - "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} + "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} ) res_pypow = lf.run_ac(self.network_ref, parameters=param) From 0bdc52f733eca283a8610da1fdbde2a371a9b6c0 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 11 Mar 2024 10:16:37 +0100 Subject: [PATCH 56/66] fix new parameters names in pypowsybl leading to broken tests --- .../tests/test_init_from_pypowsybl.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 17707545..398cdb9d 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -177,20 +177,20 @@ def test_ac_pf(self): try: param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, - transformer_voltage_control_on=False, - no_generator_reactive_limits=True, - phase_shifter_regulation_on=False, - simul_shunt=False, - distributed_slack=False, - provider_parameters={"slackBusSelectionMode": "NAME", - "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} - ) + transformer_voltage_control_on=False, + use_reactive_limits=False, + shunt_compensator_voltage_control_on=False, + phase_shifter_regulation_on=False, + distributed_slack=False, + provider_parameters={"slackBusSelectionMode": "NAME", + "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} + ) except TypeError: param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, transformer_voltage_control_on=False, - # no_generator_reactive_limits=True, # documented in the doc but apparently fails + no_generator_reactive_limits=True, # documented in the doc but apparently fails phase_shifter_regulation_on=False, - # simul_shunt=False, # documented in the doc but apparently fails + simul_shunt=False, # documented in the doc but apparently fails distributed_slack=False, provider_parameters={"slackBusSelectionMode": "NAME", "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} From f1992bf4c99a7762a75b5e519e8c358e19f10379 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 12 Mar 2024 17:51:13 +0100 Subject: [PATCH 57/66] some more fixes --- .circleci/config.yml | 4 ++-- CHANGELOG.rst | 2 ++ lightsim2grid/lightSimBackend.py | 12 ++++++++++++ src/element_container/GeneratorContainer.cpp | 2 +- src/element_container/GeneratorContainer.h | 6 ++++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a476e0b2..e13e48c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -367,7 +367,7 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - - run: choco install python --version=3.10 --force + - run: choco install python --version=3.10 --force -y - run: py --version - run: py -m pip install --upgrade pip setuptools wheel - run: py -m pip install virtualenv @@ -392,7 +392,7 @@ jobs: command: | .\venv_test\Scripts\activate cd lightsim2grid\tests - python -m unittest discover -v + py -m unittest discover -v workflows: version: 2.1 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bfa3cd8c..59b41691 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -44,6 +44,8 @@ Change Log - [FIXED] read the docs was broken - [FIXED] a bug when reading a grid from pandapower for multiple slacks when slack are given by the "ext_grid" information. +- [FIXED] a bug in "gridmodel.assign_slack_to_most_connected()" that could throw an error if a + generator with "target_p" == 0. was connected to the most connected bus on the grid - [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this one. - [ADDED] possibility to set / retrieve the names of each elements of the grid. diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index cd38e072..c7dbb52a 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -567,6 +567,18 @@ def _load_grid_pypowsybl(self, path=None, filename=None): self.name_storage = np.array(batt_sub.index) self.name_shunt = np.array(sh_sub.index) + if "reconnect_disco_gen" in loader_kwargs and loader_kwargs["reconnect_disco_gen"]: + for el in self._grid.get_generators(): + if not el.connected: + self._grid.reactivate_gen(el.id) + self._grid.change_bus_gen(el.id, self.gen_to_subid[el.id]) + + if "reconnect_disco_load" in loader_kwargs and loader_kwargs["reconnect_disco_load"]: + for el in self._grid.get_loads(): + if not el.connected: + self._grid.reactivate_load(el.id) + self._grid.change_bus_load(el.id, self.load_to_subid[el.id]) + # complete the other vectors self._compute_pos_big_topo() diff --git a/src/element_container/GeneratorContainer.cpp b/src/element_container/GeneratorContainer.cpp index 25159972..75983014 100644 --- a/src/element_container/GeneratorContainer.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -495,7 +495,7 @@ void GeneratorContainer::gen_p_per_bus(std::vector & res) const { if(!status_[gen_id]) continue; const auto my_bus = bus_id_(gen_id); - res[my_bus] += p_mw_(gen_id); + if (p_mw_(gen_id) > 0.) res[my_bus] += p_mw_(gen_id); } } diff --git a/src/element_container/GeneratorContainer.h b/src/element_container/GeneratorContainer.h index 2dbdb4c9..c8c86e68 100644 --- a/src/element_container/GeneratorContainer.h +++ b/src/element_container/GeneratorContainer.h @@ -196,7 +196,9 @@ class GeneratorContainer: public GenericContainer } } // returns only the gen_id with the highest p that is connected to this bus ! - int assign_slack_bus(int slack_bus_id, const std::vector & gen_p_per_bus, SolverControl & solver_control){ + int assign_slack_bus(int slack_bus_id, + const std::vector & gen_p_per_bus, + SolverControl & solver_control){ const int nb_gen = nb(); int res_gen_id = -1; real_type max_p = -1.; @@ -205,7 +207,7 @@ class GeneratorContainer: public GenericContainer if(!status_[gen_id]) continue; if(bus_id_(gen_id) != slack_bus_id) continue; const real_type p_mw = p_mw_(gen_id); - add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id], solver_control); + if (p_mw > 0.) add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id], solver_control); if((p_mw > max_p) || (res_gen_id == -1) ){ res_gen_id = gen_id; max_p = p_mw; From dc08f106db1dde66b2aba045bfeebca483e8c155 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 14 Mar 2024 09:07:41 +0100 Subject: [PATCH 58/66] more robust implementation of copy --- lightsim2grid/lightSimBackend.py | 106 ++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index c7dbb52a..d649b076 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -53,27 +53,19 @@ def __init__(self, stop_if_load_disco : Optional[bool] = True, stop_if_gen_disco : Optional[bool] = True, ): - try: - # for grid2Op >= 1.7.1 - Backend.__init__(self, - detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures, - can_be_copied=can_be_copied, - solver_type=solver_type, - max_iter=max_iter, - tol=tol, - turned_off_pv=turned_off_pv, - dist_slack_non_renew=dist_slack_non_renew, - use_static_gen=use_static_gen, - loader_method=loader_method, - loader_kwargs=loader_kwargs, - stop_if_load_disco=stop_if_load_disco, - stop_if_gen_disco=stop_if_gen_disco, - ) - except TypeError as exc_: - warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, " - "you cannot set max_iter, tol nor solver_type arguments.") - Backend.__init__(self, - detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + + self._aux_init_super(detailed_infos_for_cascading_failures, + can_be_copied, + solver_type, + max_iter, + tol, + turned_off_pv, + dist_slack_non_renew, + use_static_gen, + loader_method, + loader_kwargs, + stop_if_load_disco, + stop_if_gen_disco) # lazy loading because it crashes... from lightsim2grid._utils import _DoNotUseAnywherePandaPowerBackend @@ -222,6 +214,41 @@ def __init__(self, # add the static gen to the list of controlable gen in grid2Op self._use_static_gen = use_static_gen # TODO implement it + def _aux_init_super(self, + detailed_infos_for_cascading_failures, + can_be_copied, + solver_type, + max_iter, + tol, + turned_off_pv, + dist_slack_non_renew, + use_static_gen, + loader_method, + loader_kwargs, + stop_if_load_disco, + stop_if_gen_disco): + try: + # for grid2Op >= 1.7.1 + Backend.__init__(self, + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures, + can_be_copied=can_be_copied, + solver_type=solver_type, + max_iter=max_iter, + tol=tol, + turned_off_pv=turned_off_pv, + dist_slack_non_renew=dist_slack_non_renew, + use_static_gen=use_static_gen, + loader_method=loader_method, + loader_kwargs=loader_kwargs, + stop_if_load_disco=stop_if_load_disco, + stop_if_gen_disco=stop_if_gen_disco, + ) + except TypeError as exc_: + warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, " + "you cannot set max_iter, tol nor solver_type arguments.") + Backend.__init__(self, + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures) + def turnedoff_no_pv(self): self._turned_off_pv = False self._grid.turnedoff_no_pv() @@ -1140,26 +1167,45 @@ def copy(self): #################### # res = copy.deepcopy(self) # super slow res = type(self).__new__(type(self)) + res._aux_init_super(self.detailed_infos_for_cascading_failures, + self._can_be_copied, + self.__current_solver_type, + self.max_it, + self.tol, + self._turned_off_pv, + self._dist_slack_non_renew, + self._use_static_gen, + self._loader_method, + self._loader_kwargs, + self._stop_if_load_disco, + self._stop_if_gen_disco) res.comp_time = self.comp_time res.timer_gridmodel_xx_pf = self.timer_gridmodel_xx_pf # copy the regular attribute res.__has_storage = self.__has_storage - res.__current_solver_type = self.__current_solver_type + # res.__current_solver_type = self.__current_solver_type res.__nb_powerline = self.__nb_powerline res.__nb_bus_before = self.__nb_bus_before - res._can_be_copied = self._can_be_copied + # res._can_be_copied = self._can_be_copied res.cst_1 = dt_float(1.0) - li_regular_attr = ["detailed_infos_for_cascading_failures", "comp_time", "can_output_theta", "_is_loaded", + # li_regular_attr = ["detailed_infos_for_cascading_failures", "comp_time", "can_output_theta", "_is_loaded", + # "nb_bus_total", "initdc", + # "_big_topo_to_obj", "max_it", "tol", "dim_topo", + # "_idx_hack_storage", + # "_timer_preproc", "_timer_postproc", "_timer_solver", + # "_my_kwargs", "supported_grid_format", + # "_turned_off_pv", "_dist_slack_non_renew", + # "_loader_method", "_loader_kwargs", + # "_missing_two_busbars_support_info", "n_busbar_per_sub", + # "_use_static_gen", "_stop_if_load_disco", "_stop_if_gen_disco" + # ] + li_regular_attr = ["comp_time", "can_output_theta", "_is_loaded", "nb_bus_total", "initdc", - "_big_topo_to_obj", "max_it", "tol", "dim_topo", + "_big_topo_to_obj", "dim_topo", "_idx_hack_storage", "_timer_preproc", "_timer_postproc", "_timer_solver", - "_my_kwargs", "supported_grid_format", - "_turned_off_pv", "_dist_slack_non_renew", - "_loader_method", "_loader_kwargs", - "_missing_two_busbars_support_info", "n_busbar_per_sub", - "_use_static_gen", "_stop_if_load_disco", "_stop_if_gen_disco" + "supported_grid_format", ] for attr_nm in li_regular_attr: if hasattr(self, attr_nm): From 2c8947d01df2ea76e8d5fd77bb5597bbd08cb8b0 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 14 Mar 2024 09:19:48 +0100 Subject: [PATCH 59/66] fixing the previously bugy more robust implementation of copy --- lightsim2grid/lightSimBackend.py | 58 ++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index d649b076..82f72f02 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -54,6 +54,34 @@ def __init__(self, stop_if_gen_disco : Optional[bool] = True, ): + self.max_it = max_iter + self.tol = tol # tolerance for the solver + self._check_suitable_solver_type(solver_type, check_in_avail_solver=False) + self.__current_solver_type = solver_type + + # does the "turned off" generators (including when p=0) + # are pv buses + self._turned_off_pv = turned_off_pv + + # distributed slack, on non renewable gen with P > 0 + self._dist_slack_non_renew = dist_slack_non_renew + + # add the static gen to the list of controlable gen in grid2Op + self._use_static_gen = use_static_gen # TODO implement it + + self._loader_method = loader_method + self._loader_kwargs = loader_kwargs + + #: .. versionadded:: 0.7.6 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected load + self._stop_if_load_disco = stop_if_load_disco + + #: .. versionadded:: 0.7.6 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected generator + self._stop_if_gen_disco = stop_if_gen_disco + self._aux_init_super(detailed_infos_for_cascading_failures, can_be_copied, solver_type, @@ -74,18 +102,7 @@ def __init__(self, if not self.__has_storage: 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._loader_method = loader_method - self._loader_kwargs = loader_kwargs - #: .. versionadded:: 0.7.6 - #: if set to `True` (default) then the backend will raise a - #: BackendError in case of disconnected load - self._stop_if_load_disco = stop_if_load_disco - - #: .. versionadded:: 0.7.6 - #: if set to `True` (default) then the backend will raise a - #: BackendError in case of disconnected generator - self._stop_if_gen_disco = stop_if_gen_disco if loader_method == "pandapower": self.supported_grid_format = ("json", ) # new in 1.9.6 @@ -123,8 +140,6 @@ def __init__(self, self.init_pp_backend = _DoNotUseAnywherePandaPowerBackend() self.V = None - self.max_it = max_iter - self.tol = tol # tolerance for the solver self.prod_pu_to_kv = None self.load_pu_to_kv = None @@ -190,8 +205,6 @@ def __init__(self, self._timer_postproc = 0. self._timer_preproc = 0. self._timer_solver = 0. - self._check_suitable_solver_type(solver_type, check_in_avail_solver=False) - self.__current_solver_type = solver_type # hack for the storage unit: # in grid2op, for simplicity, I suppose that if a storage is alone on a busbar, and @@ -204,15 +217,7 @@ def __init__(self, # backend SHOULD not do these kind of stuff self._idx_hack_storage = [] - # does the "turned off" generators (including when p=0) - # are pv buses - self._turned_off_pv = turned_off_pv - - # distributed slack, on non renewable gen with P > 0 - self._dist_slack_non_renew = dist_slack_non_renew - - # add the static gen to the list of controlable gen in grid2Op - self._use_static_gen = use_static_gen # TODO implement it + def _aux_init_super(self, detailed_infos_for_cascading_failures, @@ -1184,7 +1189,7 @@ def copy(self): # copy the regular attribute res.__has_storage = self.__has_storage - # res.__current_solver_type = self.__current_solver_type + res.__current_solver_type = self.__current_solver_type # forced here because of special `__` res.__nb_powerline = self.__nb_powerline res.__nb_bus_before = self.__nb_bus_before # res._can_be_copied = self._can_be_copied @@ -1206,6 +1211,9 @@ def copy(self): "_idx_hack_storage", "_timer_preproc", "_timer_postproc", "_timer_solver", "supported_grid_format", + "max_it", "tol", "_turned_off_pv", "_dist_slack_non_renew", + "_use_static_gen", "_loader_method", "_loader_kwargs", + "_stop_if_load_disco", "_stop_if_gen_disco" ] for attr_nm in li_regular_attr: if hasattr(self, attr_nm): From a927c83307fe115693d094f0e04a5bfcd681dcc4 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 14 Mar 2024 09:35:32 +0100 Subject: [PATCH 60/66] trying to fix the CI --- .circleci/config.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e13e48c2..cc6fe65e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -368,10 +368,16 @@ jobs: steps: - checkout - run: choco install python --version=3.10 --force -y - - run: py --version - - run: py -m pip install --upgrade pip setuptools wheel - - run: py -m pip install virtualenv - - run: py -m virtualenv venv_test + - run: C:\Python310\python --version + - run: C:\Python310\python -m pip install --upgrade pip setuptools wheel + - run: C:\Python310\python -m pip install virtualenv + - run: C:\Python310\python -m virtualenv venv_test + - run: + name: "Chekc python / pip version in venv" + command: | + .\venv_test\Scripts\activate + python --version + pip --version - run: name: "Install grid2op from source" command: | @@ -385,14 +391,14 @@ jobs: pip install -U pybind11 git submodule init git submodule update - py setup.py build - py -m pip install -e .[test] + python setup.py build + python -m pip install -e .[test] - run: name: "make tests" command: | .\venv_test\Scripts\activate cd lightsim2grid\tests - py -m unittest discover -v + python -m unittest discover -v workflows: version: 2.1 From ac260ae5ea31f7d532a52b85e0597a9ec73d9c88 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 14 Mar 2024 09:40:27 +0100 Subject: [PATCH 61/66] fixing the backend, trying to fix windows CI --- lightsim2grid/lightSimBackend.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 82f72f02..ccc7058d 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, RTE (https://www.rte-france.com) +# Copyright (c) 2020-2024, RTE (https://www.rte-france.com) # See AUTHORS.txt # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, @@ -1172,6 +1172,8 @@ def copy(self): #################### # res = copy.deepcopy(self) # super slow res = type(self).__new__(type(self)) + # make sure to init the "base class" + # in particular with "new" attributes in future grid2op Backend res._aux_init_super(self.detailed_infos_for_cascading_failures, self._can_be_copied, self.__current_solver_type, @@ -1192,19 +1194,7 @@ def copy(self): res.__current_solver_type = self.__current_solver_type # forced here because of special `__` res.__nb_powerline = self.__nb_powerline res.__nb_bus_before = self.__nb_bus_before - # res._can_be_copied = self._can_be_copied res.cst_1 = dt_float(1.0) - # li_regular_attr = ["detailed_infos_for_cascading_failures", "comp_time", "can_output_theta", "_is_loaded", - # "nb_bus_total", "initdc", - # "_big_topo_to_obj", "max_it", "tol", "dim_topo", - # "_idx_hack_storage", - # "_timer_preproc", "_timer_postproc", "_timer_solver", - # "_my_kwargs", "supported_grid_format", - # "_turned_off_pv", "_dist_slack_non_renew", - # "_loader_method", "_loader_kwargs", - # "_missing_two_busbars_support_info", "n_busbar_per_sub", - # "_use_static_gen", "_stop_if_load_disco", "_stop_if_gen_disco" - # ] li_regular_attr = ["comp_time", "can_output_theta", "_is_loaded", "nb_bus_total", "initdc", "_big_topo_to_obj", "dim_topo", From daf8a9344fe1b4fefa3c55ae8e7c75bf91a551ee Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 15 Mar 2024 11:54:14 +0100 Subject: [PATCH 62/66] ready for version 0.8.0 --- lightsim2grid/lightSimBackend.py | 35 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 1a72af7b..4b297413 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -67,7 +67,33 @@ def __init__(self, # add the static gen to the list of controlable gen in grid2Op self._use_static_gen = use_static_gen # TODO implement it - + + self._loader_method = loader_method + + self._loader_kwargs = loader_kwargs + + #: .. versionadded:: 0.8.0 + #: Which type of grid format can be read by your backend. + #: It is "json" if loaded from pandapower or + #: "xiidm" if loaded from pypowsybl. + self.supported_grid_format = None + if loader_method == "pandapower": + self.supported_grid_format = ("json", ) # new in 0.8.0 + elif loader_method == "pypowsybl": + self.supported_grid_format = ("xiidm", ) # new in 0.8.0 + else: + raise BackendError(f"Uknown loader_metho : '{loader_method}'") + + #: .. versionadded:: 0.8.0 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected load + self._stop_if_load_disco = stop_if_load_disco + + #: .. versionadded:: 0.8.0 + #: if set to `True` (default) then the backend will raise a + #: BackendError in case of disconnected generator + self._stop_if_gen_disco = stop_if_gen_disco + self._aux_init_super(detailed_infos_for_cascading_failures, can_be_copied, solver_type, @@ -88,13 +114,6 @@ def __init__(self, if not self.__has_storage: 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.") - - if loader_method == "pandapower": - self.supported_grid_format = ("json", ) # new in 1.9.6 - elif loader_method == "pypowsybl": - self.supported_grid_format = ("xiidm", ) # new in 1.9.6 - else: - raise BackendError(f"Uknown loader_metho : '{loader_method}'") self.shunts_data_available = True # needs to be self and not type(self) here From db3ef355e4e44a4c5107baf61326f5659175638a Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 15 Mar 2024 13:38:59 +0100 Subject: [PATCH 63/66] fixing some issues --- src/powerflow_algorithm/BaseFDPFAlgo.h | 4 ++++ src/powerflow_algorithm/BaseFDPFAlgo.tpp | 13 ++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/powerflow_algorithm/BaseFDPFAlgo.h b/src/powerflow_algorithm/BaseFDPFAlgo.h index 67b98747..22ca7f5f 100644 --- a/src/powerflow_algorithm/BaseFDPFAlgo.h +++ b/src/powerflow_algorithm/BaseFDPFAlgo.h @@ -51,6 +51,8 @@ class BaseFDPFAlgo: public BaseAlgo // solution of the problem Bp_ = Eigen::SparseMatrix (); // the B prime matrix (size n_pvpq) Bpp_ = Eigen::SparseMatrix(); // the B double prime matrix (size n_pq) + grid_Bp_ = Eigen::SparseMatrix (); // the B prime matrix (size n_pvpq) + grid_Bpp_ = Eigen::SparseMatrix(); // the B double prime matrix (size n_pq) p_ = RealVect(); q_ = RealVect(); need_factorize_ = true; @@ -200,6 +202,8 @@ class BaseFDPFAlgo: public BaseAlgo LinearSolver _linear_solver_Bpp; // solution of the problem + Eigen::SparseMatrix grid_Bp_; + Eigen::SparseMatrix grid_Bpp_; Eigen::SparseMatrix Bp_; // the B prime matrix (size n_pvpq) Eigen::SparseMatrix Bpp_; // the B double prime matrix (size n_pq) RealVect p_; // (size n_pvpq) diff --git a/src/powerflow_algorithm/BaseFDPFAlgo.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp index 2e426cb8..5d6a74d6 100644 --- a/src/powerflow_algorithm/BaseFDPFAlgo.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -77,12 +77,12 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix grid_Bp; - Eigen::SparseMatrix grid_Bpp; - fillBp_Bpp(grid_Bp, grid_Bpp); + // need to extract Bp and Bpp for the whole grid + grid_Bp_ = Eigen::SparseMatrix (); + grid_Bpp_ = Eigen::SparseMatrix(); + fillBp_Bpp(grid_Bp_, grid_Bpp_); } - + // init "my" matrices // fill the solver matrices Bp_ and Bpp_ // Bp_ = Bp[array([pvpq]).T, pvpq].tocsc() @@ -97,12 +97,11 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix pvpq_inv(V.size(), -1); for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id; std::vector pq_inv(V.size(), -1); for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; - fill_sparse_matrices(grid_Bp, grid_Bpp, pvpq_inv, pq_inv, n_pvpq, n_pq); + fill_sparse_matrices(grid_Bp_, grid_Bpp_, pvpq_inv, pq_inv, n_pvpq, n_pq); } V_ = V; // V = V0 From ffcb56481d6bac80cb9749c20ca156a0c3fe2790 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 15 Mar 2024 13:45:03 +0100 Subject: [PATCH 64/66] fixing some issues --- src/powerflow_algorithm/BaseFDPFAlgo.tpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/powerflow_algorithm/BaseFDPFAlgo.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp index 5d6a74d6..acfe9fb7 100644 --- a/src/powerflow_algorithm/BaseFDPFAlgo.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -10,15 +10,15 @@ template bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -75,7 +75,7 @@ bool BaseFDPFAlgo::compute_pf(const Eigen::SparseMatrix (); From 9bb7f7caac9600ad7db42788ac9f9a03591c482e Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 15 Mar 2024 16:34:42 +0100 Subject: [PATCH 65/66] fix a bug introduced by refactoring --- lightsim2grid/lightSimBackend.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 4b297413..a7c63196 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -71,18 +71,6 @@ def __init__(self, self._loader_method = loader_method self._loader_kwargs = loader_kwargs - - #: .. versionadded:: 0.8.0 - #: Which type of grid format can be read by your backend. - #: It is "json" if loaded from pandapower or - #: "xiidm" if loaded from pypowsybl. - self.supported_grid_format = None - if loader_method == "pandapower": - self.supported_grid_format = ("json", ) # new in 0.8.0 - elif loader_method == "pypowsybl": - self.supported_grid_format = ("xiidm", ) # new in 0.8.0 - else: - raise BackendError(f"Uknown loader_metho : '{loader_method}'") #: .. versionadded:: 0.8.0 #: if set to `True` (default) then the backend will raise a @@ -107,6 +95,18 @@ def __init__(self, stop_if_load_disco, stop_if_gen_disco) + #: .. versionadded:: 0.8.0 + #: Which type of grid format can be read by your backend. + #: It is "json" if loaded from pandapower or + #: "xiidm" if loaded from pypowsybl. + self.supported_grid_format = None + if loader_method == "pandapower": + self.supported_grid_format = ("json", ) # new in 0.8.0 + elif loader_method == "pypowsybl": + self.supported_grid_format = ("xiidm", ) # new in 0.8.0 + else: + raise BackendError(f"Uknown loader_metho : '{loader_method}'") + # lazy loading because it crashes... from lightsim2grid._utils import _DoNotUseAnywherePandaPowerBackend from grid2op.Space import GridObjects # lazy import From cdba82afd7d934154581330358f2dd2ef5a91849 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 18 Mar 2024 09:23:21 +0100 Subject: [PATCH 66/66] update changelog [skip ci] --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0815664d..63f0aab9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,7 +18,7 @@ Change Log - maybe have a look at suitesparse "sliplu" tools ? - easier building (get rid of the "make" part) -[0.8.0] 2023-03-15 +[0.8.0] 2023-03-18 -------------------- - [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`). If you previously used `gridmodel.get_Sbus()` to retrieve the Sbus used for DC powerflow, please use