diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 28b8ddfa2..99971d743 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,32 @@ Change Log - [???] "asynch" multienv - [???] properly model interconnecting powerlines + +[1.9.7] - 2023-12-0z +---------------------- +- [BREAKING] removal of the `grid2op/Exceptions/PowerflowExceptions.py` file and move the + `DivergingPowerflow` as part of the BackendException. If you imported (to be avoided) + with `from grid2op.Exceptions.PowerflowExceptions import PowerflowExceptions` + simply do `from grid2op.Exceptions import PowerflowExceptions` and nothing + will change. +- [BREAKING] rename with filename starting with lowercase all the files in the "`Exceptions`", + module. This is both consistent with python practice but allows also to make the + difference between the files in the + module and the class imported. This should have little to no impact on all codes but to "upgrade" + instead of `from grid2op.Exceptions.XXX import PowerflowExceptions` (which you should not have done in the first place) + just do `from grid2op.Exceptions import PowerflowExceptions`. Expect other changes like this for other grid2op modules + in the near future. +- [BREAKING] change the `gridobj_cls.shape()` and `gridobj_cls.dtype()` to `gridobj_cls.shapes()` and `gridobj_cls.dtypes()` + to be more clear when dealing with action_space and observation_space (where `shape` and `dtype` are attribute and not functions) + This change means you can still use `act.shape()` and `act.dtype()` but that `act_space.shape` and `act_space.dtype` are now + clearly properties (and NOT attribute). For the old function `gridobj_cls.dtype()` you can now use `gridobj_cls.dtypes()` +- [FIXED] issue https://github.com/rte-france/Grid2Op/issues/561 (indent issue) +- [FIXED] issue https://github.com/rte-france/Grid2Op/issues/550 : issue with `shunts_data_available` now better handled +- [IMPROVED] the function to check the backend interface now also check that + the `topo_vect` returns value between 1 and 2. +- [IMPROVED] the function to check backend now also check the `topo_vect` + for each type of elements. + [1.9.6] - 2023-10-26 ---------------------- - [BREAKING] when a storage is connected alone on a bus, even if it produces / absorbs 0.0 MW it diff --git a/docs/conf.py b/docs/conf.py index 1972076e8..0c9b4aa62 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 = '1.9.6' +release = '1.9.7.dev0' version = '1.9' diff --git a/grid2op/Action/_backendAction.py b/grid2op/Action/_backendAction.py index 36e3cc733..b5e19022c 100644 --- a/grid2op/Action/_backendAction.py +++ b/grid2op/Action/_backendAction.py @@ -236,7 +236,8 @@ def __init__(self): ) # shunts - if self.shunts_data_available: + cls = type(self) + if cls.shunts_data_available: self.shunt_p = ValueStore(self.n_shunt, dtype=dt_float) self.shunt_q = ValueStore(self.n_shunt, dtype=dt_float) self.shunt_bus = ValueStore(self.n_shunt, dtype=dt_int) @@ -266,7 +267,8 @@ def __deepcopy__(self, memodict={}): res.storage_power.copy(self.storage_power) res.activated_bus[:, :] = self.activated_bus # res.big_topo_to_subid[:] = self.big_topo_to_subid # cste - if self.shunts_data_available: + cls = type(self) + if cls.shunts_data_available: res.shunt_p.copy(self.shunt_p) res.shunt_q.copy(self.shunt_q) res.shunt_bus.copy(self.shunt_bus) @@ -307,7 +309,8 @@ def reorder(self, no_load, no_gen, no_topo, no_storage, no_shunt): self.storage_power.reorder(no_storage) - if self.shunts_data_available: + cls = type(self) + if cls.shunts_data_available: self.shunt_p.reorder(no_shunt) self.shunt_q.reorder(no_shunt) self.shunt_bus.reorder(no_shunt) @@ -331,7 +334,8 @@ def reset(self): self.storage_power.values[:] = 0.0 # shunts - if self.shunts_data_available: + cls = type(self) + if cls.shunts_data_available: self.shunt_p.reset() self.shunt_q.reset() self.shunt_bus.reset() @@ -411,7 +415,7 @@ def __iadd__(self, other): # II shunts if type(self).shunts_data_available: shunts = {} - if other.shunts_data_available: + if type(other).shunts_data_available: shunts["shunt_p"] = other.shunt_p shunts["shunt_q"] = other.shunt_q shunts["shunt_bus"] = other.shunt_bus @@ -515,7 +519,7 @@ def __call__(self): ) topo = self.current_topo shunts = None - if self.shunts_data_available: + if type(self).shunts_data_available: shunts = self.shunt_p, self.shunt_q, self.shunt_bus self._get_active_bus() return self.activated_bus, injections, topo, shunts diff --git a/grid2op/Action/baseAction.py b/grid2op/Action/baseAction.py index 9fa1d6601..6f92ca139 100644 --- a/grid2op/Action/baseAction.py +++ b/grid2op/Action/baseAction.py @@ -440,7 +440,7 @@ def __init__(self): self._subs_impacted = None # shunts - if self.shunts_data_available: + if type(self).shunts_data_available: self.shunt_p = np.full( shape=self.n_shunt, fill_value=np.NaN, dtype=dt_float ) @@ -495,6 +495,12 @@ def copy(self) -> "BaseAction": # sometimes this method is used... return self.__deepcopy__() + def shape(self): + return type(self).shapes() + + def dtype(self): + return type(self).dtypes() + def _aux_copy(self, other): attr_simple = [ "_modif_inj", @@ -524,7 +530,7 @@ def _aux_copy(self, other): "_raise_alert", ] - if self.shunts_data_available: + if type(self).shunts_data_available: attr_vect += ["shunt_p", "shunt_q", "shunt_bus"] for attr_nm in attr_simple: @@ -1050,7 +1056,7 @@ def __eq__(self, other) -> bool: return False # shunts are the same - if self.shunts_data_available: + if type(self).shunts_data_available: if self.n_shunt != other.n_shunt: return False is_ok_me = np.isfinite(self.shunt_p) @@ -1455,7 +1461,7 @@ def reset(self): self._subs_impacted = None # shunts - if self.shunts_data_available: + if type(self).shunts_data_available: self.shunt_p[:] = np.NaN self.shunt_q[:] = np.NaN self.shunt_bus[:] = 0 @@ -1631,7 +1637,7 @@ def __iadd__(self, other): self._assign_iadd_or_warn("_change_bus_vect", me_change) # shunts - if self.shunts_data_available: + if type(self).shunts_data_available: val = other.shunt_p ok_ind = np.isfinite(val) shunt_p = 1.0 * self.shunt_p @@ -1763,7 +1769,7 @@ def __call__(self) -> Tuple[dict, np.ndarray, np.ndarray, np.ndarray, np.ndarray storage_power = self._storage_power # remark: curtailment is handled by an algorithm in the environment, so don't need to be returned here shunts = {} - if self.shunts_data_available: + if type(self).shunts_data_available: shunts["shunt_p"] = self.shunt_p shunts["shunt_q"] = self.shunt_q shunts["shunt_bus"] = self.shunt_bus @@ -1780,7 +1786,7 @@ def __call__(self) -> Tuple[dict, np.ndarray, np.ndarray, np.ndarray, np.ndarray ) def _digest_shunt(self, dict_): - if not self.shunts_data_available: + if not type(self).shunts_data_available: return if "shunt" in dict_: @@ -2664,7 +2670,7 @@ def _check_for_ambiguity(self): "which it is connected. This is ambiguous. You must *set* this bus instead." ) - if self.shunts_data_available: + if type(self).shunts_data_available: if self.shunt_p.shape[0] != self.n_shunt: raise IncorrectNumberOfElements( "Incorrect number of shunt (for shunt_p) in your action." @@ -3396,7 +3402,7 @@ def get_types(self) -> Tuple[bool, bool, bool, bool, bool, bool, bool]: """ injection = "load_p" in self._dict_inj or "prod_p" in self._dict_inj voltage = "prod_v" in self._dict_inj - if self.shunts_data_available: + if type(self).shunts_data_available: voltage = voltage or np.isfinite(self.shunt_p).any() voltage = voltage or np.isfinite(self.shunt_q).any() voltage = voltage or (self.shunt_bus != 0).any() diff --git a/grid2op/Action/voltageOnlyAction.py b/grid2op/Action/voltageOnlyAction.py index 637d87871..996be38e9 100644 --- a/grid2op/Action/voltageOnlyAction.py +++ b/grid2op/Action/voltageOnlyAction.py @@ -40,7 +40,7 @@ def __init__(self): """ BaseAction.__init__(self) - if VoltageOnlyAction._shunt_added is False and self.shunts_data_available: + if VoltageOnlyAction._shunt_added is False and type(self).shunts_data_available: VoltageOnlyAction.attr_list_vect += ["shunt_p", "shunt_q", "shunt_bus"] VoltageOnlyAction.authorized_keys.add("shunt") VoltageOnlyAction._shunt_added = True @@ -61,7 +61,7 @@ def _check_dict(self): """ if self._dict_inj: for el in self._dict_inj: - if el not in self.attr_list_vect: + if el not in type(self).attr_list_vect: raise AmbiguousAction( 'Impossible to modify something different than "prod_v" using ' '"VoltageOnlyAction" action.' diff --git a/grid2op/Backend/backend.py b/grid2op/Backend/backend.py index ba29e5e28..bf291aaf3 100644 --- a/grid2op/Backend/backend.py +++ b/grid2op/Backend/backend.py @@ -25,17 +25,15 @@ from grid2op.dtypes import dt_int, dt_float, dt_bool from grid2op.Exceptions import ( EnvError, - DivergingPowerFlow, IncorrectNumberOfElements, IncorrectNumberOfLoads, -) -from grid2op.Exceptions import ( IncorrectNumberOfGenerators, BackendError, IncorrectNumberOfLines, + DivergingPowerflow, + Grid2OpException, ) from grid2op.Space import GridObjects -from grid2op.Exceptions import Grid2OpException # TODO method to get V and theta at each bus, could be in the same shape as check_kirchoff @@ -941,7 +939,7 @@ def _runpf_with_diverging_exception(self, is_dc : bool) -> Optional[Exception]: Raises ------ - exc_: :class:`grid2op.Exceptions.DivergingPowerFlow` + exc_: :class:`grid2op.Exceptions.DivergingPowerflow` In case of divergence of the powerflow """ @@ -952,13 +950,13 @@ def _runpf_with_diverging_exception(self, is_dc : bool) -> Optional[Exception]: except Grid2OpException as exc_: exc_me = exc_ except Exception as exc_: - exc_me = DivergingPowerFlow( + exc_me = DivergingPowerflow( f" An unexpected error occurred during the computation of the powerflow." f"The error is: \n {exc_} \n. This is game over" ) if not conv and exc_me is None: - exc_me = DivergingPowerFlow( + exc_me = DivergingPowerflow( "GAME OVER: Powerflow has diverged during computation " "or a load has been disconnected or a generator has been disconnected." ) @@ -1820,7 +1818,9 @@ def update_from_obs(self, sh_q[~shunt_co] = np.NaN dict_["shunt"]["shunt_p"] = sh_p dict_["shunt"]["shunt_q"] = sh_q - act.update(dict_) + elif type(self).shunts_data_available and not type(obs).shunts_data_available: + warnings.warn("Backend supports shunt but not the observation. This behaviour is non standard.") + act.update(dict_) backend_action += act self.apply_action(backend_action) @@ -1854,8 +1854,9 @@ def assert_grid_correct(self) -> None: ) # reset the attribute of the grid2op.Backend.Backend class # that can be messed up with depending on the initialization of the backend - Backend._clear_class_attribute() - orig_type._clear_class_attribute() + Backend._clear_class_attribute() # reset totally the grid2op Backend type + # orig_type._clear_class_attribute() + orig_type._clear_grid_dependant_class_attributes() # only reset the attributes that could be modified by user my_cls = type(self) my_cls.my_bk_act_class = _BackendAction.init_grid(my_cls) diff --git a/grid2op/Backend/pandaPowerBackend.py b/grid2op/Backend/pandaPowerBackend.py index a2a296eac..3dcef1d6c 100644 --- a/grid2op/Backend/pandaPowerBackend.py +++ b/grid2op/Backend/pandaPowerBackend.py @@ -107,7 +107,8 @@ class PandaPowerBackend(Backend): # and use "env" as "any open ai gym" environment. """ - + shunts_data_available = True + def __init__( self, detailed_infos_for_cascading_failures : bool=False, @@ -713,7 +714,7 @@ def _init_private_attrs(self) -> None: self._sh_vnkv = self._grid.bus["vn_kv"][self.shunt_to_subid].values.astype( dt_float ) - self.shunts_data_available = True + # self.shunts_data_available = True # TODO shunts_data_available # store the topoid -> objid self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)] diff --git a/grid2op/Chronics/fromOneEpisodeData.py b/grid2op/Chronics/fromOneEpisodeData.py index 556736a09..46e155a09 100644 --- a/grid2op/Chronics/fromOneEpisodeData.py +++ b/grid2op/Chronics/fromOneEpisodeData.py @@ -137,10 +137,10 @@ class FromOneEpisodeData(GridValue): env = grid2op.make(env_name, chronics_class=FromOneEpisodeData, - data_feeding_kwargs={"ep_data": ep_data}, + data_feeding_kwargs={"ep_data": ep_data, + "list_perfect_forecasts": (5, 10, 15)}, opponent_class=FromEpisodeDataOpponent, opponent_attack_cooldown=1, - list_perfect_forecasts=(5, 10, 15) ) # it creates an environment with perfect forecasts available for the next step (5), # the step afterwards (10) and again the following one (15) @@ -327,7 +327,6 @@ def forecasts(self): """ if not self.list_perfect_forecasts: return [] - res = [] for h_id, h in enumerate(self.list_perfect_forecasts): res_d = {} @@ -342,6 +341,7 @@ def forecasts(self): def get_kwargs(self, dict_): dict_["ep_data"] = copy.deepcopy(self._episode_data) + # dict_["list_perfect_forecasts"] = copy.deepcopy(self.list_perfect_forecasts) return dict_ def get_id(self) -> str: diff --git a/grid2op/Converter/BackendConverter.py b/grid2op/Converter/BackendConverter.py index f3ca3868d..44b381a23 100644 --- a/grid2op/Converter/BackendConverter.py +++ b/grid2op/Converter/BackendConverter.py @@ -268,12 +268,18 @@ def _init_myself(self): nm="storage", ) + if type(self.source_backend).shunts_data_available and not type(self.target_backend).shunts_data_available: + raise Grid2OpException("Source backend supports shunts and not target backend. This is not handled") + elif type(self.target_backend).shunts_data_available and not type(self.source_backend).shunts_data_available: + raise Grid2OpException("Target backend supports shunts and not source backend. This is not handled") + + # TODO shunts data available # shunt are available if both source and target provide it - self.shunts_data_available = ( - self.source_backend.shunts_data_available - and self.target_backend.shunts_data_available + type(self).shunts_data_available = ( + type(self.source_backend).shunts_data_available + and type(self.target_backend).shunts_data_available ) - if self.shunts_data_available: + if type(self).shunts_data_available: self._shunt_tg2sr = np.full(self.n_shunt, fill_value=-1, dtype=dt_int) self._shunt_sr2tg = np.full(self.n_shunt, fill_value=-1, dtype=dt_int) # automatic mode @@ -408,11 +414,17 @@ def assert_grid_correct(self): sr_cls.set_env_name(env_name) # handle specifc case of shunt data: - if not self.target_backend.shunts_data_available: + if not type(self.target_backend).shunts_data_available: + if type(self.source_backend).shunts_data_available: + raise Grid2OpException("Impossible to use a converter when one of the backend " + "supports shunt and the other not at the moment.") + # TODO shunts_data_available: you need ideally to create a different class + # for the backend that does not support it. + # disable the shunt data in grid2op. - self.source_backend.shunts_data_available = False - self.source_backend.n_shunt = None - self.source_backend.name_shunt = np.empty(0, dtype=str) + # type(self.source_backend).shunts_data_available = False + # type(self.source_backend).n_shunt = None + # type(self.source_backend).name_shunt = np.empty(0, dtype=str) self._init_class_attr(obj=self.source_backend) if self.path_redisp is not None: @@ -494,7 +506,7 @@ def assert_grid_correct(self): assert np.all(sorted(self._topo_tg2sr[topo_sr2tg_without_storage]) == target_without_storage) self._topo_sr2tg = topo_sr2tg_without_storage - if self.shunts_data_available: + if type(self).shunts_data_available: self._check_both_consistent(self._shunt_tg2sr, self._shunt_sr2tg) # finally check that powergrids are identical (up to the env name) @@ -716,7 +728,7 @@ def get_action_to_set(self): act._set_line_status[:] = act._set_line_status[line_vect] act._switch_line_status[:] = act._switch_line_status[line_vect] - if act.shunt_added and act.shunts_data_available: + if act.shunt_added and type(act).shunts_data_available: shunt_vect = self._shunt_sr2tg act.shunt_p[:] = act.shunt_p[shunt_vect] act.shunt_q[:] = act.shunt_q[shunt_vect] diff --git a/grid2op/Converter/IdToAct.py b/grid2op/Converter/IdToAct.py index 895d6f20d..c1ffd241b 100644 --- a/grid2op/Converter/IdToAct.py +++ b/grid2op/Converter/IdToAct.py @@ -11,7 +11,7 @@ from grid2op.Action import BaseAction from grid2op.Converter.Converters import Converter -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions import Grid2OpException from grid2op.dtypes import dt_float, dt_int, int_types @@ -172,7 +172,8 @@ def init_converter(self, all_actions=None, **kwargs): self.all_actions = [] # add the do nothing action, always self.all_actions.append(super().__call__()) - if "_set_line_status" in self._template_act.attr_list_vect: + tmp_act_cls = type(self._template_act) + if "_set_line_status" in tmp_act_cls.attr_list_vect: # lines 'set' include_ = True if "set_line_status" in kwargs: @@ -180,7 +181,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_line_set(self) - if "_switch_line_status" in self._template_act.attr_list_vect: + if "_switch_line_status" in tmp_act_cls.attr_list_vect: # lines 'change' include_ = True if "change_line_status" in kwargs: @@ -188,7 +189,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_line_change(self) - if "_set_topo_vect" in self._template_act.attr_list_vect: + if "_set_topo_vect" in tmp_act_cls.attr_list_vect: # topologies 'set' include_ = True if "set_topo_vect" in kwargs: @@ -196,7 +197,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_topologies_set(self) - if "_change_bus_vect" in self._template_act.attr_list_vect: + if "_change_bus_vect" in tmp_act_cls.attr_list_vect: # topologies 'change' include_ = True if "change_bus_vect" in kwargs: @@ -204,7 +205,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_topologies_change(self) - if "_redispatch" in self._template_act.attr_list_vect: + if "_redispatch" in tmp_act_cls.attr_list_vect: # redispatch (transformed to discrete variables) include_ = True if "redispatch" in kwargs: @@ -212,7 +213,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_redispatch(self) - if "_curtail" in self._template_act.attr_list_vect: + if "_curtail" in tmp_act_cls.attr_list_vect: # redispatch (transformed to discrete variables) include_ = True if "curtail" in kwargs: @@ -220,7 +221,7 @@ def init_converter(self, all_actions=None, **kwargs): if include_: self.all_actions += self.get_all_unitary_curtail(self) - if "_storage_power" in self._template_act.attr_list_vect: + if "_storage_power" in tmp_act_cls.attr_list_vect: # redispatch (transformed to discrete variables) include_ = True if "storage" in kwargs: diff --git a/grid2op/Environment/_obsEnv.py b/grid2op/Environment/_obsEnv.py index 421ba2490..347aaeaa9 100644 --- a/grid2op/Environment/_obsEnv.py +++ b/grid2op/Environment/_obsEnv.py @@ -9,7 +9,7 @@ import copy import numpy as np import warnings -from grid2op.Exceptions.EnvExceptions import EnvError +from grid2op.Exceptions.envExceptions import EnvError from grid2op.dtypes import dt_int, dt_float, dt_bool from grid2op.Environment.baseEnv import BaseEnv diff --git a/grid2op/Episode/EpisodeReboot.py b/grid2op/Episode/EpisodeReboot.py index 383ae2348..bda5218dd 100644 --- a/grid2op/Episode/EpisodeReboot.py +++ b/grid2op/Episode/EpisodeReboot.py @@ -302,7 +302,7 @@ def _update_bk_act_topo(self, obs): """update the "previous topological state" to the right value""" self.env._backend_action.current_topo.values[:] = obs.topo_vect self.env._backend_action.current_topo.changed[:] = True - if obs.shunts_data_available: + if type(obs).shunts_data_available: self.env._backend_action.shunt_bus.values[:] = obs._shunt_bus self.env._backend_action.shunt_bus.changed[:] = True # TODO previous update self.env._backend_action.last_topo_registered too ! diff --git a/grid2op/Exceptions/PowerflowExceptions.py b/grid2op/Exceptions/PowerflowExceptions.py deleted file mode 100644 index 6575037c4..000000000 --- a/grid2op/Exceptions/PowerflowExceptions.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2019-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 Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. - -from grid2op.Exceptions.Grid2OpException import Grid2OpException - - -# powerflow exception -class DivergingPowerFlow(Grid2OpException): - """ - This exception indicate that the :class:`grid2op.Backend.Backend` is not able to find a valid solution to the - physical _grid it represents. - - This divergence can be due to: - - - the system is not feasible: there is no solution to Kirchhoff's law given the state - - the powergrid is not connex - - there is a "voltage collapse" : the voltages are ill conditioned making the _grid un realistic. - - the method to solve the powerflow fails to find a valid solution. In this case, adopting a different - :class:`grid2op.Backend.Backend` might solve the problem. - """ - - pass diff --git a/grid2op/Exceptions/__init__.py b/grid2op/Exceptions/__init__.py index 2fec308a1..f25ca1d26 100644 --- a/grid2op/Exceptions/__init__.py +++ b/grid2op/Exceptions/__init__.py @@ -38,7 +38,6 @@ "AmbiguousAction", "NonFiniteElement", "AmbiguousActionRaiseAlert", - "DivergingPowerFlow", "BaseObservationError", "NoForecastAvailable", "SimulateError", @@ -62,9 +61,9 @@ "HandlerError" ] -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException -from grid2op.Exceptions.EnvExceptions import (EnvError, +from grid2op.Exceptions.envExceptions import (EnvError, IncorrectNumberOfLoads, IncorrectNumberOfGenerators, IncorrectNumberOfLines, @@ -78,7 +77,7 @@ UnknownEnv, MultiEnvException) -from grid2op.Exceptions.IllegalActionExceptions import (IllegalAction, +from grid2op.Exceptions.illegalActionExceptions import (IllegalAction, OnProduction, VSetpointModified, ActiveSetPointAbovePmax, @@ -89,7 +88,7 @@ UnitCommitorRedispachingNotAvailable, ) -from grid2op.Exceptions.AmbiguousActionExceptions import (NotEnoughGenerators, +from grid2op.Exceptions.ambiguousActionExceptions import (NotEnoughGenerators, GeneratorTurnedOffTooSoon, GeneratorTurnedOnTooSoon, InvalidRedispatching, @@ -106,36 +105,36 @@ NonFiniteElement, AmbiguousActionRaiseAlert) -from grid2op.Exceptions.PowerflowExceptions import DivergingPowerFlow - -from grid2op.Exceptions.ObservationExceptions import (BaseObservationError, +from grid2op.Exceptions.observationExceptions import (BaseObservationError, NoForecastAvailable, SimulateError, SimulateUsedTooMuchThisStep, SimulateUsedTooMuchThisEpisode) -from grid2op.Exceptions.ChronicsExceptions import (ChronicsError, +from grid2op.Exceptions.chronicsExceptions import (ChronicsError, ChronicsNotFoundError, InsufficientData, ) from grid2op.Exceptions.handlers_exceptions import (HandlerError, ) -from grid2op.Exceptions.BackendExceptions import (BackendError, +from grid2op.Exceptions.backendExceptions import (BackendError, DivergingPowerflow, IslandedGrid, IsolatedElement, DisconnectedLoad, DisconnectedGenerator, ) +DivergingPowerFlow = DivergingPowerflow # for compatibility with lightsim2grid + -from grid2op.Exceptions.PlotExceptions import PlotError +from grid2op.Exceptions.plotExceptions import PlotError -from grid2op.Exceptions.OpponentError import OpponentError +from grid2op.Exceptions.opponentError import OpponentError -from grid2op.Exceptions.RunnerError import UsedRunnerError +from grid2op.Exceptions.runnerError import UsedRunnerError -from grid2op.Exceptions.AttentionBudgetExceptions import NotEnoughAttentionBudget +from grid2op.Exceptions.attentionBudgetExceptions import NotEnoughAttentionBudget from grid2op.Exceptions.agentError import AgentError diff --git a/grid2op/Exceptions/agentError.py b/grid2op/Exceptions/agentError.py index ac3afb2db..93f2f65b3 100644 --- a/grid2op/Exceptions/agentError.py +++ b/grid2op/Exceptions/agentError.py @@ -1,4 +1,12 @@ -from grid2op.Exceptions.Grid2OpException import Grid2OpException +# Copyright (c) 2019-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 Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. + +from grid2op.Exceptions.grid2OpException import Grid2OpException # Exception Runner is used twice, not possible on windows / macos due to the way multiprocessing works diff --git a/grid2op/Exceptions/AmbiguousActionExceptions.py b/grid2op/Exceptions/ambiguousActionExceptions.py similarity index 98% rename from grid2op/Exceptions/AmbiguousActionExceptions.py rename to grid2op/Exceptions/ambiguousActionExceptions.py index 608881c61..119627e41 100644 --- a/grid2op/Exceptions/AmbiguousActionExceptions.py +++ b/grid2op/Exceptions/ambiguousActionExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # ambiguous action diff --git a/grid2op/Exceptions/AttentionBudgetExceptions.py b/grid2op/Exceptions/attentionBudgetExceptions.py similarity index 91% rename from grid2op/Exceptions/AttentionBudgetExceptions.py rename to grid2op/Exceptions/attentionBudgetExceptions.py index 5f442a2bb..e4d4563e8 100644 --- a/grid2op/Exceptions/AttentionBudgetExceptions.py +++ b/grid2op/Exceptions/attentionBudgetExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException class NotEnoughAttentionBudget(Grid2OpException): diff --git a/grid2op/Exceptions/BackendExceptions.py b/grid2op/Exceptions/backendExceptions.py similarity index 63% rename from grid2op/Exceptions/BackendExceptions.py rename to grid2op/Exceptions/backendExceptions.py index d980de4ff..297c63d69 100644 --- a/grid2op/Exceptions/BackendExceptions.py +++ b/grid2op/Exceptions/backendExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # Backend @@ -19,7 +19,17 @@ class BackendError(Grid2OpException): class DivergingPowerflow(BackendError): - """Specific error that should be raised when the powerflow diverges + """ + This exception indicate that the :class:`grid2op.Backend.Backend` is not able to find a valid solution to the + physical _grid it represents. + + This divergence can be due to: + + - the system is not feasible: there is no solution to Kirchhoff's law given the state + - the powergrid is not connected and some area of the grid do not have slack buses + - there is a "voltage collapse" : the voltages are ill conditioned making the _grid un realistic. + - the method to solve the powerflow fails to find a valid solution. In this case, adopting a different + :class:`grid2op.Backend.Backend` might solve the problem. """ pass diff --git a/grid2op/Exceptions/ChronicsExceptions.py b/grid2op/Exceptions/chronicsExceptions.py similarity index 94% rename from grid2op/Exceptions/ChronicsExceptions.py rename to grid2op/Exceptions/chronicsExceptions.py index c6a92cdd0..8574c273a 100644 --- a/grid2op/Exceptions/ChronicsExceptions.py +++ b/grid2op/Exceptions/chronicsExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # Chronics diff --git a/grid2op/Exceptions/EnvExceptions.py b/grid2op/Exceptions/envExceptions.py similarity index 98% rename from grid2op/Exceptions/EnvExceptions.py rename to grid2op/Exceptions/envExceptions.py index 08ebf0ef3..33d3f9b30 100644 --- a/grid2op/Exceptions/EnvExceptions.py +++ b/grid2op/Exceptions/envExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # Exception bad environment configured diff --git a/grid2op/Exceptions/Grid2OpException.py b/grid2op/Exceptions/grid2OpException.py similarity index 100% rename from grid2op/Exceptions/Grid2OpException.py rename to grid2op/Exceptions/grid2OpException.py diff --git a/grid2op/Exceptions/handlers_exceptions.py b/grid2op/Exceptions/handlers_exceptions.py index c4278f647..57f65d3eb 100644 --- a/grid2op/Exceptions/handlers_exceptions.py +++ b/grid2op/Exceptions/handlers_exceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.ChronicsExceptions import ChronicsError +from grid2op.Exceptions.chronicsExceptions import ChronicsError class HandlerError(ChronicsError): diff --git a/grid2op/Exceptions/IllegalActionExceptions.py b/grid2op/Exceptions/illegalActionExceptions.py similarity index 97% rename from grid2op/Exceptions/IllegalActionExceptions.py rename to grid2op/Exceptions/illegalActionExceptions.py index 84331fe27..d303152d7 100644 --- a/grid2op/Exceptions/IllegalActionExceptions.py +++ b/grid2op/Exceptions/illegalActionExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # exception bad actions diff --git a/grid2op/Exceptions/ObservationExceptions.py b/grid2op/Exceptions/observationExceptions.py similarity index 97% rename from grid2op/Exceptions/ObservationExceptions.py rename to grid2op/Exceptions/observationExceptions.py index b7ef3817a..c5b954219 100644 --- a/grid2op/Exceptions/ObservationExceptions.py +++ b/grid2op/Exceptions/observationExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException class BaseObservationError(Grid2OpException): diff --git a/grid2op/Exceptions/OpponentError.py b/grid2op/Exceptions/opponentError.py similarity index 89% rename from grid2op/Exceptions/OpponentError.py rename to grid2op/Exceptions/opponentError.py index c904e76c3..623d66179 100644 --- a/grid2op/Exceptions/OpponentError.py +++ b/grid2op/Exceptions/opponentError.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException class OpponentError(Grid2OpException): diff --git a/grid2op/Exceptions/PlotExceptions.py b/grid2op/Exceptions/plotExceptions.py similarity index 91% rename from grid2op/Exceptions/PlotExceptions.py rename to grid2op/Exceptions/plotExceptions.py index c81380ec6..885f0192b 100644 --- a/grid2op/Exceptions/PlotExceptions.py +++ b/grid2op/Exceptions/plotExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # plot error diff --git a/grid2op/Exceptions/RunnerError.py b/grid2op/Exceptions/runnerError.py similarity index 94% rename from grid2op/Exceptions/RunnerError.py rename to grid2op/Exceptions/runnerError.py index 5663a075a..0cfb0df6e 100644 --- a/grid2op/Exceptions/RunnerError.py +++ b/grid2op/Exceptions/runnerError.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException # Exception Runner is used twice, not possible on windows / macos due to the way multiprocessing works diff --git a/grid2op/Exceptions/simulatorExceptions.py b/grid2op/Exceptions/simulatorExceptions.py index 5f88366d8..c750c994d 100644 --- a/grid2op/Exceptions/simulatorExceptions.py +++ b/grid2op/Exceptions/simulatorExceptions.py @@ -6,7 +6,7 @@ # SPDX-License-Identifier: MPL-2.0 # This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. -from grid2op.Exceptions.Grid2OpException import Grid2OpException +from grid2op.Exceptions.grid2OpException import Grid2OpException class SimulatorError(Grid2OpException): diff --git a/grid2op/Observation/baseObservation.py b/grid2op/Observation/baseObservation.py index ca913485f..1c0a259fa 100644 --- a/grid2op/Observation/baseObservation.py +++ b/grid2op/Observation/baseObservation.py @@ -583,7 +583,7 @@ def __init__(self, self._vectorized = None # for shunt (these are not stored!) - if self.shunts_data_available: + if type(self).shunts_data_available: self._shunt_p = np.empty(shape=self.n_shunt, dtype=dt_float) self._shunt_q = np.empty(shape=self.n_shunt, dtype=dt_float) self._shunt_v = np.empty(shape=self.n_shunt, dtype=dt_float) @@ -680,7 +680,7 @@ def _aux_copy(self, other): "curtailment_limit_effective", ] - if self.shunts_data_available: + if type(self).shunts_data_available: attr_vect += ["_shunt_bus", "_shunt_v", "_shunt_q", "_shunt_p"] for attr_nm in attr_simple: @@ -1144,6 +1144,12 @@ def process_grid2op_compat(cls): pass cls.attr_list_set = set(cls.attr_list_vect) + def shape(self): + return type(self).shapes() + + def dtype(self): + return type(self).dtypes() + def reset(self): """ INTERNAL @@ -1220,7 +1226,7 @@ def reset(self): self._connectivity_matrix_ = None self._bus_connectivity_matrix_ = None - if self.shunts_data_available: + if type(self).shunts_data_available: self._shunt_p[:] = np.NaN self._shunt_q[:] = np.NaN self._shunt_v[:] = np.NaN @@ -1324,7 +1330,7 @@ def set_game_over(self, env=None): # overflow self.timestep_overflow[:] = 0 - if self.shunts_data_available: + if type(self).shunts_data_available: self._shunt_p[:] = 0.0 self._shunt_q[:] = 0.0 self._shunt_v[:] = 0.0 @@ -1989,7 +1995,7 @@ def flow_bus_matrix(self, active_flow=True, as_csr_matrix=False): self.line_ex_pos_topo_vect, self.line_ex_to_subid ) - if self.shunts_data_available: + if type(self).shunts_data_available: sh_bus = 1 * self._shunt_bus sh_bus[sh_bus > 0] = ( self.shunt_to_subid[sh_bus > 0] * (sh_bus[sh_bus > 0] - 1) @@ -2013,7 +2019,7 @@ def flow_bus_matrix(self, active_flow=True, as_csr_matrix=False): or_vect = self.p_or ex_vect = self.p_ex stor_vect = self.storage_power - if self.shunts_data_available: + if type(self).shunts_data_available: sh_vect = self._shunt_p else: prod_vect = self.gen_q @@ -2021,7 +2027,7 @@ def flow_bus_matrix(self, active_flow=True, as_csr_matrix=False): or_vect = self.q_or ex_vect = self.q_ex stor_vect = np.zeros(self.n_storage, dtype=dt_float) - if self.shunts_data_available: + if type(self).shunts_data_available: sh_vect = self._shunt_q nb_lor = lor_conn.sum() @@ -2062,7 +2068,7 @@ def flow_bus_matrix(self, active_flow=True, as_csr_matrix=False): ) data[bus_stor] -= map_mat.dot(stor_vect[stor_conn]) - if self.shunts_data_available: + if type(self).shunts_data_available: # handle shunts nb_shunt = sh_conn.sum() if nb_shunt: @@ -3873,7 +3879,7 @@ def _update_attr_backend(self, backend): self.gen_margin_down[:] = 0.0 # handle shunts (if avaialble) - if self.shunts_data_available: + if type(self).shunts_data_available: sh_p, sh_q, sh_v, sh_bus = backend.shunt_info() self._shunt_p[:] = sh_p self._shunt_q[:] = sh_q diff --git a/grid2op/Observation/observationSpace.py b/grid2op/Observation/observationSpace.py index 9e64ca48b..add75c631 100644 --- a/grid2op/Observation/observationSpace.py +++ b/grid2op/Observation/observationSpace.py @@ -10,7 +10,7 @@ import copy import logging import os -from grid2op.Exceptions.EnvExceptions import EnvError +from grid2op.Exceptions.envExceptions import EnvError from grid2op.Observation.serializableObservationSpace import ( SerializableObservationSpace, diff --git a/grid2op/Plot/EpisodeReplay.py b/grid2op/Plot/EpisodeReplay.py index fca8e5f82..77d20d1bd 100644 --- a/grid2op/Plot/EpisodeReplay.py +++ b/grid2op/Plot/EpisodeReplay.py @@ -14,7 +14,7 @@ from grid2op.Episode import EpisodeData from grid2op.Exceptions import Grid2OpException from grid2op.Plot.PlotPyGame import PlotPyGame -from grid2op.Exceptions.PlotExceptions import PyGameQuit +from grid2op.Exceptions.plotExceptions import PyGameQuit try: os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" diff --git a/grid2op/Plot/PlotPyGame.py b/grid2op/Plot/PlotPyGame.py index 2d8fd2788..a58396337 100644 --- a/grid2op/Plot/PlotPyGame.py +++ b/grid2op/Plot/PlotPyGame.py @@ -22,7 +22,7 @@ import time from grid2op.Plot.BasePlot import BasePlot -from grid2op.Exceptions.PlotExceptions import PyGameQuit, PlotError +from grid2op.Exceptions.plotExceptions import PyGameQuit, PlotError try: os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" diff --git a/grid2op/Plot/Plotting.py b/grid2op/Plot/Plotting.py index 6b795ac21..0bf05b490 100644 --- a/grid2op/Plot/Plotting.py +++ b/grid2op/Plot/Plotting.py @@ -11,7 +11,7 @@ from grid2op.Plot.PlotMatplotlib import PlotMatplotlib from grid2op.Plot.PlotPyGame import PlotPyGame -from grid2op.Exceptions.PlotExceptions import PyGameQuit +from grid2op.Exceptions.plotExceptions import PyGameQuit class Plotting: diff --git a/grid2op/Plot/__init__.py b/grid2op/Plot/__init__.py index 6471a313e..220bc2883 100644 --- a/grid2op/Plot/__init__.py +++ b/grid2op/Plot/__init__.py @@ -4,7 +4,7 @@ "PlotPlotly", "PlotPyGame", "Plotting", - "EpisodeReplay", + # "EpisodeReplay", ] from grid2op.Plot.BasePlot import BasePlot @@ -12,7 +12,7 @@ from grid2op.Plot.PlotPlotly import PlotPlotly from grid2op.Plot.PlotPyGame import PlotPyGame from grid2op.Plot.Plotting import Plotting -from grid2op.Plot.EpisodeReplay import EpisodeReplay +# from grid2op.Plot.EpisodeReplay import EpisodeReplay import warnings diff --git a/grid2op/Reward/baseReward.py b/grid2op/Reward/baseReward.py index 2f9dec6dd..ab54b56a6 100644 --- a/grid2op/Reward/baseReward.py +++ b/grid2op/Reward/baseReward.py @@ -176,7 +176,7 @@ def __call__(self, action, env, has_error, is_done, is_illegal, is_ambiguous): An environment instance properly initialized. has_error: ``bool`` - Has there been an error, for example a :class:`grid2op.DivergingPowerFlow` be thrown when the action has + Has there been an error, for example a :class:`grid2op.DivergingPowerflow` be thrown when the action has been implemented in the environment. is_done: ``bool`` diff --git a/grid2op/Space/GridObjects.py b/grid2op/Space/GridObjects.py index 26cffb323..48439dca6 100644 --- a/grid2op/Space/GridObjects.py +++ b/grid2op/Space/GridObjects.py @@ -794,6 +794,117 @@ def _clear_class_attribute(cls): cls.alertable_line_names = [] cls.alertable_line_ids = [] + @classmethod + def _clear_grid_dependant_class_attributes(cls): + cls.glop_version = grid2op.__version__ + cls._PATH_ENV = None + + cls.SUB_COL = 0 + cls.LOA_COL = 1 + cls.GEN_COL = 2 + cls.LOR_COL = 3 + cls.LEX_COL = 4 + cls.STORAGE_COL = 5 + + cls.attr_list_vect = None + cls.attr_list_set = {} + cls.attr_list_json = [] + cls.attr_nan_list_set = set() + + # class been init + cls._IS_INIT = False + + # name of the objects + cls.env_name = "unknown" + cls.name_load = None + cls.name_gen = None + cls.name_line = None + cls.name_sub = None + cls.name_storage = None + + cls.n_gen = -1 + cls.n_load = -1 + cls.n_line = -1 + cls.n_sub = -1 + cls.n_storage = -1 + + cls.sub_info = None + cls.dim_topo = -1 + + # to which substation is connected each element + cls.load_to_subid = None + cls.gen_to_subid = None + cls.line_or_to_subid = None + cls.line_ex_to_subid = None + cls.storage_to_subid = None + + # which index has this element in the substation vector + cls.load_to_sub_pos = None + cls.gen_to_sub_pos = None + cls.line_or_to_sub_pos = None + cls.line_ex_to_sub_pos = None + cls.storage_to_sub_pos = None + + # which index has this element in the topology vector + cls.load_pos_topo_vect = None + cls.gen_pos_topo_vect = None + cls.line_or_pos_topo_vect = None + cls.line_ex_pos_topo_vect = None + cls.storage_pos_topo_vect = None + + # "convenient" way to retrieve information of the grid + cls.grid_objects_types = None + # to which substation each element of the topovect is connected + cls._topo_vect_to_sub = None + + # list of attribute to convert it from/to a vector + cls._vectorized = None + + # redispatch data, not available in all environment + cls.redispatching_unit_commitment_availble = False + cls.gen_type = None + cls.gen_pmin = None + cls.gen_pmax = None + cls.gen_redispatchable = None + cls.gen_max_ramp_up = None + cls.gen_max_ramp_down = None + cls.gen_min_uptime = None + cls.gen_min_downtime = None + cls.gen_cost_per_MW = None # marginal cost (in currency / (power.step) and not in $/(MW.h) it would be $ / (MW.5mins) ) + cls.gen_startup_cost = None # start cost (in currency) + cls.gen_shutdown_cost = None # shutdown cost (in currency) + cls.gen_renewable = None + + # storage unit static data + cls.storage_type = None + cls.storage_Emax = None + cls.storage_Emin = None + cls.storage_max_p_prod = None + cls.storage_max_p_absorb = None + cls.storage_marginal_cost = None + cls.storage_loss = None + cls.storage_charging_efficiency = None + cls.storage_discharging_efficiency = None + + # alarm / alert + cls.assistant_warning_type = None + + # alarms + cls.dim_alarms = 0 + cls.alarms_area_names = [] + cls.alarms_lines_area = {} + cls.alarms_area_lines = [] + + # alerts + cls.dim_alerts = 0 + cls.alertable_line_names = [] + cls.alertable_line_ids = [] + + # shunt data, not available in every backend + cls.n_shunt = None + cls.name_shunt = None + cls.shunt_to_subid = None + @classmethod def _update_value_set(cls): """ @@ -818,7 +929,7 @@ def _raise_error_attr_list_none(self): ``NotImplementedError`` """ - if self.attr_list_vect is None: + if type(self).attr_list_vect is None: raise IncorrectNumberOfElements( "attr_list_vect attribute is not defined for class {}. " "It is not possible to convert it from/to a vector, " @@ -887,7 +998,7 @@ def to_vect(self): self._raise_error_attr_list_none() li_vect = [ self._get_array_from_attr_name(el).astype(dt_float) - for el in self.attr_list_vect + for el in type(self).attr_list_vect ] if li_vect: self._vectorized = np.concatenate(li_vect) @@ -945,7 +1056,7 @@ def from_json(self, dict_): """ # TODO optimization for action or observation, to reduce json size, for example using the see `to_json` - all_keys = self.attr_list_vect + self.attr_list_json + all_keys = type(self).attr_list_vect + type(self).attr_list_json for key, array_ in dict_.items(): if key not in all_keys: raise AmbiguousAction(f'Impossible to recognize the key "{key}"') @@ -977,7 +1088,7 @@ def _convert_to_json(cls, dict_): elif dtype == bool: dict_[attr_nm] = [bool(el) for el in tmp] - def shape(self): + def shapes(self): """ The shapes of all the components of the action, mainly used for gym compatibility is the shape of all part of the action. @@ -1019,11 +1130,11 @@ def shape(self): """ self._raise_error_attr_list_none() res = np.array( - [self._get_array_from_attr_name(el).shape[0] for el in self.attr_list_vect] + [self._get_array_from_attr_name(el).shape[0] for el in type(self).attr_list_vect] ).astype(dt_int) return res - def dtype(self): + def dtypes(self): """ The types of the components of the GridObjects, mainly used for gym compatibility is the shape of all part of the action. @@ -1064,7 +1175,7 @@ def dtype(self): self._raise_error_attr_list_none() res = np.array( - [self._get_array_from_attr_name(el).dtype for el in self.attr_list_vect] + [self._get_array_from_attr_name(el).dtype for el in type(self).attr_list_vect] ) return res @@ -1161,7 +1272,7 @@ def from_vect(self, vect, check_legit=True): self._raise_error_attr_list_none() prev_ = 0 - for attr_nm, sh, dt in zip(self.attr_list_vect, self.shape(), self.dtype()): + for attr_nm, sh, dt in zip(type(self).attr_list_vect, self.shapes(), self.dtypes()): tmp = vect[prev_ : (prev_ + sh)] # TODO a flag that says "default Nan" for example for when attributes are initialized with @@ -1236,7 +1347,7 @@ def size(self): print("The size of the action space is {}".format(env.action_space.size())) """ - res = self.shape().sum(dtype=dt_int) + res = self.shapes().sum(dtype=dt_int) return res @classmethod @@ -1264,7 +1375,7 @@ def _aux_pos_big_topo(cls, vect_to_subid, vect_to_sub_pos): return res def _init_class_attr(self, obj=None): - """init the class attribute from an instance of the class + """Init the class attribute from an instance of the class THIS IS NOT A CLASS ATTR @@ -2703,7 +2814,7 @@ def init_grid(cls, gridobj, force=False, extra_name=None, force_module=None): my_class = GridObjects._build_cls_from_import(name_res, gridobj._PATH_ENV) if my_class is not None: return my_class - + if not gridobj.shunts_data_available: # if you import env for backend # with shunt and without shunt, then @@ -4363,7 +4474,7 @@ class {cls.__name__}({cls._INIT_GRID_CLS.__name__}): grid_layout = {grid_layout_str} # shunt data, not available in every backend - shunts_data_available = {"True" if cls.redispatching_unit_commitment_availble else "False"} + shunts_data_available = {"True" if cls.shunts_data_available else "False"} n_shunt = {cls.n_shunt} name_shunt = np.array([{name_shunt_str}]) shunt_to_subid = {shunt_to_subid_str} diff --git a/grid2op/Space/SerializableSpace.py b/grid2op/Space/SerializableSpace.py index 6beaa5e7f..7aa514a69 100644 --- a/grid2op/Space/SerializableSpace.py +++ b/grid2op/Space/SerializableSpace.py @@ -111,9 +111,9 @@ def __init__(self, gridobj, subtype=object, _init_grid=True): self.global_vars = None - self.shape = self._template_obj.shape() - self.dtype = self._template_obj.dtype() - self.attr_list_vect = copy.deepcopy(self._template_obj.attr_list_vect) + self._shape = self._template_obj.shapes() + self._dtype = self._template_obj.dtypes() + self.attr_list_vect = copy.deepcopy(type(self._template_obj).attr_list_vect) self._to_extract_vect = {} # key: attr name, value: tuple: (beg_, end_, dtype) beg_ = 0 @@ -123,6 +123,14 @@ def __init__(self, gridobj, subtype=object, _init_grid=True): self._to_extract_vect[attr] = (beg_, end_, dtype_) beg_ += size + @property + def shape(self): + return self._shape + + @property + def dtype(self): + return self._dtype + def _custom_deepcopy_for_copy(self, new_obj): RandomObject._custom_deepcopy_for_copy(self, new_obj) @@ -132,10 +140,10 @@ def _custom_deepcopy_for_copy(self, new_obj): new_obj._template_obj = self._template_obj.copy() new_obj.n = self.n new_obj.global_vars = copy.deepcopy(self.global_vars) - new_obj.shape = copy.deepcopy(self.shape) - new_obj.dtype = copy.deepcopy(self.dtype) - new_obj.attr_list_vect = copy.deepcopy(self.attr_list_vect) - new_obj._to_extract_vect = copy.deepcopy(self._to_extract_vect) + new_obj._shape = copy.deepcopy(self._shape) + new_obj._dtype = copy.deepcopy(self._dtype) + new_obj.attr_list_vect = copy.deepcopy(self.attr_list_vect) # TODO is this necessary, that's class attribute I think + new_obj._to_extract_vect = copy.deepcopy(self._to_extract_vect) # TODO is this necessary, that's class attribute I think @staticmethod def from_dict(dict_): diff --git a/grid2op/__init__.py b/grid2op/__init__.py index 6b4eeaacb..e6e0b19be 100644 --- a/grid2op/__init__.py +++ b/grid2op/__init__.py @@ -11,7 +11,7 @@ Grid2Op """ -__version__ = '1.9.6' +__version__ = '1.9.7.dev0' __all__ = [ "Action", diff --git a/grid2op/gym_compat/gym_act_space.py b/grid2op/gym_compat/gym_act_space.py index 57dc8cfc1..8bc428e2e 100644 --- a/grid2op/gym_compat/gym_act_space.py +++ b/grid2op/gym_compat/gym_act_space.py @@ -137,7 +137,7 @@ def __init__(self, env, converter=None, dict_variables=None): self._init_env = None else: raise RuntimeError( - "GymActionSpace must be created with an Environment of an ActionSpace (or a Converter)" + "GymActionSpace must be created with an Environment or an ActionSpace (or a Converter)" ) dict_ = {} # TODO Make sure it works well ! diff --git a/grid2op/tests/BaseBackendTest.py b/grid2op/tests/BaseBackendTest.py index c0cd9ba19..9603e0839 100644 --- a/grid2op/tests/BaseBackendTest.py +++ b/grid2op/tests/BaseBackendTest.py @@ -1560,7 +1560,7 @@ def get_path(self): def setUp(self): self.backend = self.make_backend(detailed_infos_for_cascading_failures=True) - type(self.backend)._clear_class_attribute() + type(self.backend)._clear_grid_dependant_class_attributes() self.path_matpower = self.get_path() self.case_file = self.get_casefile() with warnings.catch_warnings(): @@ -1886,7 +1886,7 @@ def test_set_bus(self): self.skip_if_needed() # print("test_set_bus") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, @@ -1902,7 +1902,7 @@ def test_change_bus(self): self.skip_if_needed() # print("test_change_bus") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, @@ -1917,7 +1917,7 @@ def test_change_bustwice(self): self.skip_if_needed() # print("test_change_bustwice") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, @@ -1939,7 +1939,7 @@ def test_isolate_load(self): self.skip_if_needed() # print("test_isolate_load") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, @@ -1952,7 +1952,7 @@ def test_reco_disco_bus(self): self.skip_if_needed() # print("test_reco_disco_bus") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_case1 = grid2op.make( @@ -1979,7 +1979,7 @@ def test_reco_disco_bus2(self): self.skip_if_needed() # print("test_reco_disco_bus2") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_case2 = grid2op.make( @@ -2006,7 +2006,7 @@ def test_reco_disco_bus3(self): self.skip_if_needed() # print("test_reco_disco_bus3") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_case2 = grid2op.make( @@ -2031,7 +2031,7 @@ def test_reco_disco_bus4(self): self.skip_if_needed() # print("test_reco_disco_bus4") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_case2 = grid2op.make( @@ -2056,7 +2056,7 @@ def test_reco_disco_bus5(self): self.skip_if_needed() # print("test_reco_disco_bus5") backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_case2 = grid2op.make( @@ -2079,7 +2079,6 @@ class BaseTestShuntAction(MakeBackend): def test_shunt_ambiguous_id_incorrect(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() with warnings.catch_warnings(): warnings.filterwarnings("ignore") with grid2op.make( @@ -2088,7 +2087,7 @@ def test_shunt_ambiguous_id_incorrect(self): gamerules_class=AlwaysLegal, action_class=CompleteAction, backend=backend, - _add_to_name=type(self).__name__ + _add_to_name=type(self).__name__ + "_1" ) as env_case2: with self.assertRaises(AmbiguousAction): act = env_case2.action_space({"shunt": {"set_bus": [(0, 2)]}}) @@ -2097,7 +2096,6 @@ def test_shunt_effect(self): self.skip_if_needed() backend1 = self.make_backend() backend2 = self.make_backend() - type(backend1)._clear_class_attribute() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env_ref = grid2op.make( @@ -2106,7 +2104,7 @@ def test_shunt_effect(self): gamerules_class=AlwaysLegal, action_class=CompleteAction, backend=backend1, - _add_to_name=type(self).__name__ + _add_to_name=type(self).__name__ + "_2" ) env_change_q = grid2op.make( "rte_case14_realistic", @@ -2114,7 +2112,7 @@ def test_shunt_effect(self): gamerules_class=AlwaysLegal, action_class=CompleteAction, backend=backend2, - _add_to_name=type(self).__name__ + _add_to_name=type(self).__name__ + "_3" ) param = env_ref.parameters param.NO_OVERFLOW_DISCONNECTION = True @@ -2174,7 +2172,7 @@ class BaseTestResetEqualsLoadGrid(MakeBackend): def setUp(self): backend1 = self.make_backend() backend2 = self.make_backend() - type(backend1)._clear_class_attribute() + type(backend1)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env1 = grid2op.make("rte_case5_example", test=True, backend=backend1, _add_to_name=type(self).__name__) @@ -2307,7 +2305,7 @@ def test_obs_from_same_chronic(self): def test_combined_changes(self): # Unlimited sub changes backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() params = grid2op.Parameters.Parameters() params.MAX_SUB_CHANGED = 999 @@ -2379,7 +2377,7 @@ class BaseTestVoltageOWhenDisco(MakeBackend): def test_this(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") with grid2op.make("rte_case14_realistic", test=True, backend=backend, _add_to_name=type(self).__name__) as env: @@ -2395,7 +2393,7 @@ class BaseTestChangeBusSlack(MakeBackend): def test_change_slack_case14(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, _add_to_name=type(self).__name__) @@ -2442,7 +2440,7 @@ def test_there_are_storage(self): """test the backend properly loaded the storage units""" self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("educ_case14_storage", test=True, backend=backend, _add_to_name=type(self).__name__) @@ -2452,7 +2450,7 @@ def test_storage_action_mw(self): """test the actions are properly implemented in the backend""" self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make("educ_case14_storage", test=True, backend=backend, _add_to_name=type(self).__name__) @@ -2523,7 +2521,7 @@ def test_storage_action_topo(self): param.NB_TIMESTEP_COOLDOWN_SUB = 0 param.NB_TIMESTEP_COOLDOWN_LINE = 0 backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") self.env = grid2op.make( @@ -2672,7 +2670,7 @@ def test_issue_125(self): # https://github.com/rte-france/Grid2Op/issues/125 self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env = grid2op.make("rte_case14_realistic", test=True, backend=backend, _add_to_name=type(self).__name__) @@ -2694,7 +2692,7 @@ def test_issue_125(self): def test_issue_134(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() param = Parameters() param.NB_TIMESTEP_COOLDOWN_LINE = 0 @@ -2772,7 +2770,7 @@ def test_issue_134(self): def test_issue_134_check_ambiguity(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() param = Parameters() param.MAX_LINE_STATUS_CHANGED = 9999 @@ -2802,7 +2800,7 @@ def test_issue_134_check_ambiguity(self): def test_issue_134_withcooldown_forrules(self): self.skip_if_needed() backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() param = Parameters() param.NB_TIMESTEP_COOLDOWN_LINE = 20 @@ -2944,7 +2942,7 @@ def test_issue_134_withcooldown_forrules(self): def test_issue_copyenv(self): # https://github.com/BDonnot/lightsim2grid/issues/10 backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") env1 = grid2op.make("rte_case14_realistic", test=True, backend=backend, _add_to_name=type(self).__name__) @@ -2957,7 +2955,7 @@ def test_issue_copyenv(self): class BaseStatusActions(MakeBackend): def _make_my_env(self): backend = self.make_backend() - type(backend)._clear_class_attribute() + type(backend)._clear_grid_dependant_class_attributes() param = Parameters() param.NB_TIMESTEP_COOLDOWN_LINE = 0 param.NB_TIMESTEP_COOLDOWN_SUB = 0 diff --git a/grid2op/tests/aaa_test_backend_interface.py b/grid2op/tests/aaa_test_backend_interface.py index b320b0d44..16d469b82 100644 --- a/grid2op/tests/aaa_test_backend_interface.py +++ b/grid2op/tests/aaa_test_backend_interface.py @@ -9,6 +9,8 @@ import os import numpy as np import warnings +import grid2op +from grid2op.Backend import Backend from grid2op.tests.helper_path_test import HelperTests, MakeBackend, PATH_DATA from grid2op.Exceptions import BackendError, Grid2OpException @@ -36,7 +38,7 @@ def aux_get_env_name(self): """do not run nor modify ! (used for this test class only)""" return "BasicTest_load_grid_" + type(self).__name__ - def aux_make_backend(self): + def aux_make_backend(self) -> Backend: """do not run nor modify ! (used for this test class only)""" backend = self.make_backend() backend.load_grid(self.get_path(), self.get_casefile()) @@ -230,6 +232,8 @@ def test_06modify_shunt(self): cls = type(backend) if not cls.shunts_data_available: self.skipTest("Your backend does not support shunts") + if cls.n_shunt == 0: + self.skipTest("The grid you used for testing does not contain any shunts") init_shunt_p = np.array([0.0]) init_shunt_q = np.array([-19.]) @@ -577,6 +581,28 @@ def test_13_disco_reco_lines_pf_getter(self): assert not np.allclose(tmp_ex_reco[2][line_id], 0.), f"extremity voltage on connected line {line_id} is > 0." assert not np.allclose(tmp_ex_reco[3][line_id], 0.), f"extremity flow (amps) on connected line {line_id} is > 0." + def _aux_check_topo_vect(self, backend : Backend): + topo_vect = backend.get_topo_vect() + dim_topo = type(backend).dim_topo + assert len(topo_vect) == dim_topo, (f"backend.get_topo_vect() should return a vector of size 'dim_topo' " + f"({dim_topo}) but found size is {len(topo_vect)}. " + f"Remember: shunt are not part of the topo_vect") + assert np.all(topo_vect <= 2), (f"For simple environment, we suppose there are 2 buses per substation / voltage levels. " + f"topo_vect is supposed to give the id of the busbar (in the substation) to " + f"which the element is connected. This cannot be {np.max(topo_vect)}." + f"NB: this test is expected to fail if you test on a grid where more " + f"at least a substation can be split into (strictly) " + f"more than 2 independant buses.") + + assert np.all(topo_vect >= -1), (f"All element of topo_vect should be >= -1 (-1 meaning disconnected), " + f" or if it's a number >= 1 it's the id of the busbar") + + assert np.all(topo_vect != 0), (f"To avoid mixing the 'do not move an element' action and the " + f"id of a busbar, we decided that busbars labelling should start at 1 " + f"and not at 0. So there should not be any component of topo_vect that " + f"equals to 0.") + return topo_vect + def test_14change_topology(self): """try to change the topology of 2 different substations : connect their elements to different busbars and check consistency @@ -599,8 +625,11 @@ def test_14change_topology(self): cls = type(backend) res = backend.runpf(is_dc=False) + + _ = self._aux_check_topo_vect(backend) + if not cls.shunts_data_available: - warnings.warn(f"{type(self.__name__)} test_14change_topology: This test is not performed in depth as your backend does not support shunts") + warnings.warn(f"{type(self).__name__} test_14change_topology: This test is not performed in depth as your backend does not support shunts") else: p_subs, q_subs, p_bus, q_bus, diff_v_bus = backend.check_kirchoff() assert np.allclose(p_subs, 0., atol=3 * self.tol_one), "there are some discrepency in the backend after a powerflow (no modif): kirchoff laws are not met for p (creation or suppression of active). Check the handling of the slack bus(se) maybe ?" @@ -622,7 +651,7 @@ def test_14change_topology(self): assert res[0], "Your powerflow has diverged after the loading of the file, which should not happen" if not cls.shunts_data_available: - warnings.warn(f"{type(self.__name__)} test_14change_topology: This test is not performed in depth as your backend does not support shunts") + warnings.warn(f"{type(self).__name__} test_14change_topology: This test is not performed in depth as your backend does not support shunts") else: p_subs, q_subs, p_bus, q_bus, diff_v_bus = backend.check_kirchoff() assert np.allclose(p_subs, 0., atol=3 * self.tol_one), "there are some discrepency in the backend after a powerflow (modif with no impact): kirchoff laws are not met for p (creation or suppression of active)." @@ -648,7 +677,7 @@ def test_14change_topology(self): res = backend.runpf(is_dc=False) assert res[0], "Your powerflow has diverged after a topology action (but should not). Check `apply_action` for topology" if not cls.shunts_data_available: - warnings.warn(f"{type(self.__name__)} test_14change_topology: This test is not performed in depth as your backend does not support shunts") + warnings.warn(f"{type(self).__name__} test_14change_topology: This test is not performed in depth as your backend does not support shunts") else: p_subs, q_subs, p_bus, q_bus, diff_v_bus = backend.check_kirchoff() assert np.allclose(p_subs, 0., atol=3 * self.tol_one), "there are some discrepency in the backend after a powerflow (modif with a real impact): kirchoff laws are not met for p (creation or suppression of active)." @@ -722,8 +751,8 @@ def test_15_reset(self): assert np.allclose(v2_or, v_or), f"The v_or differ between its original value and after a reset. Check backend.reset()" assert np.allclose(a2_or, a_or), f"The a_or flow differ between its original value and after a reset. Check backend.reset()" - def test_16_isolated_load_make_divergence(self): - """Tests that an isolated load will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_16_isolated_load_stops_computation(self): + """Tests that an isolated load will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -731,6 +760,11 @@ def test_16_isolated_load_make_divergence(self): - backend.runpf() (AC and DC mode) is implemented - backend.apply_action() for topology modification - backend.reset() is implemented + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -746,7 +780,7 @@ def test_16_isolated_load_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated loads in AC." assert res[1] is not None, "When your backend diverges, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") @@ -761,12 +795,12 @@ def test_16_isolated_load_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated loads in DC." assert res[1] is not None, "When your backend diverges, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") - def test_17_isolated_gen_make_divergence(self): - """Tests that an isolated generator will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_17_isolated_gen_stops_computation(self): + """Tests that an isolated generator will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -774,6 +808,11 @@ def test_17_isolated_gen_make_divergence(self): - backend.runpf() (AC and DC mode) is implemented - backend.apply_action() for topology modification - backend.reset() is implemented + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -789,7 +828,7 @@ def test_17_isolated_gen_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated gen." assert res[1] is not None, "When your backend diverges, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") @@ -804,12 +843,12 @@ def test_17_isolated_gen_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated gen." assert res[1] is not None, "When your backend diverges, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") - def test_18_isolated_shunt_make_divergence(self): - """Tests test that an isolated shunt will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_18_isolated_shunt_stops_computation(self): + """Tests test that an isolated shunt will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -819,6 +858,11 @@ def test_18_isolated_shunt_make_divergence(self): - backend.reset() is implemented NB: this test is skipped if your backend does not (yet :-) ) supports shunt + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -838,7 +882,7 @@ def test_18_isolated_shunt_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated shunt." assert res[1] is not None, "When your backend diverges, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") @@ -853,12 +897,12 @@ def test_18_isolated_shunt_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated shunt in DC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend returns `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend returns `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated shunt) should preferably inherit from BackendError") - def test_19_isolated_storage_make_divergence(self): - """Teststest that an isolated storage unit will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_19_isolated_storage_stops_computation(self): + """Teststest that an isolated storage unit will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -868,6 +912,11 @@ def test_19_isolated_storage_make_divergence(self): - backend.reset() is implemented NB: this test is skipped if your backend does not (yet :-) ) supports storage units + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -884,7 +933,7 @@ def test_19_isolated_storage_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated storage units in AC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated storage units) should preferably inherit from BackendError") @@ -898,12 +947,12 @@ def test_19_isolated_storage_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of isolated storage unit." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to isolated storage units) should preferably inherit from BackendError") - def test_20_disconnected_load_make_divergence(self): - """Tests that a disconnected load unit will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_20_disconnected_load_stops_computation(self): + """Tests that a disconnected load unit will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -913,6 +962,11 @@ def test_20_disconnected_load_make_divergence(self): - backend.reset() is implemented NB: this test is skipped if your backend does not (yet :-) ) supports storage units + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -927,7 +981,7 @@ def test_20_disconnected_load_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of disconnected load in AC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to disconnected load) should preferably inherit from BackendError") @@ -942,12 +996,12 @@ def test_20_disconnected_load_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of disconnected load in DC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to disconnected load) should preferably inherit from BackendError") - def test_21_disconnected_gen_make_divergence(self): - """Tests that a disconnected generator will make the method `run_pf` "diverge" (in AC and DC) [behaviour might change in the future] + def test_21_disconnected_gen_stops_computation(self): + """Tests that a disconnected generator will be spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) This test supposes that : @@ -957,6 +1011,11 @@ def test_21_disconnected_gen_make_divergence(self): - backend.reset() is implemented NB: this test is skipped if your backend does not (yet :-) ) supports storage units + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -971,7 +1030,7 @@ def test_21_disconnected_gen_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of disconnected gen in AC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to disconnected gen) should preferably inherit from BackendError") @@ -986,15 +1045,15 @@ def test_21_disconnected_gen_make_divergence(self): assert not res[0], "It is expected (at time of writing) that your backend returns `False` in case of disconnected gen in DC." assert res[1] is not None, "When your backend stops, we expect it throws an exception (second return value)" error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to disconnected gen) should preferably inherit from BackendError") - def test_22_islanded_grid_make_divergence(self): - """Tests that when the grid is split in two different "sub_grid" it makes the runpf diverge both in AC and DC + def test_22_islanded_grid_stops_computation(self): + """Tests that when the grid is split in two different "sub_grid" is spotted by the `run_pf` method and forwarded to grid2op by returining `False, an_exception` (in AC and DC) For information, this is suppose to make a subgrid with substation 5, 11 and 12 on one side - and all the rest on the other. + and all the rest on the other (and works only for educ_case14_storage grid or equivalent). This test supposes that : @@ -1005,6 +1064,11 @@ def test_22_islanded_grid_make_divergence(self): - backend.reset() is implemented NB: this test is skipped if your backend does not (yet :-) ) supports storage units + + .. note:: + Currently this stops the computation of the environment and lead to a game over. + + This behaviour might change in the future. """ self.skip_if_needed() backend = self.aux_make_backend() @@ -1018,7 +1082,7 @@ def test_22_islanded_grid_make_divergence(self): res = backend.runpf(is_dc=False) assert not res[0], "It is expected that your backend return `False` in case of non connected grid in AC." error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to non connected grid) should preferably inherit from BackendError") backend.reset(self.get_path(), self.get_casefile()) @@ -1034,7 +1098,7 @@ def test_22_islanded_grid_make_divergence(self): res = backend.runpf(is_dc=True) assert not res[0], "It is expected that your backend throws an exception inheriting from BackendError in case of non connected grid in DC." error = res[1] - assert isinstance(error, Grid2OpException), "When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value)" + assert isinstance(error, Grid2OpException), f"When your backend return `False`, we expect it throws an exception inheriting from Grid2OpException (second return value), backend returned {type(error)}" if not isinstance(error, BackendError): warnings.warn("The error returned by your backend when it stopped (due to non connected grid) should preferably inherit from BackendError") @@ -1215,4 +1279,256 @@ def test_26_copy(self): p_or_cpy, *_ = backend_cpy.lines_or_info() assert not np.allclose(p_or, p_or_cpy), (f"The p_or for your backend and its copy are identical though one has been modify and not the other. " "It is likely that backend.copy implementation does not perform a deep copy") - \ No newline at end of file + + def test_27_topo_vect_disconnect(self): + """Tests that the topo_vect vector is properly computed in some + atomic cases (disconnection of elements that can be disconnected) + + This test supposes that : + + - backend.load_grid(...) is implemented + - backend.runpf() (AC mode) is implemented + - backend.apply_action() for all types of action + - backend.reset() is implemented + - backend.get_topo_vect() is implemented + + NB: part of this test is skipped if your backend does not support shunts + NB: part of this test is skipped if your backend does not support storage units + or if there are no storage units on the grid you test it on. + + """ + self.skip_if_needed() + backend = self.aux_make_backend() + cls = type(backend) + + res = backend.runpf(is_dc=False) + topo_vect_orig = self._aux_check_topo_vect(backend) + + # disconnect line + line_id = 0 + backend.reset(self.get_path(), self.get_casefile()) + action = type(backend)._complete_action_class() + action.update({"set_line_status": [(line_id, -1)]}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) + res = backend.runpf(is_dc=False) + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"Line {line_id} has been disconnected, yet according to 'topo_vect' " + f"is still connected (origin side) to busbar {topo_vect[cls.line_or_pos_topo_vect[line_id]]}") + assert topo_vect[cls.line_or_pos_topo_vect[line_id]] == -1, error_msg + error_msg = (f"Line {line_id} has been disconnected, yet according to 'topo_vect' " + f"is still connected (ext side) to busbar {topo_vect[cls.line_ex_pos_topo_vect[line_id]]}") + assert topo_vect[cls.line_ex_pos_topo_vect[line_id]] == -1, error_msg + + # disconnect storage + if cls.n_storage > 0: + sto_id = 0 + backend.reset(self.get_path(), self.get_casefile()) + action = type(backend)._complete_action_class() + action.update({"set_bus": {"storages_id": [(sto_id, -1)]}}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) + res = backend.runpf(is_dc=False) + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"Storage {sto_id} has been disconnected, yet according to 'topo_vect' " + f"is still connected (origin side) to busbar {topo_vect[cls.storage_pos_topo_vect[line_id]]}") + assert topo_vect[cls.storage_pos_topo_vect[sto_id]] == -1, error_msg + else: + warnings.warn(f"{type(self).__name__} test_27_topo_vect_disconnect: This test is not performed in depth as your backend does not support storage units (or there are none on the grid)") + + # disconnect shunt + if not cls.shunts_data_available: + warnings.warn(f"{type(self).__name__} test_27_topo_vect_disconnect: This test is not performed in depth as your backend does not support shunts data") + elif cls.n_shunt == 0: + warnings.warn(f"{type(self).__name__} test_27_topo_vect_disconnect The grid you used for testing does not contain any shunt, this test is not performed in depth") + else: + # so cls.shunts_data_available and cls.n_shunt >= 1 + shunt_id = 0 + backend.reset(self.get_path(), self.get_casefile()) + action = type(backend)._complete_action_class() + action.update({"shunt": {"shunt_bus": [(shunt_id, -1)]}}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) + res = backend.runpf(is_dc=False) + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"Disconnecting a shunt should have no impact on the topo_vect vector " + f"as shunt are not taken into account in this") + assert (topo_vect == topo_vect_orig).all(), error_msg + + def _aux_aux_get_line(self, el_id, el_to_subid, line_xx_to_subid): + sub_id = el_to_subid[el_id] + if (line_xx_to_subid == sub_id).sum() >= 2: + return True, np.where(line_xx_to_subid == sub_id)[0][0] + elif (line_xx_to_subid == sub_id).sum() == 1: + return False, np.where(line_xx_to_subid == sub_id)[0][0] + else: + return None + + def _aux_get_lines(self, cls, el_id, el_to_subid): + or_id = None + ex_id = None + + line_or_maybe = self._aux_aux_get_line(el_id, el_to_subid, cls.line_or_to_subid) + if line_or_maybe is not None: + can_do_or, or_id = line_or_maybe + if can_do_or: + return or_id, ex_id + line_ex_maybe = self._aux_aux_get_line(el_id, el_to_subid, cls.line_ex_to_subid) + + if line_ex_maybe is None: + if line_or_maybe is None: + # not possible for this el_id + return None + if not can_do_or: + # only one line to this substation (or side) + return None + else: + # line_ex_maybe is not None + if line_or_maybe is None: + # only one line to this substation (ex side) + return None + can_do_ex, ex_id = line_ex_maybe + if can_do_ex: + # 2 ext lines are connected I return 1 + return None, ex_id + else: + # one "or" and one "ex" I return the or + return or_id, None + + def _aux_check_el_generic(self, backend, busbar_id, + nb_el, el_to_subid, + el_nm, el_key, el_pos_topo_vect): + cls = type(backend) + # try to find a line with which an element of this type can be moved + # we do that to avoid avoid isolated element on the grid + res = None + el_id = -1 + while res is None: + el_id += 1 + if el_id >= nb_el: + break + res = self._aux_get_lines(cls, el_id, el_to_subid) + if res is None: + # there would be an isolated element if I move all element of this type, + # I cannot perform the test, probably a more suitable grid could be used. + warnings.warn(f"{type(self).__name__} test_28_topo_vect_set: The grid you provide does not " + f"allow to set_bus with {el_nm}, because no {el_nm} is " + f"connected to a substation with at least 2 powerlines. " + f"You need to change the powergrid used for this test if you " + f"want to perform it in depth.") + return + + # prepare the action to move the element and the line + or_id, ex_id = res + if or_id is not None: + key_ = "lines_or_id" + val = [(or_id, busbar_id)] + else: + key_ = "lines_ex_id" + val = [(ex_id, busbar_id)] + + action = type(backend)._complete_action_class() + action.update({"set_bus": { + el_key: [(el_id, busbar_id)], # move the element + key_: val # move the line + }}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) # apply the action + res = backend.runpf(is_dc=False) + # now check the topology vector + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"{el_nm} {el_id} has been moved to busbar {busbar_id}, yet according to 'topo_vect' " + f"it is connected to busbar {topo_vect[el_pos_topo_vect[el_id]]}") + + assert topo_vect[el_pos_topo_vect[el_id]] == busbar_id, error_msg + + def test_28_topo_vect_set(self): + """Tests that the topo_vect vector is properly computed in some + atomic cases (moved elements) + + This test supposes that : + + - backend.load_grid(...) is implemented + - backend.runpf() (AC mode) is implemented + - backend.apply_action() for all types of action + - backend.reset() is implemented + - backend.get_topo_vect() is implemented + + """ + self.skip_if_needed() + backend = self.aux_make_backend() + cls = type(backend) + + res = backend.runpf(is_dc=False) + topo_vect_orig = self._aux_check_topo_vect(backend) + + # line or + line_id = 0 + busbar_id = 2 + backend.reset(self.get_path(), self.get_casefile()) + action = type(backend)._complete_action_class() + action.update({"set_bus": {"lines_or_id": [(line_id, busbar_id)]}}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) + res = backend.runpf(is_dc=False) + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"Line {line_id} (or. side) has been moved to busbar {busbar_id}, yet according to 'topo_vect' " + f"is still connected (origin side) to busbar {topo_vect[cls.line_or_pos_topo_vect[line_id]]}") + assert topo_vect[cls.line_or_pos_topo_vect[line_id]] == busbar_id, error_msg + + # line ex + line_id = 0 + busbar_id = 2 + backend.reset(self.get_path(), self.get_casefile()) + action = type(backend)._complete_action_class() + action.update({"set_bus": {"lines_ex_id": [(line_id, busbar_id)]}}) + bk_act = type(backend).my_bk_act_class() + bk_act += action + backend.apply_action(bk_act) + res = backend.runpf(is_dc=False) + topo_vect = self._aux_check_topo_vect(backend) + error_msg = (f"Line {line_id} (ex. side) has been moved to busbar {busbar_id}, yet according to 'topo_vect' " + f"is still connected (ext side) to busbar {topo_vect[cls.line_ex_pos_topo_vect[line_id]]}") + assert topo_vect[cls.line_ex_pos_topo_vect[line_id]] == busbar_id, error_msg + + # load + backend.reset(self.get_path(), self.get_casefile()) + busbar_id = 2 + nb_el = cls.n_load + el_to_subid = cls.load_to_subid + el_nm = "load" + el_key = "loads_id" + el_pos_topo_vect = cls.load_pos_topo_vect + self._aux_check_el_generic(backend, busbar_id, nb_el, el_to_subid, + el_nm, el_key, el_pos_topo_vect) + + # generator + backend.reset(self.get_path(), self.get_casefile()) + busbar_id = 2 + nb_el = cls.n_gen + el_to_subid = cls.gen_to_subid + el_nm = "generator" + el_key = "generators_id" + el_pos_topo_vect = cls.gen_pos_topo_vect + self._aux_check_el_generic(backend, busbar_id, nb_el, el_to_subid, + el_nm, el_key, el_pos_topo_vect) + + # storage + if cls.n_storage > 0: + backend.reset(self.get_path(), self.get_casefile()) + busbar_id = 2 + nb_el = cls.n_storage + el_to_subid = cls.storage_to_subid + el_nm = "storage" + el_key = "storages_id" + el_pos_topo_vect = cls.storage_pos_topo_vect + self._aux_check_el_generic(backend, busbar_id, nb_el, el_to_subid, + el_nm, el_key, el_pos_topo_vect) + else: + warnings.warn(f"{type(self).__name__} test_28_topo_vect_set: This test is not performed in depth as your backend does not support storage units (or there are none on the grid)") + \ No newline at end of file diff --git a/grid2op/tests/test_Action.py b/grid2op/tests/test_Action.py index e9f8bf5b1..5de72f7b9 100644 --- a/grid2op/tests/test_Action.py +++ b/grid2op/tests/test_Action.py @@ -27,9 +27,6 @@ # TODO check that if i set the element of a powerline to -1, then it's working as intended (disconnect both ends) -import pdb - - def _get_action_grid_class(): GridObjects.env_name = "test_action_env" GridObjects.n_gen = 5 @@ -387,7 +384,8 @@ def setUp(self): def tearDown(self): self.authorized_keys = {} - self.gridobj._clear_class_attribute() + type(self.gridobj)._clear_class_attribute() + GridObjects._clear_class_attribute() def test_reset_modified_flags(self): act = self.helper_action.sample() @@ -874,9 +872,9 @@ def test_to_vect(self): tmp[-action.n_gen :] = -1 # compute the "set_bus" vect - id_set = np.where(np.array(action.attr_list_vect) == "_set_topo_vect")[0][0] + id_set = np.where(np.array(type(action).attr_list_vect) == "_set_topo_vect")[0][0] size_before = 0 - for el in action.attr_list_vect[:id_set]: + for el in type(action).attr_list_vect[:id_set]: arr_ = action._get_array_from_attr_name(el) size_before += arr_.shape[0] tmp[size_before : (size_before + action.dim_topo)] = np.array( @@ -941,11 +939,11 @@ def test_to_vect(self): 0, ] ) - id_change = np.where(np.array(action.attr_list_vect) == "_change_bus_vect")[0][ + id_change = np.where(np.array(type(action).attr_list_vect) == "_change_bus_vect")[0][ 0 ] size_before = 0 - for el in action.attr_list_vect[:id_change]: + for el in type(action).attr_list_vect[:id_change]: arr_ = action._get_array_from_attr_name(el) size_before += arr_.shape[0] tmp[size_before : (size_before + action.dim_topo)] = 1.0 * np.array( @@ -1448,11 +1446,11 @@ def test_to_from_vect_action(self): def test_sum_shape_equal_size(self): act = self.helper_action({}) - assert act.size() == np.sum(act.shape()) + assert act.size() == np.sum(act.shapes()) def test_shape_correct(self): act = self.helper_action({}) - assert act.shape().shape == act.dtype().shape + assert act.shapes().shape == act.dtypes().shape def test_redispatching(self): self._skipMissingKey("redispatch") diff --git a/grid2op/tests/test_BackendAction.py b/grid2op/tests/test_BackendAction.py index b773404cd..84486f512 100644 --- a/grid2op/tests/test_BackendAction.py +++ b/grid2op/tests/test_BackendAction.py @@ -72,7 +72,7 @@ def apply_action(self, backendAction=None): if np.any(load_q.changed): tmp_load_q.iloc[load_q.changed] = load_q.values[load_q.changed] - if self.shunts_data_available: + if type(self).shunts_data_available: shunt_p, shunt_q, shunt_bus = shunts__ if np.any(shunt_p.changed): diff --git a/grid2op/tests/test_Observation.py b/grid2op/tests/test_Observation.py index 944653f1b..7019c87fc 100644 --- a/grid2op/tests/test_Observation.py +++ b/grid2op/tests/test_Observation.py @@ -34,10 +34,6 @@ # temporary deactivation of all the failing test until simulate is fixed DEACTIVATE_FAILING_TEST = False -import warnings - -warnings.simplefilter("error") - class TestBasisObsBehaviour(unittest.TestCase): def setUp(self): @@ -50,7 +46,10 @@ def setUp(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore") - self.env = grid2op.make("rte_case14_test", test=True, _add_to_name=type(self).__name__) + self.env = grid2op.make("rte_case14_test", + test=True, + _add_to_name=type(self).__name__) + self.dict_ = { "name_gen": ["gen_1_0", "gen_2_1", "gen_5_2", "gen_7_3", "gen_0_4"], "name_load": [ @@ -992,7 +991,7 @@ def tearDown(self): def test_sum_shape_equal_size(self): obs = self.env.observation_space(self.env) - assert obs.size() == np.sum(obs.shape()) + assert obs.size() == np.sum(obs.shapes()) def test_sub_topology(self): """test the sub_topology function""" @@ -2013,19 +2012,21 @@ def test_observation_space(self): def test_shape_correct(self): obs = self.env.observation_space(self.env) - assert obs.shape().shape == obs.dtype().shape - assert np.all(obs.dtype() == self.dtypes) - assert np.all(obs.shape() == self.shapes) + assert obs.shapes().shape == obs.dtypes().shape + assert np.all(obs.dtypes() == self.dtypes) + assert np.all(obs.shapes() == self.shapes) def test_0_load_properly(self): # this test aims at checking that everything in setUp is working properly, eg that "ObsEnv" class has enough # information for example - pass + assert type(self.env).shunts_data_available def test_1_generating_obs(self): # test that helper_obs is abl to generate a valid observation + assert type(self.env).shunts_data_available obs = self.env.observation_space(self.env) - pass + assert type(self.env).shunts_data_available + assert type(obs).shunts_data_available def test_2_reset(self): # test that helper_obs is abl to generate a valid observation @@ -2033,8 +2034,8 @@ def test_2_reset(self): assert obs.prod_p[0] is not None obs.reset() assert np.all(np.isnan(obs.prod_p)) - assert np.all(obs.dtype() == self.dtypes) - assert np.all(obs.shape() == self.shapes) + assert np.all(obs.dtypes() == self.dtypes) + assert np.all(obs.shapes() == self.shapes) def test_3_reset(self): # test that helper_obs is able to generate a valid observation @@ -2043,15 +2044,15 @@ def test_3_reset(self): assert obs == obs2 obs2.reset() assert np.all(np.isnan(obs2.prod_p)) - assert np.all(obs2.dtype() == self.dtypes) - assert np.all(obs2.shape() == self.shapes) + assert np.all(obs2.dtypes() == self.dtypes) + assert np.all(obs2.shapes() == self.shapes) # assert obs.prod_p is not None def test_shapes_types(self): obs = self.env.observation_space(self.env) - dtypes = obs.dtype() + dtypes = obs.dtypes() assert np.all(dtypes == self.dtypes) - shapes = obs.shape() + shapes = obs.shapes() assert np.all(shapes == self.shapes) def test_4_to_from_vect(self): @@ -2062,8 +2063,8 @@ def test_4_to_from_vect(self): assert vect.shape[0] == obs.size() obs2.reset() obs2.from_vect(vect) - assert np.all(obs.dtype() == self.dtypes) - assert np.all(obs.shape() == self.shapes) + assert np.all(obs.dtypes() == self.dtypes) + assert np.all(obs.shapes() == self.shapes) # TODO there is not reason that these 2 are equal: reset, will erase everything # TODO whereas creating the observation diff --git a/grid2op/tests/test_Runner.py b/grid2op/tests/test_Runner.py index 3f07aa996..d4324015f 100644 --- a/grid2op/tests/test_Runner.py +++ b/grid2op/tests/test_Runner.py @@ -501,6 +501,10 @@ def test_backward_compatibility(self): "1.8.1", # "1.9.0", # this one is bugy I don"t know why "1.9.1", + "1.9.2", + "1.9.3", + "1.9.4", + "1.9.5", ] curr_version = "test_version" assert ( diff --git a/grid2op/tests/test_act_as_serializable_dict.py b/grid2op/tests/test_act_as_serializable_dict.py index e9590714f..f15f6fae1 100644 --- a/grid2op/tests/test_act_as_serializable_dict.py +++ b/grid2op/tests/test_act_as_serializable_dict.py @@ -118,7 +118,7 @@ def _action_setup(self): def tearDown(self): self.authorized_keys = {} - self.gridobj._clear_class_attribute() + type(self.gridobj)._clear_class_attribute() ActionSpace._clear_class_attribute() def setUp(self): diff --git a/grid2op/tests/test_env_diff_format.py b/grid2op/tests/test_env_diff_format.py index ea1f7e3f3..d8cd88456 100644 --- a/grid2op/tests/test_env_diff_format.py +++ b/grid2op/tests/test_env_diff_format.py @@ -46,7 +46,7 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): return backend def setUp(self): - self.tests_skipped = ("test_01load_grid", "test_22_islanded_grid_make_divergence") + self.tests_skipped = ("test_01load_grid", "test_22_islanded_grid_stops_computation") return super().setUp() diff --git a/grid2op/tests/test_issue_164.py b/grid2op/tests/test_issue_164.py index 6af4c210b..c9817affc 100644 --- a/grid2op/tests/test_issue_164.py +++ b/grid2op/tests/test_issue_164.py @@ -12,7 +12,6 @@ import grid2op from grid2op.Reward import BaseReward from grid2op.dtypes import dt_float -from grid2op.Exceptions import DivergingPowerFlow class Test164_Reward(BaseReward): diff --git a/grid2op/tests/test_issue_550.py b/grid2op/tests/test_issue_550.py new file mode 100644 index 000000000..7b5f3c553 --- /dev/null +++ b/grid2op/tests/test_issue_550.py @@ -0,0 +1,46 @@ +# 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 Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. + +import grid2op +from grid2op.Backend import PandaPowerBackend +import warnings +import unittest + + +class PandaPowerNoShunt_Test550(PandaPowerBackend): + shunts_data_available = False # class attribute (only one used) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class Issue550Tester(unittest.TestCase): + def test_no_shunt(self): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env = grid2op.make("l2rpn_case14_sandbox", + test=True, + backend=PandaPowerNoShunt_Test550(), + _add_to_name=type(self).__name__) + obs_init = env.reset() + assert not type(obs_init).shunts_data_available + assert not type(env.backend).shunts_data_available + + def test_with_shunt(self): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env = grid2op.make("l2rpn_case14_sandbox", + test=True, + backend=PandaPowerBackend(), + _add_to_name=type(self).__name__) + obs_init = env.reset() + assert type(obs_init).shunts_data_available + assert type(env.backend).shunts_data_available + + +if __name__ == "__main__": + unittest.main() diff --git a/grid2op/tests/test_issue_561.py b/grid2op/tests/test_issue_561.py new file mode 100644 index 000000000..f598bb47e --- /dev/null +++ b/grid2op/tests/test_issue_561.py @@ -0,0 +1,45 @@ +# 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 Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems. + +import numpy as np +import grid2op +from grid2op.Backend import PandaPowerBackend +import warnings +import unittest + + +class PandaPowerNoShunt_Test(PandaPowerBackend): + shunts_data_available = False # class attribute (only one used) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _init_private_attrs(self) -> None: + super()._init_private_attrs() + + +class Issue561Tester(unittest.TestCase): + def test_update_from_obs(self): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore") + env = grid2op.make("l2rpn_case14_sandbox", + test=True, + backend=PandaPowerNoShunt_Test(), + _add_to_name=type(self).__name__) + obs_init = env.reset() + assert not type(obs_init).shunts_data_available + assert not type(env.backend).shunts_data_available + backend = env.backend.copy() + backend1 = env.backend.copy() + obs, *_ = env.step(env.action_space()) + obs.load_p[:] += 1. # to make sure everything changes + backend.update_from_obs(obs) + assert np.all(backend._grid.load["p_mw"] != backend1._grid.load["p_mw"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/grid2op/tests/test_no_backend_copy.py b/grid2op/tests/test_no_backend_copy.py index ada1e7336..6a48d8676 100644 --- a/grid2op/tests/test_no_backend_copy.py +++ b/grid2op/tests/test_no_backend_copy.py @@ -12,10 +12,10 @@ import copy from grid2op.Backend import PandaPowerBackend -from grid2op.Exceptions import NoForecastAvailable -from grid2op.Exceptions.EnvExceptions import EnvError -from grid2op.Exceptions.ObservationExceptions import BaseObservationError -from grid2op.Exceptions.simulatorExceptions import SimulatorError +from grid2op.Exceptions import (NoForecastAvailable, + EnvError, + BaseObservationError, + SimulatorError) from grid2op.simulator import Simulator