diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 43d53b1..574dd5b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,11 @@ Change Log - [FIXED] some bugs preventing backward compatibility - [FIXED] an issue in the computation of gen_q when intialized with pypowsybl (some overflow cpp side leading to infinite number in gen_q) +- [FIXED] a bug in the "containers" cpp side (wrong bus was assigned) + when elements was disconnected, which lead to wrong computations for + time series or contingency analysis. +- [FIXED] another bug in ContingencyAnalysis (cpp side) leading to wrong computation + when a powerline was disconnected - [ADDED] some information of compilation directly in the cpp module - [ADDED] some information of compilation available in the python `compilation_options` module python side @@ -37,6 +42,8 @@ Change Log - [IMPROVED] speed, by accelerating the reading back of the data (now read only once and then pointers are re used) - [IMPROVED] c++ side avoid allocating memory (which allow to gain speed python side too) +- [IMPROVED] type hinting in `LightSimBackend` for all 'public' methods (most + notably the one used by grid2op) [0.8.0] 2024-03-18 -------------------- diff --git a/lightsim2grid/compilation_options/__init__.py b/lightsim2grid/compilation_options/__init__.py new file mode 100644 index 0000000..cdcb8df --- /dev/null +++ b/lightsim2grid/compilation_options/__init__.py @@ -0,0 +1,37 @@ +# Copyright (c) 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, +# 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__ = ["klu_solver_available", + "nicslu_solver_available", + "cktso_solver_available", + "compiled_march_native", + "compiled_o3_optim", + "version", + "lightsim2grid_lib"] + +from lightsim2grid_cpp import klu_solver_available +from lightsim2grid_cpp import nicslu_solver_available +from lightsim2grid_cpp import cktso_solver_available +from lightsim2grid_cpp import compiled_march_native +from lightsim2grid_cpp import compiled_o3_optim +from lightsim2grid_cpp import version +from lightsim2grid_cpp import __file__ as lightsim2grid_lib + +try: + from lightsim2grid_cpp import nicslu_lib + __all__.append("nicslu_lib") +except ImportError : + # NICSLU linear solver is not available + pass + +try: + from lightsim2grid_cpp import cktso_lib + __all__.append("cktso_lib") +except ImportError : + # CKTSO linear solver is not available + pass diff --git a/lightsim2grid/contingencyAnalysis.py b/lightsim2grid/contingencyAnalysis.py index 77b5c79..9391a63 100644 --- a/lightsim2grid/contingencyAnalysis.py +++ b/lightsim2grid/contingencyAnalysis.py @@ -110,10 +110,15 @@ def all_contingencies(self, val): raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` " "or `add_multiple_contingencies`.") - def update_grid(self, backend_act): + # TODO implement that ! + def __update_grid(self, backend_act): self.clear(with_contlist=False) self._ls_backend.apply_action(backend_act) - self.computer = ContingencyAnalysisCPP(self._ls_backend._grid) + # run the powerflow + self._ls_backend.runpf() + # update the computer + # self.computer = ContingencyAnalysisCPP(self._ls_backend._grid) + # self.computer.update_grid(...) # not implemented def clear(self, with_contlist=True): """ @@ -320,7 +325,7 @@ def compute_V(self): been entered. Please use `get_flows()` method for easier reading back of the results """ - v_init = self._ls_backend.V + v_init = 1. * self._ls_backend.V self.computer.compute(v_init, self._ls_backend.max_it, self._ls_backend.tol) diff --git a/lightsim2grid/gridmodel/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py index 78293cd..220f4e1 100644 --- a/lightsim2grid/gridmodel/initGridModel.py +++ b/lightsim2grid/gridmodel/initGridModel.py @@ -15,6 +15,9 @@ import numpy as np from numbers import Number import warnings + +import pandapower +import lightsim2grid from lightsim2grid_cpp import GridModel, PandaPowerConverter from lightsim2grid.gridmodel._aux_add_sgen import _aux_add_sgen from lightsim2grid.gridmodel._aux_add_load import _aux_add_load @@ -28,7 +31,7 @@ from lightsim2grid.gridmodel._aux_add_dc_line import _aux_add_dc_line -def init(pp_net): +def init(pp_net: "pandapower.auxiliary.pandapowerNet") -> GridModel: """ Convert a pandapower network as input into a GridModel. @@ -53,7 +56,7 @@ def init(pp_net): Parameters ---------- - pp_net: :class:`pandapower.grid` + pp_net: :class:`pandapower.auxiliary.pandapowerNet` The initial pandapower network you want to convert Returns diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index 8b9e92d..648f0b6 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -6,13 +6,21 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. +import os import copy -from typing import Optional, Union +from typing import Tuple, Optional, Any, Dict, Union +try: + from typing import Self +except ImportError: + # python version is probably bellow 3.11 + from typing_extensions import Self + import warnings import numpy as np import pandas as pd import time +import grid2op from grid2op.Action import CompleteAction from grid2op.Backend import Backend from grid2op.Exceptions import BackendError @@ -296,7 +304,7 @@ def turnedoff_pv(self): self._turned_off_pv = True self._grid.turnedoff_pv() - def get_theta(self): + def get_theta(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Returns @@ -344,7 +352,7 @@ def get_solver_types(self) -> Union[SolverType, SolverType]: """ return self._grid.get_solver_type(), self._grid.get_dc_solver_type() - def set_solver_type(self, solver_type: SolverType): + def set_solver_type(self, solver_type: SolverType) -> None: """ Change the type of solver you want to use. @@ -386,7 +394,7 @@ def _check_suitable_solver_type(self, solver_type, check_in_avail_solver=True): raise BackendError(f"The solver type provided \"{solver_type}\" is not available on your system. Available" f"solvers are {self.available_solvers}") - def set_solver_max_iter(self, max_iter): + def set_solver_max_iter(self, max_iter: int) -> None: """ Set the maximum number of iteration the solver is allowed to perform. @@ -419,7 +427,7 @@ def set_solver_max_iter(self, max_iter): raise BackendError("max_iter should be a strictly positive integer (integer >= 1)") self.max_it = max_iter - def set_tol(self, new_tol): + def set_tol(self, new_tol: float) -> None: """ Set the tolerance of the powerflow. This means that the powerflow will stop when the Kirchhoff's Circuit Laws are met up to a tolerance of "new_tol". @@ -469,7 +477,9 @@ def _assign_right_solver(self): else: self._grid.change_solver(SolverType.SparseLU) - def load_grid(self, path=None, filename=None): + def load_grid(self, + path : Union[os.PathLike, str], + filename : Optional[Union[os.PathLike, str]]=None) -> 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() @@ -832,7 +842,7 @@ def _aux_finish_setup_after_reading(self): self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int).reshape(-1) - if type(self).shunts_data_available: + if cls.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) @@ -877,7 +887,7 @@ def _aux_finish_setup_after_reading(self): self.__init_topo_vect[:] = self.topo_vect self.sh_bus[:] = 1 - def assert_grid_correct_after_powerflow(self): + def assert_grid_correct_after_powerflow(self) -> None: """ This method is called by the environment. It ensure that the backend remains consistent even after a powerflow has be run with :func:`Backend.runpf` method. @@ -945,11 +955,11 @@ def _count_object_per_bus(self): arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (arr_[is_connected] - 1) self.nb_obj_per_bus[arr_] += 1 - def close(self): + def close(self) -> None: self.init_pp_backend.close() self._grid = None - def apply_action(self, backendAction): + def apply_action(self, backendAction: Union["grid2op.Action._backendAction._BackendAction", None]) -> None: """ Specific implementation of the method to apply an action modifying a powergrid in the pandapower format. """ @@ -1031,7 +1041,7 @@ def _fetch_grid_data(self): self._shunt_res = self._grid.get_shunts_res_full() self._timer_fetch_data_cpp += time.perf_counter() - beg_test - def runpf(self, is_dc=False): + def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]: my_exc_ = None res = False try: @@ -1217,7 +1227,7 @@ def __deepcopy__(self, memo): memo[id(self)] = result return result - def copy(self): + def copy(self) -> Self: # i can perform a regular copy, everything has been initialized mygrid = self._grid __me_at_init = self.__me_at_init @@ -1341,36 +1351,36 @@ def copy(self): self.__me_at_init = __me_at_init return res - def get_line_status(self): + def get_line_status(self) -> np.ndarray: l_s = self._grid.get_lines_status() t_s = self._grid.get_trafo_status() return np.concatenate((l_s, t_s)).astype(dt_bool) - def get_line_flow(self): + def get_line_flow(self) -> np.ndarray: return self.a_or - def get_topo_vect(self): + def get_topo_vect(self) -> np.ndarray: return 1 * self.topo_vect - def generators_info(self): + def generators_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v - def loads_info(self): + def loads_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: return self.cst_1 * self.load_p, self.cst_1 * self.load_q, self.cst_1 * self.load_v - def lines_or_info(self): + def lines_or_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: return self.cst_1 * self.p_or, self.cst_1 * self.q_or, self.cst_1 * self.v_or, self.cst_1 * self.a_or - def lines_ex_info(self): + def lines_ex_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: return self.cst_1 * self.p_ex, self.cst_1 * self.q_ex, self.cst_1 * self.v_ex, self.cst_1 * self.a_ex - def storages_info(self): + def storages_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: if not self.__has_storage: raise RuntimeError("Storage units are not supported with your grid2op version. Please upgrade to " "grid2op >1.5") return self.cst_1 * self.storage_p, self.cst_1 * self.storage_q, self.cst_1 * self.storage_v - def shunt_info(self): + def shunt_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: return self.cst_1 * self.sh_p, self.cst_1 * self.sh_q, self.cst_1 * self.sh_v, self.sh_bus def _compute_shunt_bus_with_compat(self, shunt_bus): @@ -1404,10 +1414,12 @@ def _disconnect_line(self, id_): else: self._grid.deactivate_trafo(id_ - self.__nb_powerline) - def get_current_solver_type(self): + def get_current_solver_type(self) -> SolverType: return self.__current_solver_type - def reset(self, grid_path, grid_filename=None): + def reset(self, + path : Union[os.PathLike, str], + grid_filename : Optional[Union[os.PathLike, str]]=None) -> None: self._fill_nans() self._grid = self.__me_at_init.copy() self._grid.unset_changes() diff --git a/lightsim2grid/rewards/__init__.py b/lightsim2grid/rewards/__init__.py index 1515cc5..be7dc2a 100644 --- a/lightsim2grid/rewards/__init__.py +++ b/lightsim2grid/rewards/__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. -_ + __all__ = ["N1ContingencyReward"] from lightsim2grid.rewards.n1ContingencyReward import N1ContingencyReward diff --git a/lightsim2grid/rewards/n1ContingencyReward.py b/lightsim2grid/rewards/n1ContingencyReward.py index 7fe755d..7126fc6 100644 --- a/lightsim2grid/rewards/n1ContingencyReward.py +++ b/lightsim2grid/rewards/n1ContingencyReward.py @@ -6,6 +6,9 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. +import time +import numpy as np + from grid2op.Reward import BaseReward from grid2op.Environment import Environment from grid2op.Backend import PandaPowerBackend @@ -45,7 +48,9 @@ def __init__(self, threshold_margin=1., dc=False, normalize=False, - logger=None,): + logger=None, + tol=1e-8, + nb_iter=10): BaseReward.__init__(self, logger=logger) self._backend = None self._backend_action = None @@ -65,6 +70,13 @@ def __init__(self, self._solver_type = SolverType.DC else: self._solver_type = SolverType.SparseLU + self._backend_ls = False + self._tol = tol + self._nb_iter = nb_iter + self._timer_call = 0. + self._timer_pre_proc = 0. + self._timer_compute = 0. + self._timer_post_proc = 0. def initialize(self, env: Environment): if not isinstance(env.backend, (PandaPowerBackend, LightSimBackend)): @@ -73,51 +85,93 @@ def initialize(self, env: Environment): "``PandaPowerBackend` nor `LightSimBackend`." ) if isinstance(env.backend, LightSimBackend): - self._backend = env.backend.copy() + self._backend : LightSimBackend = env.backend.copy() + self._backend_ls :bool = True elif isinstance(env.backend, PandaPowerBackend): from lightsim2grid.gridmodel import init - self._backend = init(env.backend) + gridmodel = init(env.backend._grid) + self._backend = LightSimBackend.init_grid(type(env.backend))() + self._backend._grid = gridmodel else: raise NotImplementedError() - bk_act_cls = _BackendAction.init_grid(env.backend) + self._backend.set_solver_type(self._solver_type) + conv, exc_ = self._backend.runpf() + if not conv: + raise RuntimeError(f"The reward N1ContingencyReward diverge with error {exc_}") + bk_act_cls = _BackendAction.init_grid(type(env.backend)) self._backend_action = bk_act_cls() if self._l_ids is None: self._l_ids = list(range(type(env).n_line)) - + + if len(self._l_ids) == 0: + raise RuntimeError("Impossible to use the N1ContingencyReward " + "without any contingencies !") self.reward_min = 0. - self.reward_max = type(env).n_line if not self._normalize else 1. - self._contingecy_analyzer = ContingencyAnalysis(self._backend) - self._contingecy_analyzer.add_multiple_contingencies(self._l_ids) + self.reward_max = len(self._l_ids) if not self._normalize else 1. + # self._contingecy_analyzer = ContingencyAnalysis(self._backend) + # self._contingecy_analyzer.add_multiple_contingencies(self._l_ids) def __call__(self, action, env, has_error, is_done, is_illegal, is_ambiguous): if is_done: return self.reward_min + beg = time.perf_counter() + # retrieve the state of the grid self._backend_action.reset() act = env.backend.get_action_to_set() - th_lim = env.get_thermal_limit() - th_lim[th_lim <= 1] = 1 # assign 1 for the thermal limit - this_n1 = copy.deepcopy(act) - self._backend_action += this_n1 - + th_lim_a = env.get_thermal_limit() + th_lim_a[th_lim_a <= 1] = 1 # assign 1 for the thermal limit + + # apply it to the backend + self._backend_action += act self._backend.apply_action(self._backend_action) - self._backend._disconnect_line(self.l_id) - div_exc_ = None - try: - # TODO there is a bug in lightsimbackend that make it crash instead of diverging - conv, div_exc_ = self._backend.runpf() - except Exception as exc_: - conv = False - div_exc_ = exc_ - - if conv: - flow = self._backend.get_line_flow() - res = (flow / th_lim).max() + conv, exc_ = self._backend.runpf() + if not conv: + self.logger.warn("Cannot set the backend of the `N1ContingencyReward` => divergence") + return self.reward_min + + # synch the contingency analyzer + # self._contingecy_analyzer.update_grid(self._backend_action) + contingecy_analyzer = ContingencyAnalysis(self._backend) + contingecy_analyzer.add_multiple_contingencies(*self._l_ids) + now_ = time.perf_counter() + self._timer_pre_proc += now_ - beg + tmp = contingecy_analyzer.get_flows() + self.logger.info(f"{contingecy_analyzer.computer.nb_solved()} converging contingencies") + now_2 = time.perf_counter() + self._timer_compute += now_2 - now_ + if self._dc: + # In DC is study p, but take into account q in the limits + res = tmp[0] # this is Por + # now transform the limits in A in MW + por, qor, aor, vor = env.backend.lines_or_info() + p_sq = (th_lim_a * np.sqrt(3.) * vor) - qor + p_sq[p_sq <= 0.] = 0. + limits = np.sqrt(p_sq) else: - self.logger.info(f"Divergence of the backend at step {env.nb_time_step} for N1Reward with error `{div_exc_}`") - res = self.reward_min + res = tmp[1] + limits = th_lim_a + + res = ((res > self._threshold_margin * limits) | (~np.isfinite(res))).any(axis=1) # whether one powerline is above its limit, per cont + # print(res.nonzero()) + # import pdb + # pdb.set_trace() + res = res.sum() # count total of n-1 unsafe + res = len(self._l_ids) - res # reward = things to maximise + if self._normalize: + res /= len(self._l_ids) + now_3 = time.perf_counter() + self._timer_post_proc += now_3 - now_2 + self._timer_call += time.perf_counter() - beg return res + def reset(self, env: "grid2op.Environment.BaseEnv") -> None: + self._timer_call = 0. + self._timer_pre_proc = 0. + self._timer_compute = 0. + self._timer_post_proc = 0. + return super().reset(env) + def close(self): self._backend.close() del self._backend diff --git a/lightsim2grid/tests/test_n1contingencyrewards.py b/lightsim2grid/tests/test_n1contingencyrewards.py new file mode 100644 index 0000000..1be7811 --- /dev/null +++ b/lightsim2grid/tests/test_n1contingencyrewards.py @@ -0,0 +1,160 @@ +# Copyright (c) 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, +# 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 grid2op.Reward import EpisodeDurationReward + +from lightsim2grid import LightSimBackend +from lightsim2grid.rewards import N1ContingencyReward + +TH_LIM_A_REF = np.array([ + 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, + ]) + + +class TestN1ContingencyReward_Base(unittest.TestCase): + def get_env_nm(self): + return "educ_case14_storage" + + def init_backend(self): + return LightSimBackend() + + def is_dc(self): + return False + + def threshold_margin(self): + return 1. + + def l_ids(self): + # return [0] + return None + + def setUp(self) -> None: + reward = N1ContingencyReward(dc=self.is_dc(), + threshold_margin=self.threshold_margin(), + l_ids=self.l_ids()) + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + self.env = grid2op.make(self.get_env_nm(), + test=True, + backend=self.init_backend(), + reward_class=reward, + action_class=CompleteAction, + _add_to_name=type(self).__name__) + params = self.env.parameters + params.MAX_LINE_STATUS_CHANGED = 999999 + params.MAX_SUB_CHANGED = 999999 + params.NB_TIMESTEP_COOLDOWN_LINE = 0 + params.NB_TIMESTEP_COOLDOWN_SUB = 0 + self.env.change_parameters(params) + self.env.change_forecast_parameters(params) + assert (self.env.get_thermal_limit() == TH_LIM_A_REF).all() + self.my_ids = self.l_ids() + if self.my_ids is None: + self.my_ids = list(range(type(self.env).n_line)) + self.env.observation_space.change_reward(EpisodeDurationReward) + return super().setUp() + + def tearDown(self) -> None: + self.env.close() + return super().tearDown() + + def _aux_test_reward(self, obs, reward): + unsafe_cont = 0 + for l_id in self.my_ids: + sim_obs, sim_r, sim_d, sim_i = obs.simulate(self.env.action_space({"set_line_status": [(l_id, -1)]}), + time_step=0) + if np.any(sim_obs.a_or > obs._thermal_limit) or sim_d: + unsafe_cont += 1 + assert reward == (len(self.my_ids) - unsafe_cont), f"{reward} vs {(len(self.my_ids) - unsafe_cont)}" + + def test_do_nothing(self): + obs = self.env.reset() + obs, reward, done, info = self.env.step(self.env.action_space()) + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space()) + self._aux_test_reward(obs, reward) + + def test_disconnected_line(self): + obs = self.env.reset() + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_line_status": [(0, -1)]})) + assert not done + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_line_status": [(0, +1)]})) + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_line_status": [(4, -1)]})) + assert not done + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_line_status": [(4, +1)]})) + self._aux_test_reward(obs, reward) + + def test_modif_topo(self): + obs = self.env.reset() + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_bus": {"substations_id": [(1, (1, 2, 1, 2, 1, 2))]}})) + assert not done + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_bus": {"substations_id": [(3, (1, 2, 1, 2, 1, 2))]}})) + assert not done + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_bus": {"substations_id": [(1, (1, 1, 1, 1, 1, 1))]}})) + assert not done + self._aux_test_reward(obs, reward) + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_bus": {"substations_id": [(3, (1, 1, 1, 1, 1, 1))]}})) + assert not done + self._aux_test_reward(obs, reward) + + def test_copy(self): + obs = self.env.reset() + env_cpy = self.env.copy() + obs_cpy = env_cpy.copy() + assert self.env._reward_helper.template_reward._backend is not env_cpy._reward_helper.template_reward._backend + obs, reward, done, info = self.env.step(self.env.action_space( + {"set_bus": {"substations_id": [(3, (1, 2, 1, 2, 1, 2))]}})) + obs_cpy, reward_cpy, done_cpy, info_cpy = env_cpy.step(self.env.action_space( + {"set_line_status": [(0, -1)]})) + self._aux_test_reward(obs_cpy, reward_cpy) + self._aux_test_reward(obs, reward) + assert reward != reward_cpy + + +# TODO test with only a subset of powerlines +# TODO test with the "margin" +# TODO test with pandapower and lightsim as base backend +# TODO test AC and DC \ No newline at end of file diff --git a/src/batch_algorithm/BaseBatchSolverSynch.h b/src/batch_algorithm/BaseBatchSolverSynch.h index 3d6387d..42c1ae5 100644 --- a/src/batch_algorithm/BaseBatchSolverSynch.h +++ b/src/batch_algorithm/BaseBatchSolverSynch.h @@ -61,6 +61,16 @@ class BaseBatchSolverSynch std::vector available_solvers() const {return _solver.available_solvers(); } SolverType get_solver_type() const {return _solver.get_type(); } + // TODO + // void change_gridmodel(const GridModel & new_grid_model){ + // if(_grid_model.total_bus() != new_grid_model.total_bus()){ + // throw std::runtime_error("BaseBatchSolverSynch: impossible to change the gridmodel. It appears it's not the same grid (not the same number of buses)."); + // } + // CplxVect V = CplxVect::Constant(new_grid_model.total_bus(), 1.04); + // _grid_model = new_grid_model.copy(); // not implemented !!! + + // } + // utlities informations double amps_computation_time() const {return _timer_compute_A;} double solver_time() const {return _timer_solver;} diff --git a/src/batch_algorithm/ContingencyAnalysis.cpp b/src/batch_algorithm/ContingencyAnalysis.cpp index 1118289..abedbd4 100644 --- a/src/batch_algorithm/ContingencyAnalysis.cpp +++ b/src/batch_algorithm/ContingencyAnalysis.cpp @@ -52,6 +52,7 @@ void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ const auto & id_me_to_solver = ac_solver_used ? _grid_model.id_me_to_ac_solver(): _grid_model.id_me_to_dc_solver(); Eigen::Index bus_1_id, bus_2_id; cplx_type y_ff, y_ft, y_tf, y_tt; + bool status; for(const auto & this_cont_id: _li_defaults){ std::vector this_cont_coeffs; this_cont_coeffs.reserve(this_cont_id.size() * 4); // usually there are 4 coeffs per powerlines / trafos @@ -61,6 +62,7 @@ void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ // this is a powerline bus_1_id = id_me_to_solver[powerlines.get_bus_from()[line_id]]; bus_2_id = id_me_to_solver[powerlines.get_bus_to()[line_id]]; + status = powerlines.get_status()[line_id]; if(ac_solver_used){ y_ff = powerlines.yac_ff()[line_id]; y_ft = powerlines.yac_ft()[line_id]; @@ -75,6 +77,7 @@ void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ }else{ // this is a trafo const auto trafo_id = line_id - n_line_; + status = trafos.get_status()[trafo_id]; bus_1_id = id_me_to_solver[trafos.get_bus_from()[trafo_id]]; bus_2_id = id_me_to_solver[trafos.get_bus_to()[trafo_id]]; if(ac_solver_used){ @@ -90,7 +93,7 @@ void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ } } - if(bus_1_id != GenericContainer::_deactivated_bus_id && bus_2_id != GenericContainer::_deactivated_bus_id) + if(status && 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}); @@ -103,15 +106,15 @@ void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ } } - bool ContingencyAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, - const std::vector & coeffs) const + const std::vector & coeffs) const { for(const auto & coeff_to_remove: coeffs){ Ybus.coeffRef(coeff_to_remove.row_id, coeff_to_remove.col_id) -= coeff_to_remove.value; } return check_invertible(Ybus); } + void ContingencyAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, const std::vector & coeffs) const { @@ -180,18 +183,12 @@ void ContingencyAnalysis::compute(const CplxVect & Vinit, int max_iter, real_typ Eigen::Index cont_id = 0; bool conv; CplxVect V; - // int contingency = 0; for(const auto & coeffs_modif: _li_coeffs){ auto timer_modif_Ybus = CustTimer(); bool invertible = remove_from_Ybus(Ybus, coeffs_modif); _timer_modif_Ybus += timer_modif_Ybus.duration(); conv = false; - // I have absolutely no idea why, but if i add this "if" - // which reduces the computation time, it somehow increase it by A LOT ! - // 5.2ms without it vs 81.9ms with it (for the iee 118) - // So better make the computation, even if it's not used... - if(invertible) { V = Vinit_solver; // Vinit is reused for each contingencies diff --git a/src/element_container/GeneratorContainer.h b/src/element_container/GeneratorContainer.h index a47f7f0..8f5e0a0 100644 --- a/src/element_container/GeneratorContainer.h +++ b/src/element_container/GeneratorContainer.h @@ -60,7 +60,7 @@ class GeneratorContainer: public GenericContainer id(-1), name(""), connected(false), - bus_id(-1), + bus_id(_deactivated_bus_id), is_slack(false), slack_weight(-1.0), voltage_regulator_on(false), @@ -82,7 +82,7 @@ class GeneratorContainer: public GenericContainer name = r_data_gen.names_[my_id]; } connected = r_data_gen.status_[my_id]; - bus_id = r_data_gen.bus_id_[my_id]; + if(connected) bus_id = r_data_gen.bus_id_[my_id]; is_slack = r_data_gen.gen_slackbus_[my_id]; slack_weight = r_data_gen.gen_slack_weight_[my_id]; diff --git a/src/element_container/LineContainer.h b/src/element_container/LineContainer.h index 531729f..1fc1470 100644 --- a/src/element_container/LineContainer.h +++ b/src/element_container/LineContainer.h @@ -57,8 +57,8 @@ class LineContainer : public GenericContainer id(my_id), name(""), connected(false), - bus_or_id(-1), - bus_ex_id(-1), + bus_or_id(_deactivated_bus_id), + bus_ex_id(_deactivated_bus_id), r_pu(-1.0), x_pu(-1.0), h_pu(0., 0.), @@ -83,8 +83,8 @@ class LineContainer : public GenericContainer 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); + if(connected) bus_or_id = r_data_line.bus_or_id_.coeff(my_id); + if(connected) bus_ex_id = r_data_line.bus_ex_id_.coeff(my_id); r_pu = r_data_line.powerlines_r_.coeff(my_id); x_pu = r_data_line.powerlines_x_.coeff(my_id); h_or_pu = r_data_line.powerlines_h_or_.coeff(my_id); diff --git a/src/element_container/LoadContainer.h b/src/element_container/LoadContainer.h index 7b41a2c..0dc86cd 100644 --- a/src/element_container/LoadContainer.h +++ b/src/element_container/LoadContainer.h @@ -60,7 +60,7 @@ class LoadContainer : public GenericContainer id(-1), name(""), connected(false), - bus_id(-1), + bus_id(_deactivated_bus_id), target_p_mw(0.), target_q_mvar(0.), has_res(false), @@ -76,7 +76,7 @@ class LoadContainer : public GenericContainer name = r_data_load.names_[my_id]; } connected = r_data_load.status_[my_id]; - bus_id = r_data_load.bus_id_[my_id]; + if(connected) bus_id = r_data_load.bus_id_[my_id]; target_p_mw = r_data_load.p_mw_.coeff(my_id); target_q_mvar = r_data_load.q_mvar_.coeff(my_id); diff --git a/src/element_container/SGenContainer.h b/src/element_container/SGenContainer.h index 549afc0..6a3f831 100644 --- a/src/element_container/SGenContainer.h +++ b/src/element_container/SGenContainer.h @@ -64,7 +64,7 @@ class SGenContainer: public GenericContainer id(-1), name(""), connected(false), - bus_id(-1), + bus_id(_deactivated_bus_id), min_q_mvar(0.), max_q_mvar(0.), min_p_mw(0.), @@ -84,7 +84,7 @@ class SGenContainer: public GenericContainer name = r_data_sgen.names_[my_id]; } connected = r_data_sgen.status_[my_id]; - bus_id = r_data_sgen.bus_id_[my_id]; + if(connected) bus_id = r_data_sgen.bus_id_[my_id]; min_q_mvar = r_data_sgen.q_min_mvar_(my_id); max_q_mvar = r_data_sgen.q_max_mvar_(my_id); diff --git a/src/element_container/ShuntContainer.h b/src/element_container/ShuntContainer.h index e2dee42..59c4a8a 100644 --- a/src/element_container/ShuntContainer.h +++ b/src/element_container/ShuntContainer.h @@ -52,7 +52,7 @@ class ShuntContainer : public GenericContainer id(-1), name(""), connected(false), - bus_id(-1), + bus_id(_deactivated_bus_id), target_p_mw(0.), target_q_mvar(0.), has_res(false), @@ -68,7 +68,7 @@ class ShuntContainer : public GenericContainer name = r_data_shunt.names_[my_id]; } connected = r_data_shunt.status_[my_id]; - bus_id = r_data_shunt.bus_id_[my_id]; + if(connected) bus_id = r_data_shunt.bus_id_[my_id]; target_p_mw = r_data_shunt.p_mw_.coeff(my_id); target_q_mvar = r_data_shunt.q_mvar_.coeff(my_id); diff --git a/src/element_container/TrafoContainer.h b/src/element_container/TrafoContainer.h index 6d6e07f..50338e1 100644 --- a/src/element_container/TrafoContainer.h +++ b/src/element_container/TrafoContainer.h @@ -64,8 +64,8 @@ class TrafoContainer : public GenericContainer id(-1), name(""), connected(false), - bus_hv_id(-1), - bus_lv_id(-1), + bus_hv_id(_deactivated_bus_id), + bus_lv_id(_deactivated_bus_id), r_pu(-1.0), x_pu(-1.0), h_pu(0., 0.), @@ -91,8 +91,8 @@ class TrafoContainer : public GenericContainer 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); + if(connected) bus_hv_id = r_data_trafo.bus_hv_id_.coeff(my_id); + if(connected) bus_lv_id = r_data_trafo.bus_lv_id_.coeff(my_id); r_pu = r_data_trafo.r_.coeff(my_id); x_pu = r_data_trafo.x_.coeff(my_id); h_pu = r_data_trafo.h_.coeff(my_id);