From 0f15bc47fce5f29799fcba3cd9b55b089dd7d57a Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 9 Jan 2020 18:09:58 +0100 Subject: [PATCH 01/14] adding part of redispatching, need to code environment and backend with modifications now --- .gitignore | 1 + CHANGELOG.rst | 7 + grid2op/Action.py | 170 ++++++++++++++---- grid2op/Backend.py | 87 +-------- grid2op/BackendPandaPower.py | 12 +- grid2op/ChronicsHandler.py | 91 +++++++--- grid2op/Environment.py | 13 +- grid2op/Exceptions.py | 9 +- grid2op/PlotPlotly.py | 6 +- grid2op/Runner.py | 2 + grid2op/Settings_L2RPN2019.py | 8 +- grid2op/Space.py | 35 +++- grid2op/tests/test_Action.py | 9 +- grid2op/tests/test_Observation.py | 68 ++++++- grid2op/tests/test_PandaPowerBackend.py | 13 -- .../tests/test_notebooks_getting_started.py | 1 + grid2op/tests/test_runner.py | 4 +- 17 files changed, 351 insertions(+), 185 deletions(-) diff --git a/.gitignore b/.gitignore index 07beb2420..9ae3d001f 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,4 @@ grid2op/data/*/*.csv getting_started/study_agent_getting_started/ **Untitled.ipynb grid2op/tests/test_agent/ +grid2op/tests/start_datetime.info diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b5c38f94..ef1e19a0f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,12 @@ Change Log ============= +[0.5.0] - 2020-01-xx +-------------------- +- [UPDATED] more unit test for observations +- [UPDATED]remove the TODO's already coded (no more todo then) +- [UPDATED] GridStateFromFile can now read the starting date and the time interval of the chronics. +- [BREAKING] Action/Backend has been modified with the implementation of redispatching. If + you used a custom backend, you'll have to implement the "redispatching" part. [0.4.2] - 2020-01-08 -------------------- diff --git a/grid2op/Action.py b/grid2op/Action.py index 98799fa99..8f14b68b4 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -55,15 +55,14 @@ # TODO code "reduce" multiple action (eg __add__ method, carefull with that... for example "change", then "set" is not # ambiguous at all, same with "set" then "change") -# TODO code "json" serialization + # TODO code "convert_for" and "convert_from" to be able to change the backend (should be handled by the backend directly) # TODO have something that output a dict like "i want to change this element" (with a simpler API than the update stuff) # TODO time delay somewhere (eg action is implemented after xxx timestep, and not at the time where it's proposed) # TODO have the "reverse" action, that does the opposite of an action. Will be hard but who know ? :eyes: -# TODO code the from_vect and to_vect to use shape() and dtype(), and code shape() and dtype() to use attributes list - +# TODO tests for redispatching action. class Action(GridObjects): """ @@ -142,14 +141,14 @@ class Action(GridObjects): Attributes ---------- - _set_line_status: :class:`numpy.array`, dtype:int + _set_line_status: :class:`numpy.ndarray`, dtype:int For each powerlines, it gives the effect of the action on the status of it. It should be understand as: - -1 : disconnect the powerline - 0 : don't affect the powerline - +1 : reconnect the powerline - _switch_line_status: :class:`numpy.array`, dtype:bool + _switch_line_status: :class:`numpy.ndarray`, dtype:bool For each powerline, it informs whether the action will switch the status of a powerline of not. It should be understood as followed: @@ -168,7 +167,7 @@ class Action(GridObjects): to the number of generators in the test case. - "prod_v" : same as above but set the voltage setpoint of generator units. - _set_topo_vect: :class:`numpy.array`, dtype:int + _set_topo_vect: :class:`numpy.ndarray`, dtype:int Similar to :attr:`Action._set_line_status` but instead of affecting the status of powerlines, it affects the bus connectivity at substation. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and for each element it should be understood as: @@ -177,7 +176,7 @@ class Action(GridObjects): - +1 : this element is affected to bus 1 - -1 : this element is affected to bus 2 - _change_bus_vect: :class:`numpy.array`, dtype:bool + _change_bus_vect: :class:`numpy.ndarray`, dtype:bool Similar to :attr:`Action._switch_line_status` but it affects the topology at substations instead of the status of the powerline. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and each component should means: @@ -189,18 +188,12 @@ class Action(GridObjects): authorized_keys: :class:`set` The set indicating which keys the actions is able to understand when calling :func:`Action.update` - as_vect: :class:`numpy.array`, dtype:float - The representation of the action as a vector. See the help of :func:`Action.to_vect` and - :func:`Action.from_vect` for more information. **NB** for performance reason, the convertion of the internal - representation to a vector is not performed at any time. It is only performed when :func:`Action.to_vect` is - called. Otherwise, this attribute is set to ``None`` - - _subs_impacted: :class:`numpy.array`, dtype:bool + _subs_impacted: :class:`numpy.ndarray`, dtype:bool This attributes is either not initialized (set to ``None``) or it tells, for each substation, if it is impacted by the action (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``True``) or not (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``False``) - _lines_impacted: :class:`numpy.array`, dtype:bool + _lines_impacted: :class:`numpy.ndarray`, dtype:bool This attributes is either not initialized (set to ``None``) or it tells, for each powerline, if it is impacted by the action (in this case :attr:`Action._lines_impacted`\[line_id\] is ``True``) or not (in this case :attr:`Action._subs_impacted`\[line_id\] is ``False``) @@ -210,6 +203,10 @@ class Action(GridObjects): vars_action_set: ``set``, static Authorized key that are processed by :func:`Action.__call__` to modify the injections + + _redispatch: :class:`numpy.ndarray`, dtype:bool + TODO code that and to the documentation + """ vars_action = ["load_p", "load_q", "prod_p", "prod_v"] @@ -234,18 +231,21 @@ def __init__(self, gridobj): self.authorized_keys = {"injection", "hazards", "maintenance", "set_line_status", "change_line_status", - "set_bus", "change_bus"} + "set_bus", "change_bus", "redispatch"} # False(line is disconnected) / True(line is connected) - self._set_line_status = np.full(shape=self.n_line, fill_value=0, dtype=np.int) - self._switch_line_status = np.full(shape=self.n_line, fill_value=False, dtype=np.bool) + self._set_line_status = None + self._switch_line_status = None # injection change self._dict_inj = {} + # redispatching + self._redispatch = None + # topology changed - self._set_topo_vect = np.full(shape=self.dim_topo, fill_value=0, dtype=np.int) - self._change_bus_vect = np.full(shape=self.dim_topo, fill_value=False, dtype=np.bool) + self._set_topo_vect = None + self._change_bus_vect = None self._vectorized = None @@ -253,11 +253,14 @@ def __init__(self, gridobj): self._lines_impacted = None # add the hazards and maintenance usefull for saving. - self._hazards = np.full(shape=self.n_line, fill_value=False, dtype=np.bool) - self._maintenance = np.full(shape=self.n_line, fill_value=False, dtype=np.bool) + self._hazards = None + self._maintenance = None + + self.reset() # decomposition of the Action into homogeneous sub-spaces - self.attr_list_vect = ["prod_p", "prod_v", "load_p", "load_q", "_set_line_status", "_switch_line_status", + self.attr_list_vect = ["prod_p", "prod_v", "load_p", "load_q", "_redispatch", + "_set_line_status", "_switch_line_status", "_set_topo_vect", "_change_bus_vect", "_hazards", "_maintenance"] def _get_array_from_attr_name(self, attr_name): @@ -332,6 +335,8 @@ def __eq__(self, other) -> bool: Returns ------- + res: ``bool`` + Whether the actions are equal or not. """ @@ -376,6 +381,10 @@ def __eq__(self, other) -> bool: if not np.all(self._switch_line_status == other._switch_line_status): return False + # redispatching is same + if not np.all(self._redispatch == other._redispatch): + return False + # same topology changes if not np.all(self._set_topo_vect == other._set_topo_vect): return False @@ -461,6 +470,9 @@ def reset(self): self._hazards = np.full(shape=self.n_line, fill_value=False, dtype=np.bool) self._maintenance = np.full(shape=self.n_line, fill_value=False, dtype=np.bool) + # redispatching vector + self._redispatch = np.full(shape=self.n_gen, fill_value=0., dtype=np.float) + self._vectorized = None self._lines_impacted = None self._subs_impacted = None @@ -470,6 +482,8 @@ def __call__(self): This method is used to return the effect of the current action in a format understandable by the backend. This format is detailed bellow. + This function must also integrate the redispatching strategy for the Action. + It also performs a check of whether or not an action is "Ambiguous", eg an action that reconnect a powerline but doesn't specify on which bus to reconnect it is said to be ambiguous. @@ -493,14 +507,25 @@ def __call__(self): change_bus_vect: :class:`numpy.array`, dtype:bool This array is :attr:`Action._change_bus_vect` + redispatch: :class:`numpy.ndarray`, dtype:float + This array, that has the same size as the number of generators indicates for each generator the amount of + redispatching performed by the action. + Raises ------- AmbiguousAction Or one of its derivate class. """ + self._check_for_ambiguity() - return self._dict_inj, self._set_line_status, self._switch_line_status,\ - self._set_topo_vect, self._change_bus_vect + dict_inj = self._dict_inj + set_line_status = self._set_line_status + switch_line_status = self._switch_line_status + set_topo_vect = self._set_topo_vect + change_bus_vect = self._change_bus_vect + redispatch = self._redispatch + + return dict_inj, set_line_status, switch_line_status, set_topo_vect, change_bus_vect, redispatch def _digest_injection(self, dict_): # I update the action @@ -707,6 +732,54 @@ def _digest_change_status(self, dict_): raise AmbiguousAction("You can only change line status with int or boolean numpy array vector.") self._switch_line_status[dict_["change_line_status"]] = True + def __convert_and_redispatch(self, kk, val): + try: + kk = int(kk) + val = float(val) + except Exception as e: + raise AmbiguousAction("In redispatching, it's not possible to understand the key/value pair " + "{}/{} provided in the dictionnary. Key must be an integer, value " + "a float".format(kk, val)) + self._redispatch[kk] = val + + def _digest_redispatching(self, dict_): + if "redispatch" in dict_: + if dict_["redispatch"] is None: + return + tmp = dict_["redispatch"] + if isinstance(tmp, np.ndarray): + # complete redispatching is provided + self._redispatch = tmp + elif isinstance(tmp, dict): + # dict must have key: generator to modify, value: the delta value applied to this generator + ddict_ = tmp + for kk, val in ddict_.items(): + kk, val = self.__convert_and_redispatch(kk, val) + elif isinstance(tmp, list): + # list of tuples: each tupe (k,v) being the same as the key/value describe above + if len(tmp) == 2: + kk, val = tmp + self.__convert_and_redispatch(kk, val) + else: + for el in tmp: + if len(el) != 2: + raise AmbiguousAction("When asking for redispatching with a list, you should make a list" + "of tuple of 2 elements, the first one being the id of the" + "generator to redispatch, the second one the value of the " + "redispatching.") + kk, val = el + self.__convert_and_redispatch(kk, val) + elif isinstance(tmp, tuple): + if len(tmp) != 2: + raise AmbiguousAction("When asking for redispatching with a tuple, you should make a " + "of tuple of 2 elements, the first one being the id of the" + "generator to redispatch, the second one the value of the " + "redispatching.") + kk, val = tmp + self.__convert_and_redispatch(kk, val) + else: + raise AmbiguousAction("Impossible to understand the redispatching action implemented.") + def update(self, dict_): """ Update the action with a comprehensible format specified by a dictionnary. @@ -811,6 +884,7 @@ def update(self, dict_): warnings.warn(warn) self._digest_injection(dict_) + self._digest_redispatching(dict_) self._digest_setbus(dict_) self._digest_change_bus(dict_) self._digest_set_status(dict_) @@ -890,6 +964,30 @@ def _check_for_ambiguity(self): if len(self._change_bus_vect) != self.dim_topo: raise InvalidNumberOfObjectEnds("This action acts on {} ends of object while there are {} in the _grid".format(len(self._change_bus_vect), self.dim_topo)) + if len(self._redispatch) != self.n_gen: + raise InvalidNumberOfGenerators("This action acts on {} generators (redispatching= while there are {} in the grid".format( + len(self._redispatch), self.n_gen)) + + # redispatching specific check + if np.any(self._redispatch > self.gen_max_ramp_up): + raise InvalidRedispatching("Some redispatching amount are above the maximum ramp up") + if np.any(-self._redispatch > self.gen_max_ramp_down): + raise InvalidRedispatching("Some redispatching amount are bellow the maximum ramp down") + + if np.any(self._redispatch[~self.gen_redispatchable] != 0.): + raise InvalidRedispatching("Trying to apply a redispatching action on a non redispatchable generator") + + if "prod_p" in self._dict_inj: + new_p = self._dict_inj["prod_p"] + tmp_p = new_p + self._redispatch + if np.any(tmp_p > self.gen_pmax): + raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " + "are above pmax for some generator.") + if np.any(tmp_p < self.gen_pmin): + raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " + "are below pmin for some generator.") + + # topological action if np.any(self._set_topo_vect[self._change_bus_vect] != 0): raise InvalidBusStatus("You asked to change the bus of an object with" " using the keyword \"change_bus\" and set this same object state in \"set_bus\"" @@ -1149,6 +1247,8 @@ def as_dict(self): that are disconnected because of them. * `nb_maintenance` the number of maintenance the "action" implemented eg the number of powerlines disconnected becaues of maintenance operation. + * `redispatch` the redispatching action (if any). It gives, for each generator (all generator, not just the + dispatchable one) the amount of power redispatched in this action. Returns ------- @@ -1219,6 +1319,9 @@ def as_dict(self): res["maintenance"] = np.where(self._maintenance)[0] res["nb_maintenance"] = np.sum(self._maintenance) + if np.any(self._redispatch != 0.): + res["redispatch"] = self._redispatch + return res def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, substation_id=None): @@ -1267,6 +1370,7 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub - "set_bus" the new bus where the load will be moved (int: id of the bus, 0 no change, -1 disconnected) - "change_bus" whether or not this load will be moved from one bus to another (for example is an action asked it to go from bus 1 to bus 2) + - "redispatch" the amount of power redispatched for this generator. - if a powerline is inspected then the keys are: @@ -1295,13 +1399,15 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub ------ Grid2OpException If _sentinel is modified, or if None of the arguments are set or alternatively if 2 or more of the - _parameters are being set. + parameters are being set. """ if _sentinel is not None: raise Grid2OpException("action.effect_on should only be called with named argument.") + if load_id is None and gen_id is None and line_id is None and substation_id is None: raise Grid2OpException("You ask the effect of an action on something, wihtout provided anything") + if load_id is not None: if gen_id is not None or line_id is not None or substation_id is not None: raise Grid2OpException("You can only the inpsect the effect of an action on one single element") @@ -1313,10 +1419,11 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub my_id = self.load_pos_topo_vect[load_id] res["change_bus"] = self._change_bus_vect[my_id] res["set_bus"] = self._set_topo_vect[my_id] + elif gen_id is not None: if line_id is not None or substation_id is not None: raise Grid2OpException("You can only the inpsect the effect of an action on one single element") - res = {"new_p": np.NaN, "new_v": np.NaN, "set_topology": 0.} + res = {"new_p": np.NaN, "new_v": np.NaN, "set_bus": 0., "change_bus": False} if "prod_p" in self._dict_inj: res["new_p"] = self._dict_inj["prod_p"][gen_id] if "prod_v" in self._dict_inj: @@ -1324,6 +1431,8 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub my_id = self.gen_pos_topo_vect[gen_id] res["change_bus"] = self._change_bus_vect[my_id] res["set_bus"] = self._set_topo_vect[my_id] + res["redispatch"] = self._redispatch[my_id] + elif line_id is not None: if substation_id is not None: raise Grid2OpException("You can only the inpsect the effect of an action on one single element") @@ -1480,10 +1589,12 @@ def from_vect(self, vect): def sample(self): """ - TODO + + Returns ------- - + res: :class:`Action` + The current action (useful to chain some calls to methods) """ self.reset() # TODO code the sampling now @@ -1924,7 +2035,6 @@ def get_change_line_status_vect(self): return self.template_act.get_change_line_status_vect() -# TODO have something that output a dict like "i want to change this element", with its name accessible here class HelperAction(SerializableActionSpace): """ :class:`HelperAction` should be created by an :class:`grid2op.Environment.Environment` diff --git a/grid2op/Backend.py b/grid2op/Backend.py index 612658eee..56dd0d93d 100644 --- a/grid2op/Backend.py +++ b/grid2op/Backend.py @@ -46,11 +46,6 @@ import pdb -# TODO code a method to give information about element (given name, gives type, substation, bus connected etc.) -# TODO given a bus, returns the names of the elements connected to it -# TODO given a substation, returns the name of the elements connected to it -# TODO given to substations, returns the name of the powerlines connecting them, if any - # TODO URGENT: if chronics are "loop through" multiple times, only last results are saved. :-/ @@ -65,82 +60,6 @@ class Backend(GridObjects, ABC): detailed_infos_for_cascading_failures: :class:`bool` Whether to be verbose when computing a cascading failure. - n_line: :class:`int` - number of powerline in the _grid - - n_gen: :class:`int` - number of generators in the _grid - - n_load: :class:`int` - number of loads in the powergrid - - n_sub: :class:`int` - number of substation in the powergrid - - subs_elements: :class:`numpy.array`, dtype:int - for each substation, gives the number of elements connected to it - - load_to_subid: :class:`numpy.array`, dtype:int - for each load, gives the id the substation to which it is connected - - gen_to_subid: :class:`numpy.array`, dtype:int - for each generator, gives the id the substation to which it is connected - - lines_or_to_subid: :class:`numpy.array`, dtype:int - for each lines, gives the id the substation to which its "origin" end is connected - - lines_ex_to_subid: :class:`numpy.array`, dtype:int - for each lines, gives the id the substation to which its "extremity" end is connected - - load_to_sub_pos: :class:`numpy.array`, dtype:int - The topology if of the subsation *i* is given by a vector, say *sub_topo_vect* of size - :attr:`Backend.sub_info`\[i\]. For a given load of id *l*, :attr:`Backend.load_to_sub_pos`\[l\] is the index - of the load *l* in the vector *sub_topo_vect*. This means that, if - *sub_topo_vect\[ action.load_to_sub_pos\[l\] \]=2* - then load of id *l* is connected to the second bus of the substation. - - gen_to_sub_pos: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_to_sub_pos` but for generators. - - lines_or_to_sub_pos: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_to_sub_pos` but for "origin" end of powerlines. - - lines_ex_to_sub_pos: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_to_sub_pos` but for "extremity" end of powerlines. - - load_pos_topo_vect: :class:`numpy.array`, dtype:int - It has a similar role as :attr:`Backend.load_to_sub_pos` but it gives the position in the vector representing - the whole topology. More concretely, if the complete topology of the powergrid is represented here by a vector - *full_topo_vect* resulting of the concatenation of the topology vector for each substation - (see :attr:`Backend.load_to_sub_pos`for more information). For a load of id *l* in the powergrid, - :attr:`Backend.load_pos_topo_vect`\[l\] gives the index, in this *full_topo_vect* that concerns load *l*. - More formally, if *_topo_vect\[ backend.load_pos_topo_vect\[l\] \]=2* then load of id l is connected to the - second bus of the substation. - - gen_pos_topo_vect: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_pos_topo_vect` but for generators. - - line_or_pos_topo_vect: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_pos_topo_vect` but for "origin" end of powerlines. - - line_ex_pos_topo_vect: :class:`numpy.array`, dtype:int - same as :attr:`Backend.load_pos_topo_vect` but for "extremity" end of powerlines. - - _grid: (its type depends on the backend, precisely) - is a representation of the powergrid that can be called and manipulated by the backend. - - name_load: :class:`numpy.array`, dtype:str - ordered name of the loads in the backend. This is mainly use to make sure the "chronics" are used properly. - - name_gen: :class:`numpy.array`, dtype:str - ordered name of the productions in the backend. This is mainly use to make sure the "chronics" are used properly. - - name_line: :class:`numpy.array`, dtype:str - ordered name of the productions in the backend. This is mainly use to make sure the "chronics" are used properly. - - name_sub: :class:`numpy.array`, dtype:str - ordered name of the substation in the _grid. This is mainly use to make sure the "chronics" are used properly. - thermal_limit_a: :class:`numpy.array`, dtype:float Thermal limit of the powerline in amps for each powerline. Thie thermal limit is relevant on only one side of the powerline: the same side returned by :func:`Backend.get_line_overflow` @@ -267,7 +186,8 @@ def apply_action(self, action): For the L2RPN project, this action is mainly for topology if it has been sent by the agent. Or it can also affect production and loads, if the action is made by the environment. - The help of :class:`grid2op.Action` or the code in Action.py file give more information about the implementation of this method. + The help of :func:`grid2op.Action.Action.__call__` or the code in Action.py file give more information about + the implementation of this method. :param action: the action to be implemented on the powergrid. :type action: :class:`grid2op.Action.Action` @@ -285,7 +205,8 @@ def runpf(self, is_dc=False): :param is_dc: is the powerflow run in DC or in AC :type is_dc: :class:`bool` - :return: True if it has converged, or false otherwise. In case of non convergence, no flows can be inspected on the _grid. + :return: True if it has converged, or false otherwise. In case of non convergence, no flows can be inspected on + the _grid. :rtype: :class:`bool` """ pass diff --git a/grid2op/BackendPandaPower.py b/grid2op/BackendPandaPower.py index 4e31bad5a..146bc4b46 100644 --- a/grid2op/BackendPandaPower.py +++ b/grid2op/BackendPandaPower.py @@ -79,8 +79,6 @@ class PandaPowerBackend(Backend): v_ex: :class:`numpy.array`, dtype:float The voltage magnitude at the extremity bus of the powerline - thermal_limit: :class:`numpy.array`, dtype:float - The """ def __init__(self): Backend.__init__(self) @@ -107,7 +105,6 @@ def __init__(self): self._iref_slack = None self._id_bus_added = None self._fact_mult_gen = -1 - self._subs_to_table = None self._what_object_where = None self._number_true_line = -1 self._corresp_name_fun = {} @@ -225,7 +222,6 @@ def load_grid(self, path=None, filename=None): self.line_or_to_sub_pos = np.zeros(self.n_line, dtype=np.int) self.line_ex_to_sub_pos = np.zeros(self.n_line, dtype=np.int) - self._subs_to_table = [None for _ in range(self.n_sub)] # TODO later if I have too pos_already_used = np.zeros(self.n_sub, dtype=np.int) self._what_object_where = [[] for _ in range(self.n_sub)] @@ -336,7 +332,8 @@ def apply_action(self, action: Action): raise UnrecognizedAction("Action given to PandaPowerBackend should be of class Action and not \"{}\"".format(action.__class__)) # change the _injection if needed - dict_injection, change_status, switch_status, set_topo_vect, switcth_topo_vect = action() + dict_injection, change_status, switch_status, set_topo_vect, switcth_topo_vect, redispatching = action() + for k in dict_injection: if k in self._vars_action_set: tmp = self._get_vector_inj[k](self._grid) @@ -368,6 +365,11 @@ def apply_action(self, action: Action): warn = "The key {} is not recognized by PandaPowerBackend when setting injections value.".format(k) warnings.warn(warn) + if np.any(redispatching != 0.): + tmp = self._get_vector_inj["prod_p"](self._grid) + ok_ind = np.isfinite(redispatching) + tmp[ok_ind] += redispatching[ok_ind] + # topology # run through all substations, find the topology. If it has changed, then update it. beg_ = 0 diff --git a/grid2op/ChronicsHandler.py b/grid2op/ChronicsHandler.py index 35a5836d7..834914641 100644 --- a/grid2op/ChronicsHandler.py +++ b/grid2op/ChronicsHandler.py @@ -60,11 +60,10 @@ # TODO sous echantillonner ou sur echantilloner les scenario -# TODO max_iter is not properly handled in the example of GridValue now + # TODO add a class to sample "online" the data. -# TODO this is weird that maintenance and hazards are in the action, -# but not maintenance_time and maintenance_duration... -# TODO in FromFile, add the possibility to change the timestamps (for now it's not modified at all...) + +# TODO add a method 'skip' that can skip a given number of timestep or a until a specific date. class GridValue(ABC): @@ -87,8 +86,11 @@ class GridValue(ABC): time_interval: :class:`.datetime.timedelta` Time interval between 2 consecutive timestamps. Default 5 minutes. + start_datetime: :class:`datetime.datetime` + The datetime of the first timestamp of the scenario. + current_datetime: :class:`datetime.datetime` - The timestamp of the current observation. + The timestamp of the current scenario. n_gen: ``int`` Number of generators in the powergrid @@ -131,10 +133,11 @@ class GridValue(ABC): """ - def __init__(self, time_interval=timedelta(minutes=5), max_iter=-1): - # TODO complete that with a real datetime + def __init__(self, time_interval=timedelta(minutes=5), max_iter=-1, + start_datetime=datetime(year=2019, month=1, day=1)): self.time_interval = time_interval - self.current_datetime = datetime(year=2019, month=1, day=1) + self.current_datetime = start_datetime + self.start_datetime = start_datetime self.n_gen = None self.n_load = None self.n_line = None @@ -243,7 +246,8 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin >>> gridval.initialize(order_backend_loads, order_backend_prods, order_backend_lines, names_chronics_to_backend) """ - pass + self.curr_iter += 1 + self.current_datetime += self.time_interval @staticmethod def get_maintenance_time_1d(maintenance): @@ -617,19 +621,20 @@ class ChangeNothing(GridValue): This class is the most basic class to modify a powergrid values. It does nothing exceptie increasing :attr:`GridValue.max_iter` and the :attr:`GridValue.current_datetime`. """ - def __init__(self, time_interval=timedelta(minutes=5), max_iter=-1): - GridValue.__init__(self, time_interval=time_interval, max_iter=max_iter) + def __init__(self, time_interval=timedelta(minutes=5), max_iter=-1, + start_datetime=datetime(year=2019, month=1, day=1)): + GridValue.__init__(self, time_interval=time_interval, max_iter=max_iter, start_datetime=start_datetime) def initialize(self, order_backend_loads, order_backend_prods, order_backend_lines, order_backend_subs, names_chronics_to_backend=None): self.n_gen = len(order_backend_prods) self.n_load = len(order_backend_loads) - self.n_lines = len(order_backend_lines) + self.n_line = len(order_backend_lines) self.curr_iter = 0 - self.maintenance_time = np.zeros(shape=(self.n_lines, ), dtype=np.int) - 1 - self.maintenance_duration = np.zeros(shape=(self.n_lines, ), dtype=np.int) - self.hazard_duration = np.zeros(shape=(self.n_lines, ), dtype=np.int) + self.maintenance_time = np.zeros(shape=(self.n_line, ), dtype=np.int) - 1 + self.maintenance_duration = np.zeros(shape=(self.n_line, ), dtype=np.int) + self.hazard_duration = np.zeros(shape=(self.n_line, ), dtype=np.int) def load_next(self): """ @@ -688,13 +693,12 @@ def next_chronics(self): ------- """ - self.current_datetime = datetime(year=2019, month=1, day=1) + self.current_datetime = self.start_datetime self.curr_iter = 0 # TODO handle case of missing files: equivalent to "change nothing" probably -# TODO change date time: should be read from file! -# TODO initialize datetime properly ! probably in a file (i just need start_date and time_interval + class GridStateFromFile(GridValue): """ Read the injections values from a file stored on hard drive. More detailed about the files is provided in the @@ -752,7 +756,8 @@ class GridStateFromFile(GridValue): This directory matches the name of the objects (line extremity, generator or load) to the same object in the backed. See the help of :func:`GridValue.initialize` for more information). """ - def __init__(self, path, sep=";", time_interval=timedelta(minutes=5), max_iter=-1): + def __init__(self, path, sep=";", time_interval=timedelta(minutes=5), max_iter=-1, + start_datetime=datetime(year=2019, month=1, day=1)): """ Build an instance of GridStateFromFile. Such an instance should be built before an :class:`grid2op.Environment` is created. @@ -772,7 +777,7 @@ def __init__(self, path, sep=";", time_interval=timedelta(minutes=5), max_iter=- Used to initialize :attr:`GridValue.max_iter` """ - GridValue.__init__(self, time_interval=time_interval, max_iter=max_iter) + GridValue.__init__(self, time_interval=time_interval, max_iter=max_iter, start_datetime=start_datetime) self.path = path @@ -812,6 +817,33 @@ def _assert_correct_second_stage(self, pandas_name, dict_convert, key, extra="") if not el in dict_convert[key]: raise ChronicsError("Element named {} is found in the data (column {}) but it is not found on the powergrid for data of type \"{}\".\nData in files are: {}\nConverter data are: {}".format(el, i+1, key, sorted(list(pandas_name)), sorted(list(dict_convert[key].keys())))) + def _init_date_time(self): + if os.path.exists(os.path.join(self.path, "start_datetime.info")): + with open(os.path.join(self.path, "start_datetime.info"), "r") as f: + a = f.read().rstrip().lstrip() + try: + tmp = datetime.strptime(a, "%Y-%m-%d %H:%M") + except ValueError: + tmp = datetime.strptime(a, "%Y-%m-%d") + except Exception: + raise ChronicsNotFoundError("Impossible to understand the content of \"start_datetime.info\". Make sure " + "it's composed of only one line with a datetime in the \"%Y-%m-%d %H:%M\"" + "format.") + self.start_datetime = tmp + + if os.path.exists(os.path.join(self.path, "time_interval.info")): + with open(os.path.join(self.path, "time_interval.info"), "r") as f: + a = f.read().rstrip().lstrip() + try: + tmp = datetime.strptime(a, "%H:%M") + except ValueError: + tmp = datetime.strptime(a, "%M") + except Exception: + raise ChronicsNotFoundError("Impossible to understand the content of \"time_interval.info\". Make sure " + "it's composed of only one line with a datetime in the \"%H:%M\"" + "format.") + self.time_interval = timedelta(hours=tmp.hour, minutes=tmp.minute) + def initialize(self, order_backend_loads, order_backend_prods, order_backend_lines, order_backend_subs, names_chronics_to_backend=None): """ @@ -824,21 +856,30 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin - a file named "prod_p.csv" used to initialize :attr:`GridStateFromFile.prod_p` - a file named "prod_v.csv" used to initialize :attr:`GridStateFromFile.prod_v` - a file named "hazards.csv" used to initialize :attr:`GridStateFromFile.hazards` - - a file named "_maintenance.csv" used to initialize :attr:`GridStateFromFile._maintenance` + - a file named "maintenance.csv" used to initialize :attr:`GridStateFromFile.maintenance` All these csv must have the same separator specified by :attr:`GridStateFromFile.sep`. + If a file named "start_datetime.info" is present, then it will be used to initialized + :attr:`GridStateFromFile.start_datetime`. If this file exists, it should count only one row, with the + initial datetime in the "%Y-%m-%d %H:%M" format. + + If a file named "time_interval.info" is present, then it will be used to initialized the + :attr:`GridStateFromFile.time_interval` attribute. If this file exists, it should count only one row, with the + initial datetime in the "%H:%M" format. Only timedelta composed of hours and minutes are supported (time delta + cannot go above 23 hours 55 minutes and cannot be smaller than 0 hour 1 minutes) + The first row of these csv is understood as the name of the object concerned by the column. Either this name is present in the :class:`grid2op.Backend`, in this case no modification is performed, or in case the name is not found in the backend and in this case it must be specified in the "names_chronics_to_backend" - _parameters how to understand it. See the help of :func:`GridValue.initialize` for more information + parameters how to understand it. See the help of :func:`GridValue.initialize` for more information about this dictionnary. All files should have the same number of rows. Parameters ---------- - See help of :func:`GridValue.initialize` for a detailed help about the _parameters. + See help of :func:`GridValue.initialize` for a detailed help about the parameters. Returns ------- @@ -869,6 +910,8 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin else: self._assert_correct(self.names_chronics_to_backend["subs"], order_backend_subs) + self._init_date_time() + read_compressed = ".csv" if not os.path.exists(os.path.join(self.path, "load_p.csv")): # try to read compressed data @@ -1288,7 +1331,7 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin self.n_gen = len(order_backend_prods) self.n_load = len(order_backend_loads) - self.n_lines = len(order_backend_lines) + self.n_line = len(order_backend_lines) # print("max_iter: {}".format(self.max_iter)) self.data = self.gridvalueClass(time_interval=self.time_interval, sep=self.sep, diff --git a/grid2op/Environment.py b/grid2op/Environment.py index ebd837e22..d7b9f5d77 100644 --- a/grid2op/Environment.py +++ b/grid2op/Environment.py @@ -66,7 +66,10 @@ import pdb -# TODO code "start from a given time step" +# TODO code "start from a given time step" -> link to the "skip" method of GridValue + +# TODO have a viewer / renderer now + class Environment: """ @@ -582,6 +585,12 @@ def step(self, action): # action is replace by do nothing action = self.helper_action_player({}) + # TODO redispatching: process the redispatching actions here, get a redispatching vector with 0-sum + # from the environment. + # and then remove the redispatching part of the action from the player one, and add it to the environment's + self.env_modification = self._update_actions() + # TODO check now that the action is compatible with the new modification + try: self.backend.apply_action(action) except AmbiguousAction: @@ -589,7 +598,6 @@ def step(self, action): # "do nothing" is_ambiguous = True - self.env_modification = self._update_actions() self.backend.apply_action(self.env_modification) self._time_apply_act += time.time() - beg_ @@ -646,6 +654,7 @@ def step(self, action): def _reset_vectors_and_timings(self): """ Maintenance are not reset, otherwise the data are not read properly (skip the first time step) + Returns ------- diff --git a/grid2op/Exceptions.py b/grid2op/Exceptions.py index 01675b4fd..ded6605a6 100644 --- a/grid2op/Exceptions.py +++ b/grid2op/Exceptions.py @@ -256,11 +256,18 @@ class InvalidNumberOfObjectEnds(AmbiguousAction): class InvalidBusStatus(AmbiguousAction): """ This is a more precise exception than :class:`AmbiguousAction` indicating that the :class:`grid2op.Action.Action` - try to bot "set" and "switch" some bus to which an object is connected. + try to both "set" and "switch" some bus to which an object is connected. """ pass +class InvalidRedispatching(AmbiguousAction): + """ + This is a more precise exception than :class:`AmbiguousAction` indicating that the :class:`grid2op.Action.Action` + try to apply an invalid redispatching strategy. + """ + pass + # powerflow exception class DivergingPowerFlow(Grid2OpException): """ diff --git a/grid2op/PlotPlotly.py b/grid2op/PlotPlotly.py index ed93dc171..e8860ea0c 100644 --- a/grid2op/PlotPlotly.py +++ b/grid2op/PlotPlotly.py @@ -247,7 +247,6 @@ def __init__(self, substation_layout, observation_space, objs = observation_space.get_obj_connect_to(substation_id=sub_id) for c_id in objs["loads_id"]: - # TODO here get the proper name c_nm = self._get_load_name(sub_id, c_id) this_load = {} this_load["type"] = "load" @@ -255,7 +254,6 @@ def __init__(self, substation_layout, observation_space, this_sub[c_nm] = this_load for g_id in objs["generators_id"]: - # TODO here get the proper name g_nm = self._get_gen_name(sub_id, g_id) this_gen = {} this_gen["type"] = "gen" @@ -263,7 +261,6 @@ def __init__(self, substation_layout, observation_space, this_sub[g_nm] = this_gen for lor_id in objs["lines_or_id"]: - # TODO here get the proper name ext_id = observation_space.line_ex_to_subid[lor_id] l_nm = self._get_line_name(sub_id, ext_id, lor_id) this_line = {} @@ -272,7 +269,6 @@ def __init__(self, substation_layout, observation_space, this_sub[l_nm] = this_line for lex_id in objs["lines_ex_id"]: - # TODO here get the proper name or_id = observation_space.line_or_to_subid[lex_id] l_nm = self._get_line_name(or_id, sub_id, lex_id) this_line = {} @@ -390,7 +386,6 @@ def _draw_powerlines(self, observation, fig): state = observation.state_of(line_id=line_id) sub_or_id, sub_ex_id = self._layout["line"][line_id] - # TODO have the proper name l_nm = self._get_line_name(sub_or_id, sub_ex_id, line_id) pos_or = self.subs_elements[sub_or_id][l_nm]["pos"] pos_ex = self.subs_elements[sub_ex_id][l_nm]["pos"] @@ -403,6 +398,7 @@ def _draw_powerlines(self, observation, fig): rho=rho, color_palette=self.cols, status=status)) + # TODO adjust position of labels... fig.add_trace(go.Scatter(x=[(pos_or[0] + pos_ex[0]) / 2], y=[(pos_or[1] + pos_ex[1]) / 2], diff --git a/grid2op/Runner.py b/grid2op/Runner.py index ddf4de001..ea3fb47b9 100644 --- a/grid2op/Runner.py +++ b/grid2op/Runner.py @@ -84,6 +84,8 @@ # TODO add a more suitable logging strategy +# TODO use gym logger if specified by the user. + class DoNothingLog: """ diff --git a/grid2op/Settings_L2RPN2019.py b/grid2op/Settings_L2RPN2019.py index f0fe83eb7..0d6a775aa 100644 --- a/grid2op/Settings_L2RPN2019.py +++ b/grid2op/Settings_L2RPN2019.py @@ -184,10 +184,10 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin self.maintenance_forecast = copy.deepcopy(maintenance.values[:, np.argsort(order_backend_maintenance)]) # there are maintenance and hazards only if the value in the file is not 0. - self.maintenance_time = np.zeros(shape=(self.load_p.shape[0], self.n_lines), dtype=np.int) - 1 - self.maintenance_duration = np.zeros(shape=(self.load_p.shape[0], self.n_lines), dtype=np.int) - self.hazard_duration = np.zeros(shape=(self.load_p.shape[0], self.n_lines), dtype=np.int) - for line_id in range(self.n_lines): + self.maintenance_time = np.zeros(shape=(self.load_p.shape[0], self.n_line), dtype=np.int) - 1 + self.maintenance_duration = np.zeros(shape=(self.load_p.shape[0], self.n_line), dtype=np.int) + self.hazard_duration = np.zeros(shape=(self.load_p.shape[0], self.n_line), dtype=np.int) + for line_id in range(self.n_line): self.maintenance_time[:, line_id] = self.get_maintenance_time_1d(self.maintenance[:, line_id]) self.maintenance_duration[:, line_id] = self.get_maintenance_duration_1d(self.maintenance[:, line_id]) self.hazard_duration[:, line_id] = self.get_maintenance_duration_1d(self.hazards[:, line_id]) diff --git a/grid2op/Space.py b/grid2op/Space.py index 395790bea..2f641e9bb 100644 --- a/grid2op/Space.py +++ b/grid2op/Space.py @@ -23,8 +23,9 @@ # TODO better random stuff when random observation (seed in argument is really weird) -# TODO gym integration (inheritance, shape and dtype) -# TODO tests of these methods + +# TODO tests of these methods and this class in general + class GridObjects: """ This class stores in a Backend agnostic way some information about the powergrid. @@ -119,6 +120,19 @@ class GridObjects: :func:`GridObjects.shape` as well as :func:`GridObjects.size`. If this class is derived, then it's really important that this vector is properly set. All the attributes with the name on this vector should have consistently the same size and shape, otherwise some methods will not behave as expected. + + _vectorized: :class:`numpy.array`, dtype:float + The representation of the GridObject as a vector. See the help of :func:`GridObjects.to_vect` and + :func:`GridObjects.from_vect` for more information. **NB** for performance reason, the convertion of the internal + representation to a vector is not performed at any time. It is only performed when :func:`GridObjects.to_vect` is + called the first time. Otherwise, this attribute is set to ``None``. + + gen_type + gen_pmin + gen_pmax + gen_redispatchable + gen_ramp_up_max + gen_ramp_down_min """ def __init__(self): # name of the objects @@ -157,6 +171,15 @@ def __init__(self): self.attr_list_vect = None self._vectorized = None + # for redispatching + TODO = "TODO COMPLETE THAT BELLOW!!! AND UPDATE THE init methods" + self.gen_type = TODO + self.gen_pmin = TODO + self.gen_pmax = TODO + self.gen_redispatchable = TODO + self.gen_max_ramp_up = TODO + self.gen_max_ramp_down = TODO + def _raise_error_attr_list_none(self): """ Raise a "NotImplementedError" if :attr:`GridObjects.attr_list_vect` is not defined. @@ -717,6 +740,14 @@ def init_grid(self, gridobj): self.line_or_pos_topo_vect = gridobj.line_or_pos_topo_vect self.line_ex_pos_topo_vect = gridobj.line_ex_pos_topo_vect + # for redispatching + self.gen_type = gridobj.gen_type + self.gen_pmin = gridobj.gen_pmin + self.gen_pmax = gridobj.gen_pmax + self.gen_redispatchable = gridobj.gen_redispatchable + self.gen_max_ramp_up = gridobj.gen_max_ramp_up + self.gen_max_ramp_down = gridobj.gen_max_ramp_down + def get_obj_connect_to(self, _sentinel=None, substation_id=None): """ Get all the object connected to a given substation: diff --git a/grid2op/tests/test_Action.py b/grid2op/tests/test_Action.py index 176362f52..ff9db0312 100644 --- a/grid2op/tests/test_Action.py +++ b/grid2op/tests/test_Action.py @@ -27,11 +27,6 @@ def setUp(self): The case file is a representation of the case14 as found in the ieee14 powergrid. :return: """ - # from ADNBackend import ADNBackend - # self.backend = ADNBackend() - # self.path_matpower = "/home/donnotben/Documents/RL4Grid/RL4Grid/data" - # self.case_file = "ieee14_ADN.xml" - # self.backend.load_grid(self.path_matpower, self.case_file) self.tolvect = 1e-2 self.tol_one = 1e-5 self.game_rules = GameRules() @@ -239,7 +234,6 @@ def test_update_set_topo_by_dict_sub2(self): assert action.effect_on(load_id=1)["set_bus"] == 0 assert action.effect_on(gen_id=0)["set_bus"] == 0 - # TODO maybe assert different stuff here, for the first modification def test_update_change_bus_by_dict_obj(self): action = self.helper_action({"change_bus": {"loads_id": [1]}}) @@ -274,7 +268,6 @@ def test_update_change_bus_by_dict_sub2(self): assert action.effect_on(load_id=1)["change_bus"] == False assert action.effect_on(gen_id=0)["change_bus"] == False - # TODO maybe assert different stuff here, for the first modification def test_ambiguity_topo(self): action = self.helper_action({"change_bus": {"lines_or_id": [1]}}) # i switch the bus of the origin of powerline 1 @@ -410,7 +403,7 @@ def test_from_vect(self): assert np.all(vect_act1[np.isfinite(vect_act2)] == vect_act2[np.isfinite(vect_act2)]) assert np.all(np.isfinite(vect_act1) == np.isfinite(vect_act2)) - def test_call_(self): + def test_call_change_set(self): arr1 = np.array([False, False, False, True, True, True], dtype=np.bool) arr2 = np.array([1, 1, 2, 2], dtype=np.int) id_1 = 1 diff --git a/grid2op/tests/test_Observation.py b/grid2op/tests/test_Observation.py index 025a58493..05fe33bf8 100644 --- a/grid2op/tests/test_Observation.py +++ b/grid2op/tests/test_Observation.py @@ -30,9 +30,7 @@ from Environment import Environment -# TODO add unit test for obs.connectity_matrix() -# TODO add unit test for obs.bus_connectivity_matrix() -# todo add unit test for the proper update the backend in the observation [for now there is a "data leakage" as +# TODO add unit test for the proper update the backend in the observation [for now there is a "data leakage" as # the real backend is copied when the observation is built, but i need to make a test to check that's it's properly # copied] @@ -134,10 +132,70 @@ def test_sum_shape_equal_size(self): obs = self.env.helper_observation(self.env) assert obs.size() == np.sum(obs.shape()) + def test_bus_conn_mat(self): + obs = self.env.helper_observation(self.env) + mat1 = obs.bus_connectivity_matrix() + ref_mat = np.array([[1., 1., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 1., 1., 1., 1., 0., 1., 0., 1., 0., 0., 0., 0., 0.], + [1., 1., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 1., 0.], + [0., 0., 0., 1., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 0., 0., 1., 0., 1., 1., 0., 0., 0., 1.], + [0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0.], + [0., 0., 0., 0., 0., 1., 0., 0., 0., 1., 1., 0., 0., 0.], + [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 0.], + [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 1.], + [0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 1., 1.]]) + assert np.all(mat1 == ref_mat) + def test_conn_mat(self): obs = self.env.helper_observation(self.env) - obs.bus_connectivity_matrix() - obs.connectivity_matrix() + mat = obs.connectivity_matrix() + ref_mat = np.array([[0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0.], + [1., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [1., 0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 1., 0., 1., 1., 1., 0., 0., 0., 0., 1., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 1., 1., 0., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 1., 1., 1., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0.] + ]) + assert np.all(mat[:10,:] == ref_mat) def test_observation_space(self): obs = self.env.helper_observation(self.env) diff --git a/grid2op/tests/test_PandaPowerBackend.py b/grid2op/tests/test_PandaPowerBackend.py index f6673a5e6..eab5ee98c 100644 --- a/grid2op/tests/test_PandaPowerBackend.py +++ b/grid2op/tests/test_PandaPowerBackend.py @@ -847,18 +847,5 @@ def test_change_bustwice(self): assert np.sum(env.backend._grid["bus"]["in_service"]) == 14 -# TODO test also the methods added for observation: -""" - self._line_status = copy.copy(backend.get_line_status()) - self._topo_vect = copy.copy(backend.get_topo_vect()) - # get the values related to continuous values - self.prod_p, self.prod_q, self.prod_v = backend.generators_info() - self.load_p, self.load_q, self.load_v = backend.loads_info() - self.p_or, self.q_or, self.v_or, self.a_or = backend.lines_or_info() - self.p_ex, self.q_ex, self.v_ex, self.a_ex = backend.lines_ex_info() -""" - -# TODO refactor these tests to be both done in ADNBackend and PandaPowerBackend both coming from a file "test_Backend" - if __name__ == "__main__": unittest.main() diff --git a/grid2op/tests/test_notebooks_getting_started.py b/grid2op/tests/test_notebooks_getting_started.py index 5f2589d62..88058bcc0 100644 --- a/grid2op/tests/test_notebooks_getting_started.py +++ b/grid2op/tests/test_notebooks_getting_started.py @@ -13,6 +13,7 @@ from helper_path_test import PATH_DATA_TEST +# TODO check these tests, they don't appear to be working def delete_all(folder): """ diff --git a/grid2op/tests/test_runner.py b/grid2op/tests/test_runner.py index 3e4db40a4..d3887abe1 100644 --- a/grid2op/tests/test_runner.py +++ b/grid2op/tests/test_runner.py @@ -38,8 +38,6 @@ DEBUG = True -#TODO test that a runner save the stuff correctly - class TestAgent(unittest.TestCase): def setUp(self): @@ -87,7 +85,7 @@ def test_one_episode(self): assert int(timestep) == 287 assert np.abs(cum_reward - 5739.951023) <= self.tol_one - def test_one_episode(self): + def test_one_episode_with_saving(self): cum_reward, timestep = self.runner.run_one_episode(path_save="test_agent") assert int(timestep) == 287 assert np.abs(cum_reward - 5739.951023) <= self.tol_one From 4a55eb1944e3bbc1b80648455544a675174b6929 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 10 Jan 2020 18:38:37 +0100 Subject: [PATCH 02/14] some tests added for redispatching in action, need to code observation and environment and backend (extraction of new features from the grid) now --- CHANGELOG.rst | 2 + grid2op/Action.py | 241 +++++++++++------------------------ grid2op/Backend.py | 55 ++++---- grid2op/BackendPandaPower.py | 2 +- grid2op/Observation.py | 20 --- grid2op/Space.py | 230 ++++++++++++++++++++++++++------- grid2op/tests/test_Action.py | 20 ++- 7 files changed, 306 insertions(+), 264 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ef1e19a0f..0a30f5fae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,8 @@ Change Log ============= [0.5.0] - 2020-01-xx -------------------- +- [ADDED] more complete documentation of the representation of the powergrid + (see documentation of `Space`) - [UPDATED] more unit test for observations - [UPDATED]remove the TODO's already coded (no more todo then) - [UPDATED] GridStateFromFile can now read the starting date and the time interval of the chronics. diff --git a/grid2op/Action.py b/grid2op/Action.py index 8f14b68b4..6b847f484 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -265,7 +265,7 @@ def __init__(self, gridobj): def _get_array_from_attr_name(self, attr_name): if attr_name in self.__dict__: - res = np.array(self.__dict__[attr_name]).flatten() + res = super()._get_array_from_attr_name(attr_name) else: if attr_name in self._dict_inj: res = self._dict_inj[attr_name] @@ -279,6 +279,16 @@ def _get_array_from_attr_name(self, attr_name): "into the Action of type \"{}\"".format(attr_name, type(self))) return res + def _assign_attr_from_name(self, attr_nm, vect): + if attr_nm in self.__dict__: + super()._assign_attr_from_name(attr_nm, vect) + else: + if np.any(np.isfinite(vect)): + self._dict_inj[attr_nm] = vect + + def check_space_legit(self): + self._check_for_ambiguity() + def get_set_line_status_vect(self): """ Computes and return a vector that can be used in the "set_status" keyword if building an :class:`Action`. @@ -757,10 +767,23 @@ def _digest_redispatching(self, dict_): kk, val = self.__convert_and_redispatch(kk, val) elif isinstance(tmp, list): # list of tuples: each tupe (k,v) being the same as the key/value describe above + treated = False if len(tmp) == 2: - kk, val = tmp - self.__convert_and_redispatch(kk, val) - else: + if isinstance(tmp[0], tuple): + # there are 2 tuples in the list, i dont treat it as a tuple + treated = False + else: + # i treat it as a tuple + if len(tmp) != 2: + raise AmbiguousAction("When asking for redispatching with a tuple, you should make a " + "of tuple of 2 elements, the first one being the id of the" + "generator to redispatch, the second one the value of the " + "redispatching.") + kk, val = tmp + self.__convert_and_redispatch(kk, val) + treated = True + + if not treated: for el in tmp: if len(el) != 2: raise AmbiguousAction("When asking for redispatching with a list, you should make a list" @@ -769,6 +792,7 @@ def _digest_redispatching(self, dict_): "redispatching.") kk, val = el self.__convert_and_redispatch(kk, val) + elif isinstance(tmp, tuple): if len(tmp) != 2: raise AmbiguousAction("When asking for redispatching with a tuple, you should make a " @@ -945,47 +969,55 @@ def _check_for_ambiguity(self): # check size if "load_p" in self._dict_inj: if len(self._dict_inj["load_p"]) != self.n_load: - raise InvalidNumberOfLoads("This action acts on {} loads while there are {} in the _grid".format(len(self._dict_inj["load_p"]), self.n_load)) + raise InvalidNumberOfLoads("This action acts on {} loads while there are {} " + "in the _grid".format(len(self._dict_inj["load_p"]), self.n_load)) if "load_q" in self._dict_inj: if len(self._dict_inj["load_q"]) != self.n_load: - raise InvalidNumberOfLoads("This action acts on {} loads while there are {} in the _grid".format(len(self._dict_inj["load_q"]), self.n_load)) + raise InvalidNumberOfLoads("This action acts on {} loads while there are {} in " + "the _grid".format(len(self._dict_inj["load_q"]), self.n_load)) if "prod_p" in self._dict_inj: if len(self._dict_inj["prod_p"]) != self.n_gen: - raise InvalidNumberOfGenerators("This action acts on {} generators while there are {} in the _grid".format(len(self._dict_inj["prod_p"]), self.n_gen)) + raise InvalidNumberOfGenerators("This action acts on {} generators while there are {} in " + "the _grid".format(len(self._dict_inj["prod_p"]), self.n_gen)) if "prod_v" in self._dict_inj: if len(self._dict_inj["prod_v"]) != self.n_gen: - raise InvalidNumberOfGenerators("This action acts on {} generators while there are {} in the _grid".format(len(self._dict_inj["prod_v"]), self.n_gen)) + raise InvalidNumberOfGenerators("This action acts on {} generators while there are {} in " + "the _grid".format(len(self._dict_inj["prod_v"]), self.n_gen)) if len(self._switch_line_status) != self.n_line: - raise InvalidNumberOfLines("This action acts on {} lines while there are {} in the _grid".format(len(self._switch_line_status), self.n_line)) + raise InvalidNumberOfLines("This action acts on {} lines while there are {} in " + "the _grid".format(len(self._switch_line_status), self.n_line)) if len(self._set_topo_vect) != self.dim_topo: - raise InvalidNumberOfObjectEnds("This action acts on {} ends of object while there are {} in the _grid".format(len(self._set_topo_vect), self.dim_topo)) + raise InvalidNumberOfObjectEnds("This action acts on {} ends of object while there are {} " + "in the _grid".format(len(self._set_topo_vect), self.dim_topo)) if len(self._change_bus_vect) != self.dim_topo: - raise InvalidNumberOfObjectEnds("This action acts on {} ends of object while there are {} in the _grid".format(len(self._change_bus_vect), self.dim_topo)) + raise InvalidNumberOfObjectEnds("This action acts on {} ends of object while there are {} " + "in the _grid".format(len(self._change_bus_vect), self.dim_topo)) if len(self._redispatch) != self.n_gen: - raise InvalidNumberOfGenerators("This action acts on {} generators (redispatching= while there are {} in the grid".format( - len(self._redispatch), self.n_gen)) + raise InvalidNumberOfGenerators("This action acts on {} generators (redispatching= while " + "there are {} in the grid".format(len(self._redispatch), self.n_gen)) # redispatching specific check - if np.any(self._redispatch > self.gen_max_ramp_up): - raise InvalidRedispatching("Some redispatching amount are above the maximum ramp up") - if np.any(-self._redispatch > self.gen_max_ramp_down): - raise InvalidRedispatching("Some redispatching amount are bellow the maximum ramp down") + if np.any(self._redispatch !=0.): + if np.any(self._redispatch > self.gen_max_ramp_up): + raise InvalidRedispatching("Some redispatching amount are above the maximum ramp up") + if np.any(-self._redispatch > self.gen_max_ramp_down): + raise InvalidRedispatching("Some redispatching amount are bellow the maximum ramp down") - if np.any(self._redispatch[~self.gen_redispatchable] != 0.): - raise InvalidRedispatching("Trying to apply a redispatching action on a non redispatchable generator") + if np.any(self._redispatch[~self.gen_redispatchable] != 0.): + raise InvalidRedispatching("Trying to apply a redispatching action on a non redispatchable generator") - if "prod_p" in self._dict_inj: - new_p = self._dict_inj["prod_p"] - tmp_p = new_p + self._redispatch - if np.any(tmp_p > self.gen_pmax): - raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " - "are above pmax for some generator.") - if np.any(tmp_p < self.gen_pmin): - raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " - "are below pmin for some generator.") + if "prod_p" in self._dict_inj: + new_p = self._dict_inj["prod_p"] + tmp_p = new_p + self._redispatch + if np.any(tmp_p > self.gen_pmax): + raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " + "are above pmax for some generator.") + if np.any(tmp_p < self.gen_pmin): + raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " + "are below pmin for some generator.") # topological action if np.any(self._set_topo_vect[self._change_bus_vect] != 0): @@ -998,84 +1030,26 @@ def _check_for_ambiguity(self): # i reconnect a powerline, i need to check that it's connected on both ends if self._set_topo_vect[self.line_or_pos_topo_vect[q_id]] == 0 or \ self._set_topo_vect[self.line_ex_pos_topo_vect[q_id]] == 0: - raise InvalidLineStatus("You ask to reconnect powerline {} yet didn't tell on which bus.".format(q_id)) + raise InvalidLineStatus("You ask to reconnect powerline {} yet didn't tell on" + " which bus.".format(q_id)) # if i disconnected of a line, but i modify also the bus where it's connected idx = self._set_line_status == -1 id_disc = np.where(idx)[0] if np.any(self._set_topo_vect[self.line_or_pos_topo_vect[id_disc]] > 0) or \ np.any(self._set_topo_vect[self.line_ex_pos_topo_vect[id_disc]] > 0): - raise InvalidLineStatus("You ask to disconnect a powerline but also to connect it to a certain bus.") + raise InvalidLineStatus("You ask to disconnect a powerline but also to connect it " + "to a certain bus.") if np.any(self._change_bus_vect[self.line_or_pos_topo_vect[id_disc]] > 0) or \ np.any(self._change_bus_vect[self.line_ex_pos_topo_vect[id_disc]] > 0): raise InvalidLineStatus("You ask to disconnect a powerline but also to change its bus.") if np.any(self._change_bus_vect[self.line_or_pos_topo_vect[self._set_line_status == 1]]): - raise InvalidLineStatus("You ask to connect an origin powerline but also to *change* the bus to which it is connected. This is ambiguous. You must *set* this bus instead.") + raise InvalidLineStatus("You ask to connect an origin powerline but also to *change* the bus to which it " + "is connected. This is ambiguous. You must *set* this bus instead.") if np.any(self._change_bus_vect[self.line_ex_pos_topo_vect[self._set_line_status == 1]]): - raise InvalidLineStatus("You ask to connect an extremity powerline but also to *change* the bus to which it is connected. This is ambiguous. You must *set* this bus instead.") - - def from_vect(self, vect): - """ - Convert a action given as a vector into a proper :class:`Action`. - - If this method is overloaded, the subclass should make sure that :func:`Action._check_for_ambiguity` is called - after the action has been loaded. - - If this method is overloaded, it is mandatory to overload also: - - - :func:`Action.size` - - :func:`Action.to_vect` - - Parameters - ---------- - vect: :class:`numpy.array`, dtype:float - The array representation of an action - - Returns - ------- - ``None`` - - Raises - ------ - IncorrectNumberOfElements - if the size of the vector is not the same as the result of a call to :func:`Action.size` - """ - if vect.shape[0] != self.size(): - raise IncorrectNumberOfElements("Incorrect number of elements found while load an action from a vector. " - "Found {} elements instead of {}".format(vect.shape[1], self.size())) - prev_ = 0 - next_ = self.n_gen - prod_p = vect[prev_:next_]; prev_ += self.n_gen; next_ += self.n_gen - prod_q = vect[prev_:next_]; prev_ += self.n_gen; next_ += self.n_load - - load_p = vect[prev_:next_]; prev_ += self.n_load; next_ += self.n_load - load_q = vect[prev_:next_]; prev_ += self.n_load; next_ += self.n_line - - if np.any(np.isfinite(prod_p)): - self._dict_inj["prod_p"] = prod_p - if np.any(np.isfinite(prod_q)): - self._dict_inj["prod_v"] = prod_q - if np.any(np.isfinite(load_p)): - self._dict_inj["load_p"] = load_p - if np.any(np.isfinite(load_q)): - self._dict_inj["load_q"] = load_q - - self._set_line_status = vect[prev_:next_]; prev_ += self.n_line; next_ += self.n_line - self._set_line_status = self._set_line_status.astype(np.int) - self._switch_line_status = vect[prev_:next_]; prev_ += self.n_line; next_ += self.dim_topo - self._switch_line_status = self._switch_line_status.astype(np.bool) - self._set_topo_vect = vect[prev_:next_]; prev_ += self.dim_topo; next_ += self.dim_topo - self._set_topo_vect = self._set_topo_vect.astype(np.int) - self._change_bus_vect = vect[prev_:next_]; prev_ += self.dim_topo; next_ += self.n_line - self._change_bus_vect = self._change_bus_vect.astype(np.bool) - - self._hazards = vect[prev_:next_]; prev_ += self.n_line; next_ += self.n_line - self._hazards = self._hazards.astype(np.bool) - self._maintenance = vect[prev_:]; prev_ += self.n_line; next_ += self.n_line - self._maintenance = self._maintenance.astype(np.bool) - - self._check_for_ambiguity() + raise InvalidLineStatus("You ask to connect an extremity powerline but also to *change* the bus to which " + "it is connected. This is ambiguous. You must *set* this bus instead.") def sample(self): """ @@ -1152,6 +1126,11 @@ def __str__(self): if not inj_changed: res.append("\t - NOT change anything to the injections") + if np.any(self._redispatch != 0.): + res.append("\t - perform the following redispatching action: {}".format(self._redispatch)) + else: + res.append("\t - NOT perform any redispatching action") + # handles actions on force line status force_linestatus_change = False if np.any(self._set_line_status == 1): @@ -1431,7 +1410,7 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub my_id = self.gen_pos_topo_vect[gen_id] res["change_bus"] = self._change_bus_vect[my_id] res["set_bus"] = self._set_topo_vect[my_id] - res["redispatch"] = self._redispatch[my_id] + res["redispatch"] = self._redispatch[gen_id] elif line_id is not None: if substation_id is not None: @@ -1545,48 +1524,6 @@ def update(self, dict_): self._digest_change_status(dict_) return self - def from_vect(self, vect): - """ - See :func:`Action.from_vect` for a detailed description of this method. - - Nothing more is made except the initial vector is smaller. - - Parameters - ---------- - vect: :class:`numpy.array`, dtype:float - A vector reprenseting an instance of :class:`.` - - Returns - ------- - - """ - self.reset() - # pdb.set_trace() - if vect.shape[0] != self.size(): - raise IncorrectNumberOfElements( - "Incorrect number of elements found while loading a \"TopologyAction\" from a vector. Found {} elements instead of {}".format( - vect.shape[1], self.size())) - prev_ = 0 - next_ = self.n_line - - self._set_line_status = vect[prev_:next_] - prev_ += self.n_line - next_ += self.n_line - self._set_line_status = self._set_line_status.astype(np.int) - self._switch_line_status = vect[prev_:next_] - prev_ += self.n_line - next_ += self.dim_topo - self._switch_line_status = self._switch_line_status.astype(np.bool) - self._set_topo_vect = vect[prev_:next_] - prev_ += self.dim_topo - next_ += self.dim_topo - self._set_topo_vect = self._set_topo_vect.astype(np.int) - self._change_bus_vect = vect[prev_:] - prev_ += self.dim_topo - - self._change_bus_vect = self._change_bus_vect.astype(np.bool) - self._check_for_ambiguity() - def sample(self): """ @@ -1689,33 +1626,8 @@ def update(self, dict_): return self - def from_vect(self, vect): - """ - See :func:`Action.from_vect` for a detailed description of this method. - - Nothing more is made except the initial vector is (much) smaller. - - Parameters - ---------- - vect: :class:`numpy.array`, dtype:float - A vector reprenseting an instance of :class:`.` - - Returns - ------- - - """ - self.reset() - if vect.shape[0] != self.size(): - raise IncorrectNumberOfElements( - "Incorrect number of elements found while loading a \"TopologyAction\" from a vector. Found {} elements instead of {}".format( - vect.shape[1], self.size())) - prev_ = 0 - next_ = self.n_line - - self._set_line_status = vect[prev_:next_] - self._set_line_status = self._set_line_status.astype(np.int) + def check_space_legit(self): self.disambiguate_reconnection() - self._check_for_ambiguity() def disambiguate_reconnection(self): @@ -1772,8 +1684,7 @@ class SerializableActionSpace(SerializableSpace): action (see :func:`Action.size`) or to sample a new Action (see :func:`grid2op.Action.Action.sample`) """ - def __init__(self, gridobj, - actionClass=Action): + def __init__(self, gridobj, actionClass=Action): """ Parameters diff --git a/grid2op/Backend.py b/grid2op/Backend.py index 56dd0d93d..c8e99148e 100644 --- a/grid2op/Backend.py +++ b/grid2op/Backend.py @@ -155,7 +155,8 @@ def load_grid(self, path, filename=None): And then fill all the helpers used by the backend eg. all the attributes of :class:`Space.GridObjects`. After a the call to :func:`Backend.load_grid` has been performed, the backend should be in such a state where - the :class:`Space.GridObjects` is properly set up. + the :class:`grid2op.Space.GridObjects` is properly set up. See the description of + :class:`grid2op.Space.GridObjects` to know which attributes should be set here and which should not. :param path: the path to find the powergrid :type path: :class:`string` @@ -403,15 +404,17 @@ def get_topo_vect(self): load of id 1 is connected to the second bus of its substation. You can check which object of the powerlines is represented by each component of this vector by looking at the - `*_pos_topo_vect` (*eg.* :attr:`Backend.load_pos_topo_vect`) vectors. For each elements it gives its position - in this vector. + `*_pos_topo_vect` (*eg.* :attr:`grid2op.Space.GridObjects.load_pos_topo_vect`) vectors. + For each elements it gives its position in this vector. TODO make an example here on how to use this! Returns -------- - res: `numpy.ndarray` + + res: `numpy.ndarray`, dtype: ``int`` An array saying to which bus the object is connected. + """ pass @@ -422,11 +425,11 @@ def generators_info(self): Returns ------- - prod_p ``numpy.array`` + prod_p ``numpy.ndarray`` The active power production for each generator - prod_q ``numpy.array`` + prod_q ``numpy.ndarray`` The reactive power production for each generator - prod_v ``numpy.array`` + prod_v ``numpy.ndarray`` The voltage magnitude of the bus to which each generators is connected """ pass @@ -438,11 +441,11 @@ def loads_info(self): Returns ------- - load_p ``numpy.array`` + load_p ``numpy.ndarray`` The active power consumption for each load - load_q ``numpy.array`` + load_q ``numpy.ndarray`` The reactive power consumption for each load - load_v ``numpy.array`` + load_v ``numpy.ndarray`` The voltage magnitude of the bus to which each load is connected """ pass @@ -456,13 +459,13 @@ def lines_or_info(self): Returns ------- - p_or ``numpy.array`` + p_or ``numpy.ndarray`` the origin active power flowing on the lines - q_or ``numpy.array`` + q_or ``numpy.ndarray`` the origin reactive power flowing on the lines - v_or ``numpy.array`` + v_or ``numpy.ndarray`` the voltage magnitude at the origin of each powerlines - a_or ``numpy.array`` + a_or ``numpy.ndarray`` the current flow at the origin of each powerlines """ pass @@ -476,13 +479,13 @@ def lines_ex_info(self): Returns ------- - p_ex ``numpy.array`` + p_ex ``numpy.ndarray`` the extremity active power flowing on the lines - q_ex ``numpy.array`` + q_ex ``numpy.ndarray`` the extremity reactive power flowing on the lines - v_ex ``numpy.array`` + v_ex ``numpy.ndarray`` the voltage magnitude at the extremity of each powerlines - a_ex ``numpy.array`` + a_ex ``numpy.ndarray`` the current flow at the extremity of each powerlines """ pass @@ -501,13 +504,13 @@ def shunt_info(self): Returns ------- - shunt_p ``numpy.array`` + shunt_p ``numpy.ndarray`` For each shunt, the active power it withdraw at the bus to which it is connected. - shunt_q ``numpy.array`` + shunt_q ``numpy.ndarray`` For each shunt, the reactive power it withdraw at the bus to which it is connected. - shunt_v ``numpy.array`` + shunt_v ``numpy.ndarray`` For each shunt, the voltage magnitude of the bus to which it is connected. - shunt_bus ``numpy.array`` + shunt_bus ``numpy.ndarray`` For each shunt, the bus id to which it is connected. """ return [], [], [], [] @@ -624,14 +627,14 @@ def check_kirchoff(self): Returns ------- - p_subs ``numpy.array`` + p_subs ``numpy.ndarray`` sum of injected active power at each substations - q_subs ``numpy.array`` + q_subs ``numpy.ndarray`` sum of injected reactive power at each substations - p_bus ``numpy.array`` + p_bus ``numpy.ndarray`` sum of injected active power at each buses. It is given in form of a matrix, with number of substations as row, and number of columns equal to the maximum number of buses for a substation - q_bus ``numpy.array`` + q_bus ``numpy.ndarray`` sum of injected reactive power at each buses. It is given in form of a matrix, with number of substations as row, and number of columns equal to the maximum number of buses for a substation """ diff --git a/grid2op/BackendPandaPower.py b/grid2op/BackendPandaPower.py index 146bc4b46..f042c51cb 100644 --- a/grid2op/BackendPandaPower.py +++ b/grid2op/BackendPandaPower.py @@ -284,7 +284,7 @@ def load_grid(self, path=None, filename=None): self._compute_pos_big_topo() - self._dim_topo = np.sum(self.sub_info) + self.dim_topo = np.sum(self.sub_info) # utilities for imeplementing apply_action diff --git a/grid2op/Observation.py b/grid2op/Observation.py index 5875365b2..aa9d3cbda 100644 --- a/grid2op/Observation.py +++ b/grid2op/Observation.py @@ -1230,26 +1230,6 @@ def bus_connectivity_matrix(self): # pdb.set_trace() return self.bus_connectivity_matrix_ - def size(self): - """ - Return the size of the flatten observation vector. - For this CompletObservation: - - - 6 calendar data - - each generator is caracterized by 3 values: p, q and v - - each load is caracterized by 3 values: p, q and v - - each end of a powerline by 4 values: flow p, flow q, v, current flow - - each line have also a status - - each line can also be impossible to reconnect - - the topology vector of dim `dim_topo` - - :return: the size of the flatten observation vector. - """ - # TODO documentation (update) - # res = 6 + 3 * self.n_gen + 3 * self.n_load + 15 * self.n_line + self.dim_topo + self.n_sub - res = np.sum(self.shape()) - return res - class SerializableObservationSpace(SerializableSpace): """ diff --git a/grid2op/Space.py b/grid2op/Space.py index 2f641e9bb..bd315250d 100644 --- a/grid2op/Space.py +++ b/grid2op/Space.py @@ -2,7 +2,7 @@ This class abstract the main components of Action, Observation, ActionSpace and ObservationSpace. It basically represents a powergrid (the object in it) in a format completely agnostic to the solver use to compute -the powerflows (:class:grid2op.Backend.Backend). +the powerflows (:class:`grid2op.Backend.Backend`). """ import re @@ -37,6 +37,95 @@ class GridObjects: :class:`grid2op.Backend.Backend` all inherit from this class. This means that each of the above has its own representation of the powergrid. + + The modeling adopted for describing a powergrid is the following: + + - only the main objects of a powergrid are represented. An "object" is either a load (consumption) a generator + (production), an end of a powerline (each powerline have exactly two extremities: "origin" (or) + and "extremity" (ext)). + - every "object" (see above) is connected to a unique substation. Each substation then counts a given (fixed) + number of objects connected to it. [in ths platform we don't consider the possibility to build new "objects" as + of today] + + For each object, the bus to which it is connected is given in the `*_to_subid` (for + example :attr:`GridObjects.load_to_subid` gives, for each load, the id of the substation to which it is + connected) + + We suppose that, at every substation, each object (if connected) can be connected to either "busbar" 1 or + "busbar" 2. This means that, at maximum, there are 2 inedpendant buses for each substation. + + With this hypothesis, we can represent (thought experiment) each substation by a vector. This vector has as + many components than the number of objects in the substation (following the previous example, the vector + representing the first substation would have 5 components). And each component of this vector would represent + a fixed element in it. For example, if say, the load with id 1 is connected to the first element, there would be + a unique component saying if load with id 1 is connected to busbar 1 or busbar 2. For the generators, this + id in this (fictive) vector is indicated in the :attr:`GridObjects.gen_to_sub_pos` vector. For example the first + position of :attr:`GridObjects.gen_to_sub_pos` indicates on which component of the (fictive) vector representing the + substation 1 to look to know on which bus the first generator is connected. + + We define the "topology" as the busbar to which each object is connected: each object being connected to either + busbar 1 or busbar 2, this topology can be represented by a vector of fixed size (and it actually is in + :attr:`grid2op.Observation.Observation.topo_vect` or in :func:`grid2op.Backend.Backend.get_topo_vect`). There are + multiple ways to make such vector. We decided to concatenate all the (fictive) vectors described above. This + concatenation represent the actual topology of this powergrid at a given timestep. This class doesn't store this + information (see :class:`grid2op.Observation.Observation` for such purpose). + This entails taht: + + - the bus to which each object on a substation will be stored in consecutive components of such a vector. For + example, if the first substation of the grid has 5 elements connected to it, then the first 5 elements of + :attr:`grid2op.Observation.Observation.topo_vect` will represent these 5 elements. The number of elements + in each substation is given in :attr:`grid2op.Space.GridObjects.sub_info`. + - the substation are stored in "order": objects of the first substations are represented, then this is the objects + of the second substation etc. So in the example above, the 6th element of + :attr:`grid2op.Observation.Observation.topo_vect` is an object connected to the second substation. + - to know on which position of this "topology vector" we can find the information relative a specific element + it is possible to: + + - method 1 (not recommended): + + i) retrieve the substation to which this object is connected (for example looking at :attr:`GridObjects.line_or_to_subid` [l_id] to know on which substation is connected the origin of powerline with id $l_id$.) + ii) once this substation id is known, compute which are the components of the topological vector that encodes information about this substation. For example, if the substation id `sub_id` is 4, we a) count the number of elements in substations with id 0, 1, 2 and 3 (say it's 42) we know, by definition that the substation 4 is encoded in ,:attr:`grid2op.Observation.Observation.topo_vect` starting at component 42 and b) this substations has :attr:`GridObjects.sub_info` [sub_id] elements (for the sake of the example say it's 5) then the end of the vector for substation 4 will be 42+5 = 47. Finally, we got the representation of the "local topology" of the substation 4 by looking at :attr:`grid2op.Observation.Observation.topo_vect` [42:47]. + iii) retrieve which component of this vector of dimension 5 (remember we assumed substation 4 had 5 elements) encodes information about the origin end of line with id `l_id`. This information is given in :attr:`GridObjects.line_or_to_sub_pos` [l_id]. This is a number between 0 and 4, say it's 3. and 3 being the index of the object in the substation) + + - method 2 (not recommended): all of the above is stored (for the same powerline) in the + :attr:`GridObjects.line_or_pos_topo_vect` [l_id]. In the example above, we will have: + :attr:`GridObjects.line_or_pos_topo_vect` [l_id] = 45 (=42+3: + 42 being the index on which the substation started and 3 being the index of the object in the substation) + - method 3 (recommended): use any of the function that computes it for you: + :func:`grid2op.Observation.Observation.state_of` is such an interesting method. The two previous methods + "method 1" and "method 2" were presented as a way to give detailed and "concrete" example on how the + modeling of the powergrid work. + + + + For a given powergrid, this object should be initialized once in the :class:`grid2op.Backend.Backend` when + the first call to :func:`grid2op.Backend.Backend.load_grid` is performed. In particular the following attributes + must necessarily be defined (see above for a detailed description of some of the attributes): + + - :attr:`GridObjects.n_line` + - :attr:`GridObjects.n_gen` + - :attr:`GridObjects.n_load` + - :attr:`GridObjects.n_sub` + - :attr:`GridObjects.sub_info` + - :attr:`GridObjects.dim_topo` + - :attr:`GridObjects.load_to_subid` + - :attr:`GridObjects.gen_to_subid` + - :attr:`GridObjects.line_or_to_subid` + - :attr:`GridObjects.line_ex_to_subid` + - :attr:`GridObjects.load_to_sub_pos` + - :attr:`GridObjects.gen_to_sub_pos` + - :attr:`GridObjects.line_or_to_sub_pos` + - :attr:`GridObjects.line_ex_to_sub_pos` + - :attr:`GridObjects.gen_type` + - :attr:`GridObjects.gen_pmin` + - :attr:`GridObjects.gen_pmax` + - :attr:`GridObjects.gen_redispatchable` + - :attr:`GridObjects.gen_max_ramp_up` + - :attr:`GridObjects.gen_max_ramp_down` + + A call to the function :func:`GridObjects._compute_pos_big_topo` allow to compute the \*_pos_topo_vect attributes + (for example :attr:`GridObjects.line_ex_pos_topo_vect`) can be computed from this data. + **NB** it does not store any information about the current state of the powergrid. It stores information that cannot be modified by the Agent, the Environment or any other entity. @@ -44,30 +133,38 @@ class GridObjects: ---------- n_line: :class:`int` - number of powerline in the _grid + number of powerline in the powergrid n_gen: :class:`int` - number of generators in the _grid + number of generators in the powergrid n_load: :class:`int` + number of loads in the + + n_sub: :class:`int` number of loads in the powergrid - sub_info: :class:`numpy.array`, dtype:int + dim_topo: :class:`int` + Total number of objects in the powergrid. This is also the dimension of the "topology vector" defined above. + + sub_info: :class:`numpy.ndarray`, dtype:int for each substation, gives the number of elements connected to it - load_to_subid: :class:`numpy.array`, dtype:int - for each load, gives the id the substation to which it is connected + load_to_subid: :class:`numpy.ndarray`, dtype:int + for each load, gives the id the substation to which it is connected. For example, + :attr:`GridObjects.load_to_subid`[load_id] gives the id of the substation to which the load of id + `load_id` is connected. - gen_to_subid: :class:`numpy.array`, dtype:int + gen_to_subid: :class:`numpy.ndarray`, dtype:int for each generator, gives the id the substation to which it is connected - line_or_to_subid: :class:`numpy.array`, dtype:int + line_or_to_subid: :class:`numpy.ndarray`, dtype:int for each lines, gives the id the substation to which its "origin" end is connected - line_ex_to_subid: :class:`numpy.array`, dtype:int + line_ex_to_subid: :class:`numpy.ndarray`, dtype:int for each lines, gives the id the substation to which its "extremity" end is connected - load_to_sub_pos: :class:`numpy.array`, dtype:int + load_to_sub_pos: :class:`numpy.ndarray`, dtype:int The topology if of the subsation *i* is given by a vector, say *sub_topo_vect* of size :attr:`GridObjects.sub_info`\[i\]. For a given load of id *l*, :attr:`Action.GridObjects.load_to_sub_pos`\[l\] is the index @@ -75,16 +172,16 @@ class GridObjects: *sub_topo_vect\[ action.load_to_sub_pos\[l\] \]=2* then load of id *l* is connected to the second bus of the substation. - gen_to_sub_pos: :class:`numpy.array`, dtype:int + gen_to_sub_pos: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_to_sub_pos` but for generators. - line_or_to_sub_pos: :class:`numpy.array`, dtype:int + line_or_to_sub_pos: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_to_sub_pos` but for "origin" end of powerlines. - line_ex_to_sub_pos: :class:`numpy.array`, dtype:int + line_ex_to_sub_pos: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_to_sub_pos` but for "extremity" end of powerlines. - load_pos_topo_vect: :class:`numpy.array`, dtype:int + load_pos_topo_vect: :class:`numpy.ndarray`, dtype:int It has a similar role as :attr:`GridObjects.load_to_sub_pos` but it gives the position in the vector representing the whole topology. More concretely, if the complete topology of the powergrid is represented here by a vector *full_topo_vect* resulting of the concatenation of the topology vector for each substation @@ -93,25 +190,25 @@ class GridObjects: More formally, if *_topo_vect\[ backend.load_pos_topo_vect\[l\] \]=2* then load of id l is connected to the second bus of the substation. - gen_pos_topo_vect: :class:`numpy.array`, dtype:int + gen_pos_topo_vect: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_pos_topo_vect` but for generators. - line_or_pos_topo_vect: :class:`numpy.array`, dtype:int + line_or_pos_topo_vect: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_pos_topo_vect` but for "origin" end of powerlines. - line_ex_pos_topo_vect: :class:`numpy.array`, dtype:int + line_ex_pos_topo_vect: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_pos_topo_vect` but for "extremity" end of powerlines. - name_load: :class:`numpy.array`, dtype:str + name_load: :class:`numpy.ndarray`, dtype:str ordered name of the loads in the grid. - name_gen: :class:`numpy.array`, dtype:str + name_gen: :class:`numpy.ndarray`, dtype:str ordered name of the productions in the grid. - name_line: :class:`numpy.array`, dtype:str + name_line: :class:`numpy.ndarray`, dtype:str ordered names of the powerline in the grid. - name_sub: :class:`numpy.array`, dtype:str + name_sub: :class:`numpy.ndarray`, dtype:str ordered names of the substation in the grid attr_list_vect: ``list`` @@ -121,18 +218,34 @@ class GridObjects: important that this vector is properly set. All the attributes with the name on this vector should have consistently the same size and shape, otherwise some methods will not behave as expected. - _vectorized: :class:`numpy.array`, dtype:float + _vectorized: :class:`numpy.ndarray`, dtype:float The representation of the GridObject as a vector. See the help of :func:`GridObjects.to_vect` and :func:`GridObjects.from_vect` for more information. **NB** for performance reason, the convertion of the internal representation to a vector is not performed at any time. It is only performed when :func:`GridObjects.to_vect` is called the first time. Otherwise, this attribute is set to ``None``. - gen_type - gen_pmin - gen_pmax - gen_redispatchable - gen_ramp_up_max - gen_ramp_down_min + gen_type: :class:`numpy.ndarray`, dtype:str + Type of the generators, among: "solar", "wind", "hydro", "thermal" and "nuclear" + + gen_pmin: :class:`numpy.ndarray`, dtype:float + Minimum active power production needed for a generator to work properly. + + gen_pmax: :class:`numpy.ndarray`, dtype:float + Maximum active power production needed for a generator to work properly. + + gen_redispatchable: :class:`numpy.ndarray`, dtype:bool + For each generator, it says if the generator is dispatchable or not. + + gen_max_ramp_up: :class:`numpy.ndarray`, dtype:float + Maximum active power variation possible between two consecutive timestep for each generator: + a redispatching action + on generator `g_id` cannot be above :attr:`GridObjects.gen_ramp_up_max` [`g_id`] + + gen_max_ramp_down: :class:`numpy.ndarray`, dtype:float + Minimum active power variationpossible between two consecutive timestep for each generator: a redispatching + action + on generator `g_id` cannot be below :attr:`GridObjects.gen_ramp_down_min` [`g_id`] + """ def __init__(self): # name of the objects @@ -199,6 +312,8 @@ def _get_array_from_attr_name(self, attr_name): This function allows to return the proper attribute vector that can be inspected in the shape, size, dtype, from_vect and to_vect method. + If this function is overloaded, then the _assign_attr_from_name must be too. + Parameters ---------- attr_name: ``str`` @@ -283,6 +398,26 @@ def dtype(self): res = np.array([self._get_array_from_attr_name(el).dtype for el in self.attr_list_vect]) return res + def _assign_attr_from_name(self, attr_nm, vect): + """ + Assign the proper attributes with name 'attr_nm' with the value of the vector vect + + If this function is overloaded, then the _get_array_from_attr_name must be too. + + Parameters + ---------- + attr_nm + vect + + Returns + ------- + ``None`` + """ + self.__dict__[attr_nm] = vect + + def check_space_legit(self): + pass + def from_vect(self, vect): """ Convert a GridObjects, represented as a vector, into an GridObjects object. @@ -312,8 +447,9 @@ def from_vect(self, vect): self._raise_error_attr_list_none() prev_ = 0 for attr_nm, sh, dt in zip(self.attr_list_vect, self.shape(), self.dtype()): - self.__dict__[attr_nm] = vect[prev_:(prev_ + sh)].astype(dt) + self._assign_attr_from_name(attr_nm, vect[prev_:(prev_ + sh)].astype(dt)) prev_ += sh + self.check_space_legit() def size(self): """ @@ -343,55 +479,55 @@ def init_grid_vect(self, name_prod, name_load, name_line, name_sub, sub_info, Parameters ---------- - name_prod: :class:`numpy.array`, dtype:str + name_prod: :class:`numpy.ndarray`, dtype:str Used to initialized :attr:`GridObjects.name_gen` - name_load: :class:`numpy.array`, dtype:str + name_load: :class:`numpy.ndarray`, dtype:str Used to initialized :attr:`GridObjects.name_load` - name_line: :class:`numpy.array`, dtype:str + name_line: :class:`numpy.ndarray`, dtype:str Used to initialized :attr:`GridObjects.name_line` - name_sub: :class:`numpy.array`, dtype:str + name_sub: :class:`numpy.ndarray`, dtype:str Used to initialized :attr:`GridObjects.name_sub` - sub_info: :class:`numpy.array`, dtype:int + sub_info: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.sub_info` - load_to_subid: :class:`numpy.array`, dtype:int + load_to_subid: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.load_to_subid` - gen_to_subid: :class:`numpy.array`, dtype:int + gen_to_subid: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.gen_to_subid` - line_or_to_subid: :class:`numpy.array`, dtype:int + line_or_to_subid: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_or_to_subid` - line_ex_to_subid: :class:`numpy.array`, dtype:int + line_ex_to_subid: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_ex_to_subid` - load_to_sub_pos: :class:`numpy.array`, dtype:int + load_to_sub_pos: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.load_to_sub_pos` - gen_to_sub_pos: :class:`numpy.array`, dtype:int + gen_to_sub_pos: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.gen_to_sub_pos` - line_or_to_sub_pos: :class:`numpy.array`, dtype:int + line_or_to_sub_pos: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_or_to_sub_pos` - line_ex_to_sub_pos: :class:`numpy.array`, dtype:int + line_ex_to_sub_pos: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_ex_to_sub_pos` - load_pos_topo_vect: :class:`numpy.array`, dtype:int + load_pos_topo_vect: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.load_pos_topo_vect` - gen_pos_topo_vect: :class:`numpy.array`, dtype:int + gen_pos_topo_vect: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.gen_pos_topo_vect` - line_or_pos_topo_vect: :class:`numpy.array`, dtype:int + line_or_pos_topo_vect: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_or_pos_topo_vect` - line_ex_pos_topo_vect: :class:`numpy.array`, dtype:int + line_ex_pos_topo_vect: :class:`numpy.ndarray`, dtype:int Used to initialized :attr:`GridObjects.line_ex_pos_topo_vect` """ @@ -470,7 +606,7 @@ def assert_grid_correct(self): """ Performs some checking on the loaded _grid to make sure it is consistent. It also makes sure that the vector such as *sub_info*, *load_to_subid* or *gen_to_sub_pos* are of the - right type eg. numpy.array with dtype: np.int + right type eg. numpy.ndarray with dtype: np.int It is called after the _grid has been loaded. diff --git a/grid2op/tests/test_Action.py b/grid2op/tests/test_Action.py index ff9db0312..1a0a257ed 100644 --- a/grid2op/tests/test_Action.py +++ b/grid2op/tests/test_Action.py @@ -82,6 +82,8 @@ def setUp(self): 'line_ex_pos_topo_vect': [3, 19, 9, 13, 20, 14, 21, 30, 35, 24, 45, 48, 52, 33, 36, 42, 55, 43, 49, 53], 'subtype': 'Action.Action'} + self.size_act = 229 + def tearDown(self): pass # self.backend._grid.delete() @@ -102,10 +104,10 @@ def test_size(self): def test_proper_size(self): action = self.helper_action() - assert action.size() == 224 + assert action.size() == self.size_act def test_size_action_space(self): - assert self.helper_action.size() == 224 + assert self.helper_action.size() == self.size_act def test_print_notcrash(self): """ @@ -339,7 +341,7 @@ def test_action_str(self): action = self.helper_action({"change_bus": {"substations_id": [(id_1, arr1)]}, "set_bus": {"substations_id": [(id_2, arr2)]}}) res = action.__str__() - act_str = 'This action will:\n\t - NOT change anything to the injections\n\t - NOT force any line status\n\t - NOT switch any line status\n\t - Change the bus of the following element:\n\t \t - switch bus of line (origin) 4 [on substation 1]\n\t \t - switch bus of load 0 [on substation 1]\n\t \t - switch bus of generator 1 [on substation 1]\n\t - Set the bus of the following element:\n\t \t - assign bus 1 to line (extremity) 18 [on substation 12]\n\t \t - assign bus 1 to line (origin) 19 [on substation 12]\n\t \t - assign bus 2 to load 9 [on substation 12]\n\t \t - assign bus 2 to line (extremity) 12 [on substation 12]' + act_str = 'This action will:\n\t - NOT change anything to the injections\n\t - NOT perform any redispatching action\n\t - NOT force any line status\n\t - NOT switch any line status\n\t - Change the bus of the following element:\n\t \t - switch bus of line (origin) 4 [on substation 1]\n\t \t - switch bus of load 0 [on substation 1]\n\t \t - switch bus of generator 1 [on substation 1]\n\t - Set the bus of the following element:\n\t \t - assign bus 1 to line (extremity) 18 [on substation 12]\n\t \t - assign bus 1 to line (origin) 19 [on substation 12]\n\t \t - assign bus 2 to load 9 [on substation 12]\n\t \t - assign bus 2 to line (extremity) 12 [on substation 12]' assert res == act_str def test_to_vect(self): @@ -352,7 +354,9 @@ def test_to_vect(self): res = action.to_vect() tmp = np.array([np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, - np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN,0., 0., 0., 0., 0., 0., 0., + np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, np.NaN, + 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., @@ -395,6 +399,7 @@ def test_from_vect(self): vect_act1 = action1.to_vect() action2.from_vect(vect_act1) + # pdb.set_trace() # if i load an action with from_vect it's equal to the original one assert action1 == action2 vect_act2 = action2.to_vect() @@ -437,7 +442,7 @@ def test_call_change_set(self): "injection": {"load_p": new_vect, "load_q": new_vect2}, "change_line_status": change_status_orig, "set_line_status": set_status_orig}) - dict_injection, set_status, change_status, set_topo_vect, switcth_topo_vect = action() + dict_injection, set_status, change_status, set_topo_vect, switcth_topo_vect, redispatching = action() assert "load_p" in dict_injection assert np.all(dict_injection["load_p"] == new_vect) assert "load_q" in dict_injection @@ -573,5 +578,10 @@ def test_shape_correct(self): act = self.helper_action_env({}) assert act.shape().shape == act.dtype().shape + def test_redispatching(self): + act = self.helper_action_env({"redispatch": [1, 10]}) + act = self.helper_action_env({"redispatch": [(1, 10), (2, 100)]}) + act = self.helper_action_env({"redispatch": np.array([10, 20, 30, 40, 50])}) + if __name__ == "__main__": unittest.main() From d8a99d2c52567c46d941b905f51dbb2dc2ac5ddf Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Tue, 14 Jan 2020 17:47:54 +0100 Subject: [PATCH 03/14] adding some tests and some generator cost in the modeling of the grid, better handling case withtout redispatching --- grid2op/Action.py | 4 + grid2op/Agent.py | 39 +++--- grid2op/Backend.py | 65 +++++++++- grid2op/BackendPandaPower.py | 5 +- grid2op/ChronicsHandler.py | 5 +- grid2op/Environment.py | 14 ++- grid2op/Exceptions.py | 15 +++ grid2op/Observation.py | 52 +++++++- grid2op/Space.py | 201 +++++++++++++++++++++++++++--- grid2op/tests/test_Observation.py | 40 ++++-- 10 files changed, 381 insertions(+), 59 deletions(-) diff --git a/grid2op/Action.py b/grid2op/Action.py index 6b847f484..8ff705e39 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -1001,6 +1001,10 @@ def _check_for_ambiguity(self): # redispatching specific check if np.any(self._redispatch !=0.): + if not self.redispatching_unit_commitment_availble: + raise UnitCommitorRedispachingNotAvailable("Impossible to use a redispatching action in this " + "environment. Please set up the proper costs for generator") + if np.any(self._redispatch > self.gen_max_ramp_up): raise InvalidRedispatching("Some redispatching amount are above the maximum ramp up") if np.any(-self._redispatch > self.gen_max_ramp_down): diff --git a/grid2op/Agent.py b/grid2op/Agent.py index db00456e7..5702f0fce 100644 --- a/grid2op/Agent.py +++ b/grid2op/Agent.py @@ -236,13 +236,8 @@ def _get_tested_action(self, observation): action = self.action_space({"change_line_status": tmp}) if not observation.line_status[i]: # so the action consisted in reconnecting the powerline - # i need to say on which bus + # i need to say on which bus (always on bus 1 for this type of agent) action = action.update({"set_bus": {"lines_or_id": [(i, 1)], "lines_ex_id": [(i, 1)]}}) - # name_element = self.action_space.name_line[i] - # action = self.action_space.set_bus(name_element, 1, extremity="or", type_element="line", - # previous_action=action) - # action = self.action_space.set_bus(name_element, 1, extremity="ex", type_element="line", - # previous_action=action) res.append(action) return res @@ -259,23 +254,25 @@ class TopologyGreedy(GreedyAgent): """ def __init__(self, action_space): GreedyAgent.__init__(self, action_space) + self.li_actions = None def _get_tested_action(self, observation): - res = [self.action_space({})] # add the do nothing - S = [0, 1] - for sub_id, num_el in enumerate(self.action_space.sub_info): - if num_el < 4: - pass - for tup in itertools.product(S, repeat=num_el-1): - indx = np.full(shape=num_el, fill_value=False, dtype=np.bool) - tup = np.array((0, *tup)).astype(np.bool) # add a zero to first element -> break symmetry - - indx[tup] = True - if np.sum(indx) >= 2 and np.sum(~indx) >= 2: - # i need 2 elements on each bus at least - action = self.action_space({"change_bus": {"substations_id": [(sub_id, indx)]}}) # add action "i disconnect powerline i" - res.append(action) - return res + if self.li_actions is None: + res = [self.action_space({})] # add the do nothing + S = [0, 1] + for sub_id, num_el in enumerate(self.action_space.sub_info): + if num_el < 4: + pass + for tup in itertools.product(S, repeat=num_el-1): + indx = np.full(shape=num_el, fill_value=False, dtype=np.bool) + tup = np.array((0, *tup)).astype(np.bool) # add a zero to first element -> break symmetry + indx[tup] = True + if np.sum(indx) >= 2 and np.sum(~indx) >= 2: + # i need 2 elements on each bus at least + action = self.action_space({"change_bus": {"substations_id": [(sub_id, indx)]}}) + res.append(action) + self.li_actions = res + return self.li_actions class MLAgent(Agent): diff --git a/grid2op/Backend.py b/grid2op/Backend.py index c8e99148e..3733e3cb8 100644 --- a/grid2op/Backend.py +++ b/grid2op/Backend.py @@ -31,10 +31,12 @@ """ import copy +import os +import warnings from abc import ABC, abstractmethod import numpy as np -import warnings +import pandas as pd try: from .Exceptions import * @@ -707,7 +709,68 @@ def check_kirchoff(self): return p_subs, q_subs, p_bus, q_bus + def load_redispacthing_data(self, path, name='prods_charac.csv'): + """ + This method will load everything needed for the redispatching and unit commitment problem. + + Parameters + ---------- + path + name + + Returns + ------- + + """ + # for redispatching + fullpath = os.path.join(path, name) + if not os.path.exists(fullpath): + self.redispatching_unit_commitment_availble = False + return + + df = pd.read_csv(fullpath) + gen_info = {} + for _, row in df.iterrows(): + gen_info[row["name"]] = {"type": row["type"], + "pmax": row["Pmax"], + "pmin": row["Pmin"], + "max_ramp_up": row["max_ramp_up"], + "max_ramp_down": row["max_ramp_down"], + "start_cost": row["start_cost"], + "shutdown_cost": row["shutdown_cost"], + "marginal_cost": row["marginal_cost"], + "min_up_time": row["min_up_time"], + "min_down_time": row["min_down_time"] + } + + self.redispatching_unit_commitment_availble = True + + self.gen_type = np.full(self.n_gen, fill_value="") + self.gen_pmin = np.full(self.n_gen, fill_value=1., dtype=np.float) + self.gen_pmax = np.full(self.n_gen, fill_value=1., dtype=np.float) + self.gen_redispatchable = np.full(self.n_gen, fill_value=False, dtype=np.bool) + self.gen_max_ramp_up = np.full(self.n_gen, fill_value=1., dtype=np.float) + self.gen_max_ramp_down = np.full(self.n_gen, fill_value=1., dtype=np.float) + self.gen_min_uptime = np.full(self.n_gen, fill_value=-1, dtype=np.int) + self.gen_min_downtime = np.full(self.n_gen, fill_value=-1, dtype=np.int) + self.gen_cost_per_MW = np.full(self.n_gen, fill_value=1., dtype=np.float) # marginal cost + self.gen_startup_cost = np.full(self.n_gen, fill_value=1., dtype=np.float) # start cost + self.gen_shutdown_cost = np.full(self.n_gen, fill_value=1., dtype=np.float) # shutdown cost + + for i, gen_nm in enumerate(self.name_gen): + tmp_gen = gen_info["gen_nm"] + self.gen_type[i] = str(tmp_gen["type"]) + self.gen_pmin[i] = float(tmp_gen["pmin"]) + self.gen_pmax[i] = float(tmp_gen["pmax"]) + self.gen_redispatchable[i] = bool(tmp_gen["type"] not in ["wind", "solar"]) + self.gen_max_ramp_up[i] = float(tmp_gen["max_ramp_up"]) + self.gen_max_ramp_down[i] = float(tmp_gen["max_ramp_down"]) + self.gen_min_uptime[i] = int(tmp_gen["min uptime"]) + self.gen_min_downtime[i] = int(tmp_gen["min downtime"]) + self.gen_cost_per_MW[i] = float(tmp_gen["marginal_cost"]) + self.gen_startup_cost[i] = float(tmp_gen["start_cost cost"]) + self.gen_shutdown_cost[i] = float(tmp_gen["shutdown_cost"]) diff --git a/grid2op/BackendPandaPower.py b/grid2op/BackendPandaPower.py index f042c51cb..7d87a8c2d 100644 --- a/grid2op/BackendPandaPower.py +++ b/grid2op/BackendPandaPower.py @@ -109,7 +109,7 @@ def __init__(self): self._number_true_line = -1 self._corresp_name_fun = {} self._get_vector_inj = {} - self._dim_topo = -1 + self.dim_topo = -1 self._vars_action = Action.vars_action self._vars_action_set = Action.vars_action_set @@ -286,7 +286,6 @@ def load_grid(self, path=None, filename=None): self.dim_topo = np.sum(self.sub_info) - # utilities for imeplementing apply_action self._corresp_name_fun = {} @@ -576,7 +575,7 @@ def _reconnect_line(self, id): def get_topo_vect(self): # TODO refactor this, this takes a looong time - res = np.full(self._dim_topo, fill_value=np.NaN, dtype=np.int) + res = np.full(self.dim_topo, fill_value=np.NaN, dtype=np.int) for i, (_, row) in enumerate(self._grid.line.iterrows()): bus_or_id = int(row["from_bus"]) diff --git a/grid2op/ChronicsHandler.py b/grid2op/ChronicsHandler.py index 834914641..a23789b6f 100644 --- a/grid2op/ChronicsHandler.py +++ b/grid2op/ChronicsHandler.py @@ -59,7 +59,10 @@ import pdb -# TODO sous echantillonner ou sur echantilloner les scenario +# TODO sous echantillonner ou sur echantilloner les scenario: need to modify everything that affect the number +# TODO of time steps there, for example "Space.gen_min_time_on" or "params.NB_TIMESTEP_POWERFLOW_ALLOWED" for +# TODO example. And more generally, it would be better to have all of this attributes exported / imported in +# TODO time interval, instead of time steps. # TODO add a class to sample "online" the data. diff --git a/grid2op/Environment.py b/grid2op/Environment.py index d7b9f5d77..bd534e9b2 100644 --- a/grid2op/Environment.py +++ b/grid2op/Environment.py @@ -45,6 +45,7 @@ import os try: + from .Space import GridObjects from .Action import HelperAction, Action, TopologyAction from .Exceptions import * from .Observation import CompleteObservation, ObservationHelper, Observation @@ -70,7 +71,7 @@ # TODO have a viewer / renderer now -class Environment: +class Environment(GridObjects): """ Attributes @@ -178,6 +179,10 @@ class Environment: hard_overflow_threshold time_remaining_before_reconnection + # redispacthing + target_dispatch + actual_dispatch + """ def __init__(self, init_grid_path: str, @@ -208,6 +213,8 @@ def __init__(self, """ # TODO documentation!! + GridObjects.__init__(self) + # some timers self._time_apply_act = 0 self._time_powerflow = 0 @@ -248,6 +255,7 @@ def __init__(self, self.backend = backend self.backend.load_grid(self.init_grid_path) # the real powergrid of the environment self.backend.assert_grid_correct() + self.init_grid(backend) *_, tmp = self.backend.generators_info() # rules of the game @@ -326,6 +334,10 @@ def __init__(self, fill_value=0, dtype=np.int) self.env_dc = self.parameters.ENV_DC + # redispatching data + self.target_dispatch = np.full(shape=self.backend.n_gen, dtype=np.float, fill_value=np.NaN) + self.actual_dispatch = np.full(shape=self.backend.n_gen, dtype=np.float, fill_value=np.NaN) + # handles input data if not isinstance(chronics_handler, ChronicsHandler): raise Grid2OpException( diff --git a/grid2op/Exceptions.py b/grid2op/Exceptions.py index ded6605a6..63ea0a6d4 100644 --- a/grid2op/Exceptions.py +++ b/grid2op/Exceptions.py @@ -268,6 +268,14 @@ class InvalidRedispatching(AmbiguousAction): """ pass + +class InvalidRedispaching(AmbiguousAction): + """ + It tells that the current redispatching action is not valid. + """ + pass + + # powerflow exception class DivergingPowerFlow(Grid2OpException): """ @@ -322,3 +330,10 @@ class BackendError(Grid2OpException): Base class of all error regarding the Backend that might be badly configured. """ pass + +# attempt to use redispatching or unit commit method in an environment not set up. +class UnitCommitorRedispachingNotAvailable(Grid2OpException): + """ + attempt to use redispatching or unit commit method in an environment not set up. + """ + pass \ No newline at end of file diff --git a/grid2op/Observation.py b/grid2op/Observation.py index aa9d3cbda..72eae3731 100644 --- a/grid2op/Observation.py +++ b/grid2op/Observation.py @@ -398,6 +398,20 @@ class Observation(GridObjects): in all case, the powerline will stay disconnected until a :class:`grid2op.Agent.Agent` performs the proper :class:`grid2op.Action.Action` to reconnect it). + target_dispatch: :class:`numpy.ndarray`, dtype:float + For **each** generators, it gives the target redispatching, asked by the agent. This is the sum of all + redispatching asked by the agent for during all the episode. It for each generator it is a number between: + - pmax and pmax. Note that there is information about all generators there, even the one that are not + dispatchable. + + actual_dispatch: :class:`numpy.ndarray`, dtype:float + For **each** generators, it gives the redispatching currently implemented by the environment. + Indeed, the environment tries to implement at best the :attr:`Observation.target_dispatch`, but sometimes, + due to physical limitation (pmin, pmax, ramp min and ramp max) it cannot. In this case, only the best possible + redispatching is implemented at the current time step, and this is what this vector stores. Note that there is + information about all generators there, even the one that are not + dispatchable. + """ def __init__(self, gridobj, obs_env=None, @@ -466,6 +480,10 @@ def __init__(self, gridobj, self.bus_connectivity_matrix_ = None self.vectorized = None + # redispatching + self.target_dispatch = None + self.actual_dispatch = None + # value to assess if two observations are equal self._tol_equal = 5e-1 @@ -517,6 +535,9 @@ def state_of(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, subs - "v" the voltage magnitude of the bus to which the generator is connected - "bus" on which bus the generator is connected in the substation - "sub_id" the id of the substation to which the generator is connected + - "actual_dispatch" the actual dispatch implemented for this generator + - "target_dispatch" the target dispatch (cumulation of all previously asked dispatch by the agent) + for this generator - if a powerline is inspected then the keys are "origin" and "extremity" each being dictionnary with keys: @@ -579,7 +600,9 @@ def state_of(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, subs "q": self.prod_q[gen_id], "v": self.prod_v[gen_id], "bus": self.topo_vect[self.gen_pos_topo_vect[gen_id]], - "sub_id": self.gen_to_subid[gen_id] + "sub_id": self.gen_to_subid[gen_id], + "target_dispatch": self.target_dispatch[gen_id], + "actual_dispatch": self.target_dispatch[gen_id] } elif line_id is not None: if substation_id is not None: @@ -691,6 +714,10 @@ def reset(self): self._forecasted_inj = [] self._forecasted_grid = [] + # redispatching + self.target_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=np.NaN) + self.actual_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=np.NaN) + def __compare_stats(self, other, name): if self.__dict__[name] is None and other.__dict__[name] is not None: return False @@ -791,7 +818,8 @@ def __eq__(self, other): "time_before_cooldown_sub", "time_before_line_reconnectable", "time_next_maintenance", - "duration_next_maintenance" + "duration_next_maintenance", + "target_dispatch", "actual_dispatch" ]: if not self.__compare_stats(other, stat_nm): # one of the above stat is not equal in this and in other @@ -982,6 +1010,10 @@ class CompleteObservation(Observation): no maintenance are planned, 0 a maintenance is in operation) [:attr:`Observation.n_line` elements] 29. :attr:`Observation.duration_next_maintenance` duration of the next maintenance. If a maintenance is taking place, this is the number of timestep before it ends. [:attr:`Observation.n_line` elements] + 30. :attr:`Observation.target_dispatch` the target dispatch for each generator + [:attr:`Observation.n_gen` elements] + 31. :attr:`Observation.actual_dispatch` the actual dispatch for each generator + [:attr:`Observation.n_gen` elements] This behavior is specified in the :attr:`Observation.attr_list_vect` vector. @@ -1011,7 +1043,8 @@ def __init__(self, gridobj, "topo_vect", "time_before_cooldown_line", "time_before_cooldown_line", "time_before_cooldown_sub", "time_before_line_reconnectable", - "time_next_maintenance", "duration_next_maintenance" + "time_next_maintenance", "duration_next_maintenance", + "target_dispatch", "actual_dispatch" ] def _reset_matrices(self): @@ -1069,6 +1102,10 @@ def update(self, env): self.time_next_maintenance = env.time_next_maintenance self.duration_next_maintenance = env.duration_next_maintenance + # redispatching + self.target_dispatch = env.target_dispatch + self.actual_dispatch = env.actual_dispatch + def from_vect(self, vect): """ Convert back an observation represented as a vector into a proper observation. @@ -1128,6 +1165,9 @@ def to_dict(self): self.dictionnarized["cooldown"]['line'] = self.time_before_cooldown_line self.dictionnarized["cooldown"]['substation'] = self.time_before_cooldown_sub self.dictionnarized["time_before_line_reconnectable"] = self.time_before_line_reconnectable + self.dictionnarized["redispatching"] = {} + self.dictionnarized["redispatching"]["target_redispatch"] = self.target_dispatch + self.dictionnarized["redispatching"]["actual_dispatch"] = self.actual_dispatch return self.dictionnarized @@ -1159,7 +1199,9 @@ def connectivity_matrix(self): beg_ = 0 end_ = 0 for sub_id, nb_obj in enumerate(self.sub_info): - nb_obj = int(nb_obj) # i must be a vanilla python integer, otherwise it's not handled by boost python method to index substations for example. + # it must be a vanilla python integer, otherwise it's not handled by some backend + # especially if written in c++ + nb_obj = int(nb_obj) end_ += nb_obj tmp = np.zeros(shape=(nb_obj, nb_obj), dtype=np.float) for obj1 in range(nb_obj): @@ -1256,7 +1298,7 @@ def __init__(self, gridobj, observationClass=CompleteObservation): gridobj: :class:`grid2op.Space.GridObjects` Representation of the objects in the powergrid. - actionClass: ``type`` + observationClass: ``type`` Type of action used to build :attr:`Space.SerializableSpace.template_obj` """ diff --git a/grid2op/Space.py b/grid2op/Space.py index bd315250d..917c4231f 100644 --- a/grid2op/Space.py +++ b/grid2op/Space.py @@ -225,26 +225,74 @@ class GridObjects: called the first time. Otherwise, this attribute is set to ``None``. gen_type: :class:`numpy.ndarray`, dtype:str - Type of the generators, among: "solar", "wind", "hydro", "thermal" and "nuclear" + Type of the generators, among: "solar", "wind", "hydro", "thermal" and "nuclear". Optional. Used + for unit commitment problems or redispacthing action. gen_pmin: :class:`numpy.ndarray`, dtype:float - Minimum active power production needed for a generator to work properly. + Minimum active power production needed for a generator to work properly. Optional. Used + for unit commitment problems or redispacthing action. gen_pmax: :class:`numpy.ndarray`, dtype:float - Maximum active power production needed for a generator to work properly. + Maximum active power production needed for a generator to work properly. Optional. Used + for unit commitment problems or redispacthing action. gen_redispatchable: :class:`numpy.ndarray`, dtype:bool - For each generator, it says if the generator is dispatchable or not. + For each generator, it says if the generator is dispatchable or not. Optional. Used + for unit commitment problems or redispacthing action. gen_max_ramp_up: :class:`numpy.ndarray`, dtype:float Maximum active power variation possible between two consecutive timestep for each generator: a redispatching action - on generator `g_id` cannot be above :attr:`GridObjects.gen_ramp_up_max` [`g_id`] + on generator `g_id` cannot be above :attr:`GridObjects.gen_ramp_up_max` [`g_id`]. Optional. Used + for unit commitment problems or redispacthing action. gen_max_ramp_down: :class:`numpy.ndarray`, dtype:float Minimum active power variationpossible between two consecutive timestep for each generator: a redispatching action - on generator `g_id` cannot be below :attr:`GridObjects.gen_ramp_down_min` [`g_id`] + on generator `g_id` cannot be below :attr:`GridObjects.gen_ramp_down_min` [`g_id`]. Optional. Used + for unit commitment problems or redispacthing action. + + gen_min_uptime: :class:`numpy.ndarray`, dtype:float + Minimum time (expressed in number of timesteps) a generator need to be turned on: it's not possible to + turn off generator `gen_id` that has been turned on less than `gen_min_time_on` [`gen_id`] timesteps + ago. Optional. Used + for unit commitment problems or redispacthing action. + + gen_min_downtime: :class:`numpy.ndarray`, dtype:float + Minimum time (expressed in number of timesteps) a generator need to be turned off: it's not possible to + turn on generator `gen_id` that has been turned off less than `gen_min_time_on` [`gen_id`] timesteps + ago. Optional. Used + for unit commitment problems or redispacthing action. + + gen_cost_per_MW: :class:`numpy.ndarray`, dtype:float + For each generator, it gives the "operating cost", eg the cost, in term of "used currency" for the production of + one MW with this generator, if it is already turned on. It's a positive real number. It's the marginal cost + for each MW. Optional. Used + for unit commitment problems or redispacthing action. + + gen_startup_cost: :class:`numpy.ndarray`, dtype:float + The cost to start a generator. It's a positive real number. Optional. Used + for unit commitment problems or redispacthing action. + + gen_shutdown_cost: :class:`numpy.ndarray`, dtype:float + The cost to shut down a generator. It's a positive real number. Optional. Used + for unit commitment problems or redispacthing action. + + redispatching_unit_commitment_availble: ``bool`` + Does the current grid allow for redispatching and / or unit commit problem. If not, any attempt to use it + will raise a :class:`grid2op.Exceptions.UnitCommitorRedispachingNotAvailable` error. + For an environment to be compatible with this feature, you need to set up, when loading the backend: + - :attr:`GridObjects.gen_type` + - :attr:`GridObjects.gen_pmin` + - :attr:`GridObjects.gen_pmax` + - :attr:`GridObjects.gen_redispatchable` + - :attr:`GridObjects.gen_max_ramp_up` + - :attr:`GridObjects.gen_max_ramp_down` + - :attr:`GridObjects.gen_min_uptime` + - :attr:`GridObjects.gen_min_downtime` + - :attr:`GridObjects.gen_cost_per_MW` + - :attr:`GridObjects.gen_startup_cost` + - :attr:`GridObjects.gen_shutdown_cost` """ def __init__(self): @@ -284,14 +332,20 @@ def __init__(self): self.attr_list_vect = None self._vectorized = None - # for redispatching + # for redispatching / unit commitment TODO = "TODO COMPLETE THAT BELLOW!!! AND UPDATE THE init methods" - self.gen_type = TODO - self.gen_pmin = TODO - self.gen_pmax = TODO - self.gen_redispatchable = TODO - self.gen_max_ramp_up = TODO - self.gen_max_ramp_down = TODO + self.gen_type = None + self.gen_pmin = None + self.gen_pmax = None + self.gen_redispatchable = None + self.gen_max_ramp_up = None + self.gen_max_ramp_down = None + self.gen_min_uptime = None + self.gen_min_downtime = None + self.gen_cost_per_MW = None # marginal cost + self.gen_startup_cost = None # start cost + self.gen_shutdown_cost = None # shutdown cost + self.redispatching_unit_commitment_availble = False def _raise_error_attr_list_none(self): """ @@ -833,6 +887,119 @@ def assert_grid_correct(self): if np.any(self.sub_info < 1): raise BackendError("There are {} bus with 0 element connected to it.".format(np.sum(self.sub_info < 1))) + # redispatching / unit commitment + if self.redispatching_unit_commitment_availble: + if self.gen_type is None: + raise InvalidRedispatching("Impossible to recognize the type of generators (gen_type) when " + "redispatching is supposed to be available.") + if self.gen_pmin is None: + raise InvalidRedispatching("Impossible to recognize the pmin of generators (gen_pmin) when " + "redispatching is supposed to be available.") + if self.gen_pmax is None: + raise InvalidRedispatching("Impossible to recognize the pmax of generators (gen_pmax) when " + "redispatching is supposed to be available.") + if self.gen_redispatchable is None: + raise InvalidRedispatching("Impossible to know which generator can be dispatched (gen_redispatchable)" + " when redispatching is supposed to be available.") + if self.gen_max_ramp_up is None: + raise InvalidRedispatching("Impossible to recognize the ramp up of generators (gen_max_ramp_up)" + " when redispatching is supposed to be available.") + if self.gen_max_ramp_down is None: + raise InvalidRedispatching("Impossible to recognize the ramp up of generators (gen_max_ramp_down)" + " when redispatching is supposed to be available.") + if self.gen_min_uptime is None: + raise InvalidRedispatching("Impossible to recognize the min uptime of generators (gen_min_uptime)" + " when redispatching is supposed to be available.") + if self.gen_min_downtime is None: + raise InvalidRedispatching("Impossible to recognize the min downtime of generators (gen_min_downtime)" + " when redispatching is supposed to be available.") + if self.gen_cost_per_MW is None: + raise InvalidRedispatching("Impossible to recognize the marginal costs of generators (gen_cost_per_MW)" + " when redispatching is supposed to be available.") + if self.gen_startup_cost is None: + raise InvalidRedispatching("Impossible to recognize the start up cost of generators (gen_startup_cost)" + " when redispatching is supposed to be available.") + if self.gen_shutdown_cost is None: + raise InvalidRedispatching("Impossible to recognize the shut down cost of generators " + "(gen_shutdown_cost) when redispatching is supposed to be available.") + + if len(self.gen_type) != self.n_gen: + raise InvalidRedispatching("Invalid length for the type of generators (gen_type) when " + "redispatching is supposed to be available.") + if len(self.gen_pmin) != self.n_gen: + raise InvalidRedispatching("Invalid length for the pmin of generators (gen_pmin) when " + "redispatching is supposed to be available.") + if len(self.gen_pmax) != self.n_gen: + raise InvalidRedispatching("Invalid length for the pmax of generators (gen_pmax) when " + "redispatching is supposed to be available.") + if len(self.gen_redispatchable) != self.n_gen: + raise InvalidRedispatching("Invalid length for which generator can be dispatched (gen_redispatchable)" + " when redispatching is supposed to be available.") + if len(self.gen_max_ramp_up) != self.n_gen: + raise InvalidRedispatching("Invalid length for the ramp up of generators (gen_max_ramp_up)" + " when redispatching is supposed to be available.") + if len(self.gen_max_ramp_down) != self.n_gen: + raise InvalidRedispatching("Invalid length for the ramp up of generators (gen_max_ramp_down)" + " when redispatching is supposed to be available.") + if len(self.gen_min_uptime) != self.n_gen: + raise InvalidRedispatching("Invalid length for the min uptime of generators (gen_min_uptime)" + " when redispatching is supposed to be available.") + if len(self.gen_min_downtime) != self.n_gen: + raise InvalidRedispatching("Invalid length for the min downtime of generators (gen_min_downtime)" + " when redispatching is supposed to be available.") + if len(self.gen_cost_per_MW) != self.n_gen: + raise InvalidRedispatching("Invalid length for the marginal costs of generators (gen_cost_per_MW)" + " when redispatching is supposed to be available.") + if len(self.gen_startup_cost) != self.n_gen: + raise InvalidRedispatching("Invalid length for the start up cost of generators (gen_startup_cost)" + " when redispatching is supposed to be available.") + if len(self.gen_shutdown_cost) != self.n_gen: + raise InvalidRedispatching("Invalid length for the shut down cost of generators " + "(gen_shutdown_cost) when redispatching is supposed to be available.") + + if np.any(self.gen_min_uptime < 0): + raise InvalidRedispatching("Minimum uptime of generator (gen_min_uptime) cannot be negative") + if np.any(self.gen_min_downtime < 0): + raise InvalidRedispatching("Minimum downtime of generator (gen_min_downtime) cannot be negative") + + for el in self.gen_type: + if not el in ["solar", "wind", "hydro", "thermal", "nuclear"]: + raise InvalidRedispatching("Unknown generator type : {}".format(el)) + + if np.any(self.gen_pmin < 0.): + raise InvalidRedispatching("One of the Pmin (gen_pmin) is negative") + if np.any(self.gen_pmax < 0.): + raise InvalidRedispatching("One of the Pmax (gen_pmax) is negative") + if np.any(self.gen_max_ramp_down < 0.): + raise InvalidRedispatching("One of the ramp up (gen_max_ramp_down) is negative") + if np.any(self.gen_max_ramp_up < 0.): + raise InvalidRedispatching("One of the ramp down (gen_max_ramp_up) is negative") + if np.any(self.gen_startup_cost < 0.): + raise InvalidRedispatching("One of the start up cost (gen_startup_cost) is negative") + if np.any(self.gen_shutdown_cost < 0.): + raise InvalidRedispatching("One of the start up cost (gen_shutdown_cost) is negative") + + for el, type_ in zip(["gen_type", "gen_pmin", "gen_pmax", "gen_redispatchable", "gen_max_ramp_up", + "gen_max_ramp_down", "gen_min_uptime", "gen_min_downtime", "gen_cost_per_MW", + "gen_startup_cost", "gen_shutdown_cost"], + [str, np.float, np.float, np.bool, np.float, + np.float, np.int, np.int, np.float, + np.float, np.float]): + if not isinstance(self.__dict__[el], np.ndarray): + try: + self.__dict__[el] = np.array(self.__dict__[el]) + except Exception as e: + raise InvalidRedispatching("{} should be convertible to a numpy array".format(el)) + if not np.issubdtype(self.__dict__[el].dtype, np.dtype(type_).type): + try: + self.__dict__[el] = self.__dict__[el].astype(type_) + except Exception as e: + raise InvalidRedispatching("{} should be convertible data should be convertible to " + "{}".format(el, type_)) + + if np.any(self.gen_max_ramp_up[self.gen_redispatchable] > self.gen_pmax[self.gen_redispatchable]): + raise InvalidRedispatching("Invalid maximum ramp for some generator (above pmax)") + def init_grid(self, gridobj): """ Initialize this :class:`GridObjects` instance with a provided instance. @@ -876,13 +1043,19 @@ def init_grid(self, gridobj): self.line_or_pos_topo_vect = gridobj.line_or_pos_topo_vect self.line_ex_pos_topo_vect = gridobj.line_ex_pos_topo_vect - # for redispatching + # for redispatching / unit commitment self.gen_type = gridobj.gen_type self.gen_pmin = gridobj.gen_pmin self.gen_pmax = gridobj.gen_pmax self.gen_redispatchable = gridobj.gen_redispatchable self.gen_max_ramp_up = gridobj.gen_max_ramp_up self.gen_max_ramp_down = gridobj.gen_max_ramp_down + self.gen_min_uptime = gridobj.gen_min_uptime + self.gen_min_downtime = gridobj.gen_min_downtime + self.gen_cost_per_MW = gridobj.gen_cost_per_MW + self.gen_startup_cost = gridobj.gen_startup_cost + self.gen_shutdown_cost = gridobj.gen_shutdown_cost + self.redispatching_unit_commitment_availble = gridobj.redispatching_unit_commitment_availble def get_obj_connect_to(self, _sentinel=None, substation_id=None): """ diff --git a/grid2op/tests/test_Observation.py b/grid2op/tests/test_Observation.py index 05fe33bf8..3b74d54ff 100644 --- a/grid2op/tests/test_Observation.py +++ b/grid2op/tests/test_Observation.py @@ -103,19 +103,21 @@ def setUp(self): '3_8_16', '4_5_17', '6_7_18', '6_8_19'], 'name_sub': ['sub_0', 'sub_1', 'sub_10', 'sub_11', 'sub_12', 'sub_13', 'sub_2', 'sub_3', 'sub_4', 'sub_5', 'sub_6', 'sub_7', 'sub_8', 'sub_9'], - 'sub_info': [3, 6, 4, 6, 5, 6, 3, 2, 5, 3, 3, 3, 4, 3], - 'load_to_subid': [1, 2, 13, 3, 4, 5, 8, 9, 10, 11, 12], - 'gen_to_subid': [1, 2, 5, 7, 0], - 'line_or_to_subid': [0, 0, 8, 8, 9, 11, 12, 1, 1, 1, 2, 3, 5, 5, 5, 3, 3, 4, 6, 6], - 'line_ex_to_subid': [1, 4, 9, 13, 10, 12, 13, 2, 3, 4, 3, 4, 10, 11, 12, 6, 8, 5, 7, 8], - 'load_to_sub_pos': [5, 3, 2, 5, 4, 5, 4, 2, 2, 2, 3], 'gen_to_sub_pos': [4, 2, 4, 1, 2], - 'line_or_to_sub_pos': [0, 1, 0, 1, 1, 0, 1, 1, 2, 3, 1, 2, 0, 1, 2, 3, 4, 3, 1, 2], - 'line_ex_to_sub_pos': [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 2, 1, 1, 2, 0, 2, 3, 0, 3], - 'load_pos_topo_vect': [8, 12, 55, 18, 23, 29, 39, 42, 45, 48, 52], - 'gen_pos_topo_vect': [7, 11, 28, 34, 2], - 'line_or_pos_topo_vect': [0, 1, 35, 36, 41, 46, 50, 4, 5, 6, 10, 15, 24, 25, 26, 16, 17, 22, 31, 32], - 'line_ex_pos_topo_vect': [3, 19, 40, 53, 43, 49, 54, 9, 13, 20, 14, 21, 44, 47, 51, 30, 37, 27, 33, 38], - 'subtype': 'Observation.CompleteObservation'} + 'sub_info': [3, 6, 4, 6, 5, 6, 3, 2, 5, 3, 3, 3, 4, 3], + 'load_to_subid': [1, 2, 13, 3, 4, 5, 8, 9, 10, 11, 12], + 'gen_to_subid': [1, 2, 5, 7, 0], + 'line_or_to_subid': [0, 0, 8, 8, 9, 11, 12, 1, 1, 1, 2, 3, 5, 5, 5, 3, 3, 4, 6, 6], + 'line_ex_to_subid': [1, 4, 9, 13, 10, 12, 13, 2, 3, 4, 3, 4, 10, 11, 12, 6, 8, 5, 7, 8], + 'load_to_sub_pos': [5, 3, 2, 5, 4, 5, 4, 2, 2, 2, 3], 'gen_to_sub_pos': [4, 2, 4, 1, 2], + 'line_or_to_sub_pos': [0, 1, 0, 1, 1, 0, 1, 1, 2, 3, 1, 2, 0, 1, 2, 3, 4, 3, 1, 2], + 'line_ex_to_sub_pos': [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 2, 1, 1, 2, 0, 2, 3, 0, 3], + 'load_pos_topo_vect': [8, 12, 55, 18, 23, 29, 39, 42, 45, 48, 52], + 'gen_pos_topo_vect': [7, 11, 28, 34, 2], + 'line_or_pos_topo_vect': [0, 1, 35, 36, 41, 46, 50, 4, 5, 6, 10, 15, 24, 25, 26, 16, 17, 22, 31, + 32], + 'line_ex_pos_topo_vect': [3, 19, 40, 53, 43, 49, 54, 9, 13, 20, 14, 21, 44, 47, 51, 30, 37, 27, + 33, 38], + 'subtype': 'Observation.CompleteObservation'} self.dtypes = np.array([dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), @@ -127,11 +129,23 @@ def setUp(self): dtype('int64'), dtype('int64')], dtype=object) self.shapes = np.array([ 1, 1, 1, 1, 1, 1, 5, 5, 5, 11, 11, 11, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 56, 20, 20, 14, 20, 20, 20]) + self.size_obs = 4242 # TODO def test_sum_shape_equal_size(self): obs = self.env.helper_observation(self.env) assert obs.size() == np.sum(obs.shape()) + def test_size(self): + action = self.env.helper_observation(self.env) + action.size() + + def test_proper_size(self): + action = self.env.helper_observation(self.env) + assert action.size() == self.size_obs + + def test_size_action_space(self): + assert self.env.helper_observation.size() == self.size_obs + def test_bus_conn_mat(self): obs = self.env.helper_observation(self.env) mat1 = obs.bus_connectivity_matrix() From 826dbb0b0f62615e20df346ca4308a5814af388f Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Wed, 15 Jan 2020 20:04:17 +0100 Subject: [PATCH 04/14] start to implement automaton in the environment for the redispatching --- grid2op/Action.py | 31 ++- grid2op/Backend.py | 16 +- grid2op/Environment.py | 344 ++++++++++++++++++++++++++-- grid2op/tests/test_Environment.py | 15 +- grid2op/tests/test_RedispatchEnv.py | 113 +++++++++ 5 files changed, 474 insertions(+), 45 deletions(-) create mode 100644 grid2op/tests/test_RedispatchEnv.py diff --git a/grid2op/Action.py b/grid2op/Action.py index 8ff705e39..7e31440ff 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -120,6 +120,9 @@ class Action(GridObjects): connected to bus 1. NB this is only active if the system has only 2 buses per substation (that's the case for the L2RPN challenge). + - the sixth elements is a vector, representing the redispatching. Component of this vector are added to the + generators active setpoint value (if set) of the first elements. + **NB** the difference between :attr:`Action._set_topo_vect` and :attr:`Action._change_bus_vect` is the following: - If a component of :attr:`Action._set_topo_vect` is 1, then the object (load, generator or powerline) @@ -865,6 +868,8 @@ def update(self, dict_): change it (eg switch it from bus 1 to bus 2 or from bus 2 to bus 1). NB this is only active if the system has only 2 buses per substation. + - "redispatch" TODO + **NB** the difference between "set_bus" and "change_bus" is the following: - If "set_bus" is 1, then the object (load, generator or powerline) will be moved to bus 1 of the @@ -949,6 +954,18 @@ def _check_for_ambiguity(self): - :code:`self._set_topo_vect` has not the same dimension as the number of elements on the powergrid - :code:`self._change_bus_vect` has not the same dimension as the number of elements on the powergrid + - For redispatching, Ambiguous actions can come from: + + - Some redispatching action is active, yet + :attr:`grid2op.Space.GridObjects.redispatching_unit_commitment_availble` is set to ``False`` + - the length of the redispatching vector :attr:`Action._redispatching` is not compatible with the number + of generators. + - some redispatching are above the maximum ramp up :attr:`grid2op.Space.GridObjects.gen_max_ramp_up` + - some redispatching are below the maximum ramp down :attr:`grid2op.Space.GridObjects.gen_max_ramp_down` + - the redispatching action affect non dispatchable generators + - the redispatching and the production setpoint, if added, are above pmax for at least a generator + - the redispatching and the production setpoint, if added, are below pmin for at least a generator + In case of need to overload this method, it is advise to still call this one from the base :class:`Action` with ":code:`super()._check_for_ambiguity()`" or ":code:`Action._check_for_ambiguity(self)`". @@ -1000,7 +1017,7 @@ def _check_for_ambiguity(self): "there are {} in the grid".format(len(self._redispatch), self.n_gen)) # redispatching specific check - if np.any(self._redispatch !=0.): + if np.any(self._redispatch != 0.): if not self.redispatching_unit_commitment_availble: raise UnitCommitorRedispachingNotAvailable("Impossible to use a redispatching action in this " "environment. Please set up the proper costs for generator") @@ -1016,10 +1033,11 @@ def _check_for_ambiguity(self): if "prod_p" in self._dict_inj: new_p = self._dict_inj["prod_p"] tmp_p = new_p + self._redispatch - if np.any(tmp_p > self.gen_pmax): + indx_ok = np.isfinite(new_p) + if np.any(tmp_p[indx_ok] > self.gen_pmax[indx_ok]): raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " "are above pmax for some generator.") - if np.any(tmp_p < self.gen_pmin): + if np.any(tmp_p[indx_ok] < self.gen_pmin[indx_ok]): raise InvalidRedispatching("Some redispatching amount, cumulated with the production setpoint, " "are below pmin for some generator.") @@ -1459,7 +1477,7 @@ def __init__(self, gridobj): # the injection keys is not authorized, meaning it will send a warning is someone try to implement some # modification injection. - self.authorized_keys = set([k for k in self.authorized_keys if k != "injection"]) + self.authorized_keys = set([k for k in self.authorized_keys if k != "injection" and k != "redispatch"]) self.attr_list_vect = ["_set_line_status", "_switch_line_status", "_set_topo_vect", "_change_bus_vect"] @@ -1489,7 +1507,7 @@ def __call__(self): if self._dict_inj: raise AmbiguousAction("You asked to modify the injection with an action of class \"TopologyAction\".") self._check_for_ambiguity() - return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect + return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect, self._redispatch def update(self, dict_): """ @@ -1510,7 +1528,6 @@ def update(self, dict_): self: :class:`TopologyAction` Return object itself thus allowing mutiple call to "update" to be chained. """ - self.as_vect = None if dict_ is not None: for kk in dict_.keys(): @@ -1593,7 +1610,7 @@ def __call__(self): if self._dict_inj: raise AmbiguousAction("You asked to modify the injection with an action of class \"TopologyAction\".") self._check_for_ambiguity() - return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect + return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect, self._redispatch def update(self, dict_): """ diff --git a/grid2op/Backend.py b/grid2op/Backend.py index 3733e3cb8..ba1146816 100644 --- a/grid2op/Backend.py +++ b/grid2op/Backend.py @@ -723,7 +723,6 @@ def load_redispacthing_data(self, path, name='prods_charac.csv'): ------- """ - # for redispatching fullpath = os.path.join(path, name) if not os.path.exists(fullpath): @@ -739,15 +738,14 @@ def load_redispacthing_data(self, path, name='prods_charac.csv'): "max_ramp_up": row["max_ramp_up"], "max_ramp_down": row["max_ramp_down"], "start_cost": row["start_cost"], - "shutdown_cost": row["shutdown_cost"], + "shut_down_cost": row["shut_down_cost"], "marginal_cost": row["marginal_cost"], "min_up_time": row["min_up_time"], "min_down_time": row["min_down_time"] } - self.redispatching_unit_commitment_availble = True - self.gen_type = np.full(self.n_gen, fill_value="") + self.gen_type = np.full(self.n_gen, fill_value="aaaaaaaaaa") self.gen_pmin = np.full(self.n_gen, fill_value=1., dtype=np.float) self.gen_pmax = np.full(self.n_gen, fill_value=1., dtype=np.float) self.gen_redispatchable = np.full(self.n_gen, fill_value=False, dtype=np.bool) @@ -760,17 +758,17 @@ def load_redispacthing_data(self, path, name='prods_charac.csv'): self.gen_shutdown_cost = np.full(self.n_gen, fill_value=1., dtype=np.float) # shutdown cost for i, gen_nm in enumerate(self.name_gen): - tmp_gen = gen_info["gen_nm"] + tmp_gen = gen_info[gen_nm] self.gen_type[i] = str(tmp_gen["type"]) self.gen_pmin[i] = float(tmp_gen["pmin"]) self.gen_pmax[i] = float(tmp_gen["pmax"]) self.gen_redispatchable[i] = bool(tmp_gen["type"] not in ["wind", "solar"]) self.gen_max_ramp_up[i] = float(tmp_gen["max_ramp_up"]) self.gen_max_ramp_down[i] = float(tmp_gen["max_ramp_down"]) - self.gen_min_uptime[i] = int(tmp_gen["min uptime"]) - self.gen_min_downtime[i] = int(tmp_gen["min downtime"]) + self.gen_min_uptime[i] = int(tmp_gen["min_up_time"]) + self.gen_min_downtime[i] = int(tmp_gen["min_down_time"]) self.gen_cost_per_MW[i] = float(tmp_gen["marginal_cost"]) - self.gen_startup_cost[i] = float(tmp_gen["start_cost cost"]) - self.gen_shutdown_cost[i] = float(tmp_gen["shutdown_cost"]) + self.gen_startup_cost[i] = float(tmp_gen["start_cost"]) + self.gen_shutdown_cost[i] = float(tmp_gen["shut_down_cost"]) diff --git a/grid2op/Environment.py b/grid2op/Environment.py index bd534e9b2..888ed54c7 100644 --- a/grid2op/Environment.py +++ b/grid2op/Environment.py @@ -55,6 +55,7 @@ from .Backend import Backend from .ChronicsHandler import ChronicsHandler except (ModuleNotFoundError, ImportError): + from Space import GridObjects from Action import HelperAction, Action, TopologyAction from Exceptions import * from Observation import CompleteObservation, ObservationHelper, Observation @@ -183,6 +184,9 @@ class Environment(GridObjects): target_dispatch actual_dispatch + gen_activeprod_t: + Should be initialized at 0. for "step" to properly recognize it's the first time step of the game + """ def __init__(self, init_grid_path: str, @@ -193,7 +197,8 @@ def __init__(self, actionClass=TopologyAction, observationClass=CompleteObservation, rewardClass=FlatReward, - legalActClass=AllwaysLegal): + legalActClass=AllwaysLegal, + epsilon_poly=1e-2): """ Initialize the environment. See the descirption of :class:`grid2op.Environment.Environment` for more information. @@ -219,6 +224,7 @@ def __init__(self, self._time_apply_act = 0 self._time_powerflow = 0 self._time_extract_obs = 0 + self._epsilon_poly = epsilon_poly # define logger self.logger = None @@ -254,6 +260,7 @@ def __init__(self, type(backend))) self.backend = backend self.backend.load_grid(self.init_grid_path) # the real powergrid of the environment + self.backend.load_redispacthing_data(os.path.split(self.init_grid_path)[0]) self.backend.assert_grid_correct() self.init_grid(backend) *_, tmp = self.backend.generators_info() @@ -311,32 +318,34 @@ def __init__(self, # type of power flow to play # if True, then it will not disconnect lines above their thermal limits self.no_overflow_disconnection = self.parameters.NO_OVERFLOW_DISCONNECTION - self.timestep_overflow = np.zeros(shape=(self.backend.n_line,), dtype=np.int) - self.nb_timestep_overflow_allowed = np.full(shape=(self.backend.n_line,), + self.timestep_overflow = np.zeros(shape=(self.n_line,), dtype=np.int) + self.nb_timestep_overflow_allowed = np.full(shape=(self.n_line,), fill_value=self.parameters.NB_TIMESTEP_POWERFLOW_ALLOWED) # store actions "cooldown" - self.times_before_line_status_actionable = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self.times_before_line_status_actionable = np.zeros(shape=(self.n_line,), dtype=np.int) self.max_timestep_line_status_deactivated = self.parameters.NB_TIMESTEP_LINE_STATUS_REMODIF - self.times_before_topology_actionable = np.zeros(shape=(self.backend.n_sub,), dtype=np.int) + self.times_before_topology_actionable = np.zeros(shape=(self.n_sub,), dtype=np.int) self.max_timestep_topology_deactivated = self.parameters.NB_TIMESTEP_TOPOLOGY_REMODIF # for maintenance operation - self.time_next_maintenance = np.zeros(shape=(self.backend.n_line,), dtype=np.int) - 1 - self.duration_next_maintenance = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self.time_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) - 1 + self.duration_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) # hazard (not used outside of this class, information is given in `time_remaining_before_line_reconnection` - self._hazard_duration = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self._hazard_duration = np.zeros(shape=(self.n_line,), dtype=np.int) # hard overflow part self.hard_overflow_threshold = self.parameters.HARD_OVERFLOW_THRESHOLD - self.time_remaining_before_line_reconnection = np.full(shape=(self.backend.n_line,), - fill_value=0, dtype=np.int) + self.time_remaining_before_line_reconnection = np.full(shape=(self.n_line,), fill_value=0, dtype=np.int) self.env_dc = self.parameters.ENV_DC # redispatching data - self.target_dispatch = np.full(shape=self.backend.n_gen, dtype=np.float, fill_value=np.NaN) - self.actual_dispatch = np.full(shape=self.backend.n_gen, dtype=np.float, fill_value=np.NaN) + self.target_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.actual_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.gen_uptime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_downtime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_activeprod_t = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) # handles input data if not isinstance(chronics_handler, ChronicsHandler): @@ -345,8 +354,8 @@ def __init__(self, "grid2op.ChronicsHandler class, type provided is \"{}\"".format( type(chronics_handler))) self.chronics_handler = chronics_handler - self.chronics_handler.initialize(self.backend.name_load, self.backend.name_gen, - self.backend.name_line, self.backend.name_sub, + self.chronics_handler.initialize(self.name_load, self.name_gen, + self.name_line, self.name_sub, names_chronics_to_backend=names_chronics_to_backend) self.names_chronics_to_backend = names_chronics_to_backend @@ -433,6 +442,7 @@ def reset_grid(self): do_nothing = self.helper_action_env({}) self.step(do_nothing) + # test the backend returns object of the proper size self.backend.assert_grid_correct_after_powerflow() @@ -549,6 +559,216 @@ def _update_time_reconnection_hazards_maintenance(self): self.time_remaining_before_line_reconnection = np.maximum(self.time_remaining_before_line_reconnection, self._hazard_duration) + def _simulate_automaton_redispatching(self, action): + # Redispatching process the redispatching actions here, get a redispatching vector with 0-sum + # from the environment. + + is_illegal = False + + # get the redispatching action (if any) + redisp_act_init = 1. * action._redispatch + redisp_act = 1. * action._redispatch + + if np.all(redisp_act == 0.): + return redisp_act, is_illegal, action + + # make sure the dispatching action is not implemented "as is" by the backend. + # the environment must make sure it's a zero-sum action. + action._redispatch = np.full(shape=self.n_gen, fill_value=0., dtype=np.float) + + # check that everything is consistent with pmin, pmax: + if np.any(self.target_dispatch > self.gen_pmax): + # action is invalid, the target redispatching would be above pmax for at least a generator + is_illegal = True + action = self.helper_action_player({}) + # TODO flag to output why an action is illegal here + return action._redispatch, is_illegal, action + + if np.any(self.target_dispatch < self.gen_pmin): + # action is invalid, the target redispatching would be below pmin for at least a generator + is_illegal = True + action = self.helper_action_player({}) + # TODO flag to output why an action is illegal here + return action._redispatch, is_illegal, action + + # get the modification of generator active setpoint from the action + new_p = 1. * self.gen_activeprod_t + if "prod_p" in action._dict_inj: + tmp = action._dict_inj["prod_p"] + indx_ok = np.isfinite(tmp) + new_p[indx_ok] = tmp[indx_ok] + + # i can't redispatch turned off generators [turned off generators need to be turned on before redispatching] + if np.any(redisp_act[new_p == 0.]): + # action is invalid, a generator has been redispatched, but it's turned off + is_illegal = True + action = self.helper_action_player({}) + # TODO flag to output why an action is illegal here + return action._redispatch, is_illegal, action + + # modification of the environment always override the modification of the agents (if any) + # TODO have a flag there if this is the case. + if "prod_p" in self.env_modification._dict_inj: + # modification of the production setpoint value + tmp = self.env_modification._dict_inj["prod_p"] + indx_ok = np.isfinite(tmp) + new_p[indx_ok] = tmp[indx_ok] + + # action is "capped", a generator has been redispatched, but it's turned off by the environment + redisp_act[new_p == 0.] = 0. + # TODO add a flag here too, like before (the action has been "cut") + + # make the target dispatch a 0-sum vector (using only dispatchable unit, not dispatched) + # dispatch only the generator that are at zero + # avail_gen = redisp_act == 0. # generators with a redispatching target cannot be redispatched again + avail_gen = self.gen_redispatchable # i can only redispatched dispatchable generators + avail_gen = avail_gen & (new_p > 0.) + + # another need to cut: it would result in above pmin or bellow pmax, in this case (but in the case it's valid) + # we choose to cut down the redispatching action. + curtail_generation = 1. * new_p + mask_min = (new_p + redisp_act < self.gen_pmin + self._epsilon_poly) & avail_gen + curtail_generation[mask_min] = self.gen_pmin[mask_min] + self._epsilon_poly + mask_max = (new_p + redisp_act > self.gen_pmax - self._epsilon_poly) & avail_gen + curtail_generation[mask_max] = self.gen_pmax[mask_max] - self._epsilon_poly + + diff_th_imp = new_p - curtail_generation + redisp_act[mask_min] += diff_th_imp[mask_min] + redisp_act[mask_max] += diff_th_imp[mask_max] + + # get the maximum difference possible + # to do change the name !!! + gen_min = np.maximum(curtail_generation - self.gen_pmin, - self.gen_max_ramp_down) + gen_max = np.minimum(curtail_generation - self.gen_pmax, self.gen_max_ramp_up) + + if not np.sum(avail_gen): + # TODO reason for that in info + is_illegal = True + action = self.helper_action_player({}) + return action._redispatch, is_illegal, action + + try: + t_zerosum = self._get_t(redisp_act[avail_gen], + pmin=np.sum(gen_min[avail_gen]), + pmax=np.sum(gen_max[avail_gen]), + total_dispatch=-np.sum(redisp_act)) + except: + # i can't implement redispatching due to impossibility to dispatch on the other generator + # it's a non valid action + return action._redispatch, is_illegal, action + + # get the target redispatching (cumulation starting from the first element of the scenario) + self.target_dispatch += redisp_act + self.actual_dispatch = 1. * self.target_dispatch + + actual_dispatch_tmp = self._get_poly(t=t_zerosum, + pmax=gen_max[avail_gen], + pmin=gen_min[avail_gen], + tmp_p=redisp_act[avail_gen]) + self.actual_dispatch[avail_gen] = actual_dispatch_tmp + pdb.set_trace() + return redisp_act_init, is_illegal, action + + ## TODO check that this part is not needed anymore!!!! + # get the maximum ranged of the generator possible with the chronics + # gen_min = np.maximum(self.gen_pmin, self.gen_activeprod_t - self.gen_max_ramp_down) + # gen_max = np.minimum(self.gen_pmax, self.gen_activeprod_t + self.gen_max_ramp_up) + + # below is the part emulating the active balancing part in a powergrid. It's not handled by operators + # but by an automaton. + + # this automated conrol only affect turned-on generators that are dispatchable + turned_on_gen = new_p > 0. + gen_redispatchable = self.gen_redispatchable & turned_on_gen + + # add the redispatching to the nex target production value + new_p += self.actual_dispatch + # the new_p vector now stores the new target value for the generators. + + # todo this might be the root of all evil!!!! + gen_min = np.maximum(self.gen_pmin - self.gen_activeprod_t, - self.gen_max_ramp_down) + gen_max = np.minimum(self.gen_pmax - self.gen_activeprod_t, self.gen_max_ramp_up) + + actual_ramp = new_p - self.gen_activeprod_t + if (np.any(new_p > self.gen_pmax) or np.any(new_p < self.gen_pmin) or \ + np.any(actual_ramp > self.gen_max_ramp_up) or np.any(actual_ramp < -self.gen_max_ramp_down)) and \ + np.any(self.gen_activeprod_t != 0.): + # i am in a case where the target redispatching is not possible, due to the new values + # i need to come up with a solution to fix that + # note that the condition "np.any(self.gen_activeprod_t != 0.)" is added because at the first time + # step there is no need to check all that. + # but take into account pmin and pmax + curtail_generation = 1. * new_p + mask_min = (new_p < gen_min + self._epsilon_poly) & gen_redispatchable + curtail_generation[mask_min] = gen_min[mask_min] + self._epsilon_poly + mask_max = (new_p > gen_max - self._epsilon_poly) & gen_redispatchable + curtail_generation[mask_max] = gen_max[mask_max] - self._epsilon_poly + + # modify the implemented redispatching to take into account this "curtailement" due to physical limitation + diff_th_imp = new_p - curtail_generation + self.actual_dispatch[mask_min] += diff_th_imp[mask_min] + self.actual_dispatch[mask_max] -= diff_th_imp[mask_max] + + # make sure the new redispatching sum to 0 + t_dispatch = 0. + try: + t_dispatch = self._get_t(self.actual_dispatch[gen_redispatchable], + pmin=np.sum(gen_min), + pmax=np.sum(gen_max), + total_dispatch=-np.sum(self.actual_dispatch[gen_redispatchable])) + except Exception as e: + # TODO check that it canont happen if the chronics are correct. + print("epic fail in redispatching with error {}".format(e)) + pass + + self.actual_dispatch[gen_redispatchable] = self._get_poly(t=t_dispatch, + pmax=gen_max[gen_redispatchable], + pmin=gen_min[gen_redispatchable], + tmp_p=self.actual_dispatch[gen_redispatchable]) + + return redisp_act_init, is_illegal, action + + def _handle_updown_times(self, action, gen_up_before, redisp_act): + # get the generators that are not connected after the action + # TODO difference between "environment action" and "player's action" + is_illegal = False + + # computes which generator will be turned on after the action + gen_up_after = self.gen_activeprod_t + if "prod_p" in self.env_modification._dict_inj: + tmp = self.env_modification._dict_inj["prod_p"] + indx_ok = np.isfinite(tmp) + gen_up_after[indx_ok] = self.env_modification._dict_inj["prod_p"][indx_ok] + gen_up_after += redisp_act + gen_up_after = gen_up_after > 0. + + # update min down time, min up time etc. + gen_disconnected_this = gen_up_before & (~gen_up_after) + gen_connected_this_timestep = (~gen_up_before) & (gen_up_after) + gen_still_connected = gen_up_before & gen_up_after + gen_still_disconnected = (~gen_up_before) & (~gen_up_after) + + if np.any(self.gen_downtime[gen_connected_this_timestep] < self.gen_min_downtime[gen_connected_this_timestep]): + # i reconnected a generator before the minimum time allowed + # TODO flag here, same as before + is_illegal = True + action = self.helper_action_player({}) + return is_illegal, action + else: + self.gen_downtime[gen_connected_this_timestep] = -1 + self.gen_uptime[gen_connected_this_timestep] = 1 + if np.any(self.gen_uptime[gen_disconnected_this] < self.gen_min_uptime[gen_disconnected_this]): + # TODO flag here, same as before + is_illegal = True + action = self.helper_action_player({}) + return is_illegal, action + else: + self.gen_downtime[gen_connected_this_timestep] = -1 + self.gen_uptime[gen_connected_this_timestep] = 1 + self.gen_uptime[gen_still_connected] += 1 + self.gen_downtime[gen_still_disconnected] += 1 + return is_illegal, action + def step(self, action): """ Run one timestep of the environment's dynamics. When end of @@ -590,18 +810,27 @@ def step(self, action): disc_lines = None is_illegal = False is_ambiguous = False + is_illegal_redisp = False + is_illegal_reco = False try: beg_ = time.time() is_illegal = not self.game_rules(action=action, env=self) if is_illegal: # action is replace by do nothing action = self.helper_action_player({}) + # todo a flag here - # TODO redispatching: process the redispatching actions here, get a redispatching vector with 0-sum - # from the environment. - # and then remove the redispatching part of the action from the player one, and add it to the environment's + # get the modification of generator active setpoint from the environment self.env_modification = self._update_actions() - # TODO check now that the action is compatible with the new modification + + # remember generator that were "up" before the action + gen_up_before = self.gen_activeprod_t > 0. + + # compute the redispatching and the new productions active setpoint + redisp_act, is_illegal_redisp, action = self._simulate_automaton_redispatching(action) + + # check the validity of min downtime and max uptime + is_illegal_reco, action = self._handle_updown_times(action, gen_up_before, redisp_act) try: self.backend.apply_action(action) @@ -609,7 +838,12 @@ def step(self, action): # action has not been implemented on the powergrid because it's ambiguous, it's equivalent to # "do nothing" is_ambiguous = True + action._redispatch = 1. * redisp_act + # now get the new generator voltage setpoint + # TODO "automaton" to do that! + + self.env_modification._redispatch = self.actual_dispatch self.backend.apply_action(self.env_modification) self._time_apply_act += time.time() - beg_ @@ -651,6 +885,9 @@ def step(self, action): self.current_obs = self.get_obs() self._time_extract_obs += time.time() - beg_ + # extract production active value at this time step (should be independant of action class) + self.gen_activeprod_t, *_ = self.backend.generators_info() + has_error = False except Grid2OpException as e: if self.logger is not None: @@ -658,8 +895,18 @@ def step(self, action): except StopIteration: # episode is over is_done = True - infos = {"disc_lines": disc_lines, "is_illegal": is_illegal, "is_ambiguous": is_ambiguous} - return self.current_obs, self._get_reward(action, has_error, is_done, is_illegal, is_ambiguous),\ + infos = {"disc_lines": disc_lines, + "is_illegal": is_illegal, + "is_ambiguous": is_ambiguous, + "is_dipatching_illegal": is_illegal_redisp, + "is_illegal_reco": is_illegal_reco} + # TODO documentation on all the possible way to be illegal now + return self.current_obs,\ + self._get_reward(action, + has_error, + is_done, + is_illegal or is_illegal_redisp or is_illegal_reco, + is_ambiguous),\ self._is_done(has_error, is_done),\ infos @@ -672,23 +919,40 @@ def _reset_vectors_and_timings(self): """ self.no_overflow_disconnection = self.parameters.NO_OVERFLOW_DISCONNECTION - self.timestep_overflow = np.zeros(shape=(self.backend.n_line,), dtype=np.int) - self.nb_timestep_overflow_allowed = np.full(shape=(self.backend.n_line,), + self.timestep_overflow = np.zeros(shape=(self.n_line,), dtype=np.int) + self.nb_timestep_overflow_allowed = np.full(shape=(self.n_line,), fill_value=self.parameters.NB_TIMESTEP_POWERFLOW_ALLOWED) self.nb_time_step = 0 self.hard_overflow_threshold = self.parameters.HARD_OVERFLOW_THRESHOLD self.env_dc = self.parameters.ENV_DC - self.times_before_line_status_actionable = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self.times_before_line_status_actionable = np.zeros(shape=(self.n_line,), dtype=np.int) self.max_timestep_line_status_deactivated = self.parameters.NB_TIMESTEP_LINE_STATUS_REMODIF - self.times_before_topology_actionable = np.zeros(shape=(self.backend.n_sub,), dtype=np.int) + self.times_before_topology_actionable = np.zeros(shape=(self.n_sub,), dtype=np.int) self.max_timestep_topology_deactivated = self.parameters.NB_TIMESTEP_TOPOLOGY_REMODIF + self.time_remaining_before_line_reconnection = np.zeros(shape=(self.n_line,), dtype=np.int) + + # # maintenance and hazards + # self.time_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) - 1 + # self.duration_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) + # self._hazard_duration = np.zeros(shape=(self.n_line,), dtype=np.int) + # self.time_remaining_before_line_reconnection = np.full(shape=(self.n_line,), fill_value=0, dtype=np.int) + + # reset timings self._time_apply_act = 0 self._time_powerflow = 0 self._time_extract_obs = 0 + def _reset_redispatching(self): + # redispatching + self.target_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.actual_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.gen_uptime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_downtime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_activeprod_t = np.zeros(self.n_gen, dtype=np.float) + def reset(self): """ Reset the environment to a clean state. @@ -705,6 +969,7 @@ def reset(self): names_chronics_to_backend=self.names_chronics_to_backend) self.current_obs = None self._reset_maintenance() + self._reset_redispatching() self.reset_grid() # if True, then it will not disconnect lines above their thermal limits @@ -720,4 +985,33 @@ def close(self): if self.viewer: self.viewer.close() self.viewer = None - self.backend.close() \ No newline at end of file + self.backend.close() + + @staticmethod + def _get_poly(t, tmp_p, pmin, pmax): + return tmp_p + 0.5 * (pmax - pmin) * t + 0.5 * (pmax + pmin - 2 * tmp_p) * t ** 2 + + @staticmethod + def _get_poly_coeff(tmp_p, pmin, pmax): + p_s = tmp_p.sum() + p_min_s = pmin.sum() + p_max_s = pmax.sum() + + p_0 = p_s + p_1 = 0.5 * (p_max_s - p_min_s) + p_2 = 0.5 * (p_max_s + p_min_s - 2 * p_s) + return p_0, p_1, p_2 + + @staticmethod + def _get_t(tmp_p, pmin, pmax, total_dispatch): + # to_dispatch = too_much.sum() + not_enough.sum() + p_0, p_1, p_2 = Environment._get_poly_coeff(tmp_p, pmin, pmax) + + res = np.roots((p_2, p_1, p_0-(total_dispatch))) + res = res[np.isreal(res)] + res = res[(res <= 1) & (res >= -1)] + if res.shape[0] == 0: + raise RuntimeError("Impossible to solve for this equilibrium, not enough production") + else: + res = res[0] + return res \ No newline at end of file diff --git a/grid2op/tests/test_Environment.py b/grid2op/tests/test_Environment.py index 6cef09c58..1406fa67c 100644 --- a/grid2op/tests/test_Environment.py +++ b/grid2op/tests/test_Environment.py @@ -2,7 +2,7 @@ import os import sys import unittest - +import copy import numpy as np import pdb @@ -67,9 +67,9 @@ def setUp(self): self.env_params = Parameters() self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), - backend=self.backend, - chronics_handler=self.chronics_handler, - parameters=self.env_params, + backend=self.backend, + chronics_handler=self.chronics_handler, + parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend) def tearDown(self): @@ -78,6 +78,13 @@ def tearDown(self): def compare_vect(self, pred, true): return np.max(np.abs(pred- true)) <= self.tolvect + def test_step_doesnt_change_action(self): + # TODO THIS TEST + act = self.env.acttion_space() + act_init = copy.deepcopy(act) + res = self.env.step(act) + assert act == act_init + def test_load_env(self): """ Just executes the SetUp and tearDown functions. diff --git a/grid2op/tests/test_RedispatchEnv.py b/grid2op/tests/test_RedispatchEnv.py new file mode 100644 index 000000000..878438e6f --- /dev/null +++ b/grid2op/tests/test_RedispatchEnv.py @@ -0,0 +1,113 @@ +# making some test that the backned is working as expected +import os +import sys +import unittest +import copy +import numpy as np +import pdb + +from helper_path_test import PATH_DATA_TEST_PP, PATH_CHRONICS + +from Exceptions import * +from Environment import Environment +from BackendPandaPower import PandaPowerBackend +from Parameters import Parameters +from ChronicsHandler import ChronicsHandler, GridStateFromFile +from Reward import L2RPNReward +from MakeEnv import make +from GameRules import GameRules, DefaultRules +from Action import Action +import time + + +DEBUG = False +PROFILE_CODE = False +if PROFILE_CODE: + import cProfile + + +class TestLoadingBackendPandaPower(unittest.TestCase): + def setUp(self): + # powergrid + self.backend = PandaPowerBackend() + self.path_matpower = PATH_DATA_TEST_PP + self.case_file = "test_case14.json" + + # chronics + self.path_chron = os.path.join(PATH_CHRONICS, "chronics") + self.chronics_handler = ChronicsHandler(chronicsClass=GridStateFromFile, path=self.path_chron) + + self.tolvect = 1e-2 + self.tol_one = 1e-5 + self.id_chron_to_back_load = np.array([0, 1, 10, 2, 3, 4, 5, 6, 7, 8, 9]) + + # force the verbose backend + self.backend.detailed_infos_for_cascading_failures = True + + self.names_chronics_to_backend = {"loads": {"2_C-10.61": 'load_1_0', "3_C151.15": 'load_2_1', + "14_C63.6": 'load_13_2', "4_C-9.47": 'load_3_3', + "5_C201.84": 'load_4_4', + "6_C-6.27": 'load_5_5', "9_C130.49": 'load_8_6', + "10_C228.66": 'load_9_7', + "11_C-138.89": 'load_10_8', "12_C-27.88": 'load_11_9', + "13_C-13.33": 'load_12_10'}, + "lines": {'1_2_1': '0_1_0', '1_5_2': '0_4_1', '9_10_16': '8_9_2', + '9_14_17': '8_13_3', + '10_11_18': '9_10_4', '12_13_19': '11_12_5', '13_14_20': '12_13_6', + '2_3_3': '1_2_7', '2_4_4': '1_3_8', '2_5_5': '1_4_9', + '3_4_6': '2_3_10', + '4_5_7': '3_4_11', '6_11_11': '5_10_12', '6_12_12': '5_11_13', + '6_13_13': '5_12_14', '4_7_8': '3_6_15', '4_9_9': '3_8_16', + '5_6_10': '4_5_17', + '7_8_14': '6_7_18', '7_9_15': '6_8_19'}, + "prods": {"1_G137.1": 'gen_0_4', "3_G36.31": "gen_2_1", "6_G63.29": "gen_5_2", + "2_G-56.47": "gen_1_0", "8_G40.43": "gen_7_3"}, + } + + # _parameters for the environment + self.env_params = Parameters() + + self.env = Environment(init_grid_path=os.path.join(self.path_matpower, self.case_file), + backend=self.backend, + chronics_handler=self.chronics_handler, + parameters=self.env_params, + names_chronics_to_backend=self.names_chronics_to_backend, + actionClass=Action) + + def tearDown(self): + pass + + def compare_vect(self, pred, true): + return np.max(np.abs(pred- true)) <= self.tolvect + + def test_basic_redispatch_act(self): + act = self.env.action_space({"redispatch": [2, 20]}) + obs, reward, done, info = self.env.step(act) + pdb.set_trace() + assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one + th_dispatch = np.array([0., -5.2173913, 20., 0., -14.7826087]) + assert self.compare_vect(self.env.actual_dispatch, th_dispatch) + + target_val = self.chronics_handler.real_data.prod_p[1,:] + self.env.actual_dispatch + assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus + + # def test_two_redispatch_act(self): + # act = self.env.action_space({"redispatch": [2, 20]}) + # obs, reward, done, info = self.env.step(act) + # act = self.env.action_space({"redispatch": [1, 10]}) + # obs, reward, done, info = self.env.step(act) + # pdb.set_trace() + # th_dispatch = np.array([0., 10, 20., 0., -30]) + # assert self.compare_vect(self.env.actual_dispatch, th_dispatch) + # target_val = self.chronics_handler.real_data.prod_p[2,:] + self.env.actual_dispatch + # assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus + # assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one + # + # def test_count_turned_on(self): + # act = self.env.action_space() + # obs, reward, done, info = self.env.step(act) + +# TODO test that if i try to redispatched a turned off generator it breaks everything + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 9ba1cdf6f0fef8477e53632dd0214422fdfbd653 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 16 Jan 2020 11:58:50 +0100 Subject: [PATCH 05/14] all tests for redispatching are working, need to do all the others now --- grid2op/tests/test_RedispatchEnv.py | 101 +++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/grid2op/tests/test_RedispatchEnv.py b/grid2op/tests/test_RedispatchEnv.py index 878438e6f..434d41a14 100644 --- a/grid2op/tests/test_RedispatchEnv.py +++ b/grid2op/tests/test_RedispatchEnv.py @@ -73,6 +73,7 @@ def setUp(self): parameters=self.env_params, names_chronics_to_backend=self.names_chronics_to_backend, actionClass=Action) + self.array_double_dispatch = np.array([0., 12.41833569, 10.89081339, 0., -23.30914908]) def tearDown(self): pass @@ -81,31 +82,95 @@ def compare_vect(self, pred, true): return np.max(np.abs(pred- true)) <= self.tolvect def test_basic_redispatch_act(self): + # test of the implementation of a simple case redispatching on one generator, bellow ramp min and ramp max + act = self.env.action_space({"redispatch": [2, 5]}) + obs, reward, done, info = self.env.step(act) + assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one + th_dispatch = np.array([0., -1.30434783, 5., 0., -3.69565217]) + assert self.compare_vect(self.env.actual_dispatch, th_dispatch) + target_val = self.chronics_handler.real_data.prod_p[1, :] + self.env.actual_dispatch + assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus + + # check that the redispatching is apply in the right direction + indx_ok = self.env.target_dispatch != 0. + assert np.all(np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(self.env.target_dispatch[indx_ok])) + + def test_redispatch_act_above_pmax(self): + # in this test, the asked redispatching for generator 2 would make it above pmax, so the environment + # need to "cut" it automatically, without invalidating the action act = self.env.action_space({"redispatch": [2, 20]}) obs, reward, done, info = self.env.step(act) - pdb.set_trace() assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one - th_dispatch = np.array([0., -5.2173913, 20., 0., -14.7826087]) + th_dispatch = np.array([0., -2.69837697, 10.89081339, 0., -8.19243642]) + assert self.compare_vect(self.env.actual_dispatch, th_dispatch) + target_val = self.chronics_handler.real_data.prod_p[1, :] + self.env.actual_dispatch + assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus + + def test_two_redispatch_act(self): + act = self.env.action_space({"redispatch": [2, 20]}) + obs, reward, done, info = self.env.step(act) + act = self.env.action_space({"redispatch": [1, 10]}) + obs, reward, done, info = self.env.step(act) + th_dispatch = np.array([0., 10, 20., 0., 0.]) + assert self.compare_vect(self.env.target_dispatch, th_dispatch) + + # check that the redispatching is apply in the right direction + indx_ok = self.env.target_dispatch != 0. + assert np.all(np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(self.env.target_dispatch[indx_ok])) + + th_dispatch = np.array([0. , 7.37325847, 10.38913319, 0., -17.76239165]) assert self.compare_vect(self.env.actual_dispatch, th_dispatch) - target_val = self.chronics_handler.real_data.prod_p[1,:] + self.env.actual_dispatch + target_val = self.chronics_handler.real_data.prod_p[2, :] + self.env.actual_dispatch assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus + assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one + + def test_redispacth_two_gen(self): + act = self.env.action_space({"redispatch": [(2, 20), (1, 10)]}) + obs, reward, done, info = self.env.step(act) + + th_dispatch = np.array([0., 10, 20., 0., 0.]) + assert self.compare_vect(self.env.target_dispatch, th_dispatch) + + assert self.compare_vect(self.env.actual_dispatch, self.array_double_dispatch) + + # check that the redispatching is apply in the right direction + indx_ok = self.env.target_dispatch != 0. + assert np.all(np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(self.env.target_dispatch[indx_ok])) + + def test_redispacth_all_gen(self): + # this should be exactly the same as the previous one + act = self.env.action_space({"redispatch": [(2, 20.), (1, 10.), (4, -30.)]}) + obs, reward, done, info = self.env.step(act) + + th_dispatch = np.array([0., 10, 20., 0., -30.]) + assert self.compare_vect(self.env.target_dispatch, th_dispatch) + + assert self.compare_vect(self.env.actual_dispatch, self.array_double_dispatch) + + # check that the redispatching is apply in the right direction + indx_ok = self.env.target_dispatch != 0. + assert np.all(np.sign(self.env.actual_dispatch[indx_ok]) == np.sign(self.env.target_dispatch[indx_ok])) + + def test_count_turned_on(self): + act = self.env.action_space() + obs, reward, done, info = self.env.step(act) + assert np.all(self.env.gen_uptime == np.array([0, 1, 1, 0, 1])) + assert np.all(self.env.gen_downtime == np.array([1, 0, 0, 1, 0])) + obs, reward, done, info = self.env.step(act) + assert np.all(self.env.gen_uptime == np.array([0, 2, 2, 0, 2])) + assert np.all(self.env.gen_downtime == np.array([2, 0, 0, 2, 0])) + for i in range(63): + obs, reward, done, info = self.env.step(act) + + obs, reward, done, info = self.env.step(act) + assert np.all(self.env.gen_uptime == np.array([ 0, 66, 66, 1, 66])) + assert np.all(self.env.gen_downtime == np.array([66, 0, 0, 0, 0])) + + obs, reward, done, info = self.env.step(act) + assert np.all(self.env.gen_uptime == np.array([ 1, 67, 67, 2, 67])) + assert np.all(self.env.gen_downtime == np.array([0, 0, 0, 0, 0])) - # def test_two_redispatch_act(self): - # act = self.env.action_space({"redispatch": [2, 20]}) - # obs, reward, done, info = self.env.step(act) - # act = self.env.action_space({"redispatch": [1, 10]}) - # obs, reward, done, info = self.env.step(act) - # pdb.set_trace() - # th_dispatch = np.array([0., 10, 20., 0., -30]) - # assert self.compare_vect(self.env.actual_dispatch, th_dispatch) - # target_val = self.chronics_handler.real_data.prod_p[2,:] + self.env.actual_dispatch - # assert self.compare_vect(obs.prod_p[:-1], target_val[:-1]) # I remove last component which is the slack bus - # assert np.abs(np.sum(self.env.actual_dispatch)) <= self.tol_one - # - # def test_count_turned_on(self): - # act = self.env.action_space() - # obs, reward, done, info = self.env.step(act) # TODO test that if i try to redispatched a turned off generator it breaks everything From 0f33e6d5a45dd4ec89b0ad83c6311429199d6842 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 16 Jan 2020 15:09:47 +0100 Subject: [PATCH 06/14] fixing spelling on action, finish implementing redispatching --- CHANGELOG.rst | 3 + grid2op/Action.py | 241 ++++++++-------- grid2op/Environment.py | 264 ++++++++++-------- grid2op/Observation.py | 22 +- grid2op/Settings_5busExample.py | 4 +- grid2op/Space.py | 141 ++++++---- .../5bus_example.json | 0 .../chronics}/0/hazards.csv.bz2 | Bin .../chronics}/0/load_p.csv.bz2 | Bin .../chronics}/0/load_p_forecasted.csv.bz2 | Bin .../chronics}/0/load_q.csv.bz2 | Bin .../chronics}/0/load_q_forecasted.csv.bz2 | Bin .../chronics}/0/maintenance.csv.bz2 | Bin .../0/maintenance_forecasted.csv.bz2 | Bin .../chronics}/0/prod_p.csv.bz2 | Bin .../chronics}/0/prod_p_forecasted.csv.bz2 | Bin .../chronics}/0/prod_v.csv.bz2 | Bin .../chronics}/0/prod_v_forecasted.csv.bz2 | Bin .../chronics}/1/hazards.csv.bz2 | Bin .../chronics}/1/load_p.csv.bz2 | Bin .../chronics}/1/load_p_forecasted.csv.bz2 | Bin .../chronics}/1/load_q.csv.bz2 | Bin .../chronics}/1/load_q_forecasted.csv.bz2 | Bin .../chronics}/1/maintenance.csv.bz2 | Bin .../1/maintenance_forecasted.csv.bz2 | Bin .../chronics}/1/prod_p.csv.bz2 | Bin .../chronics}/1/prod_p_forecasted.csv.bz2 | Bin .../chronics}/1/prod_v.csv.bz2 | Bin .../chronics}/1/prod_v_forecasted.csv.bz2 | Bin .../chronics}/10/hazards.csv.bz2 | Bin .../chronics}/10/load_p.csv.bz2 | Bin .../chronics}/10/load_p_forecasted.csv.bz2 | Bin .../chronics}/10/load_q.csv.bz2 | Bin .../chronics}/10/load_q_forecasted.csv.bz2 | Bin .../chronics}/10/maintenance.csv.bz2 | Bin .../10/maintenance_forecasted.csv.bz2 | Bin .../chronics}/10/prod_p.csv.bz2 | Bin .../chronics}/10/prod_p_forecasted.csv.bz2 | Bin .../chronics}/10/prod_v.csv.bz2 | Bin .../chronics}/10/prod_v_forecasted.csv.bz2 | Bin .../chronics}/11/hazards.csv.bz2 | Bin .../chronics}/11/load_p.csv.bz2 | Bin .../chronics}/11/load_p_forecasted.csv.bz2 | Bin .../chronics}/11/load_q.csv.bz2 | Bin .../chronics}/11/load_q_forecasted.csv.bz2 | Bin .../chronics}/11/maintenance.csv.bz2 | Bin .../11/maintenance_forecasted.csv.bz2 | Bin .../chronics}/11/prod_p.csv.bz2 | Bin .../chronics}/11/prod_p_forecasted.csv.bz2 | Bin .../chronics}/11/prod_v.csv.bz2 | Bin .../chronics}/11/prod_v_forecasted.csv.bz2 | Bin .../chronics}/12/hazards.csv.bz2 | Bin .../chronics}/12/load_p.csv.bz2 | Bin .../chronics}/12/load_p_forecasted.csv.bz2 | Bin .../chronics}/12/load_q.csv.bz2 | Bin .../chronics}/12/load_q_forecasted.csv.bz2 | Bin .../chronics}/12/maintenance.csv.bz2 | Bin .../12/maintenance_forecasted.csv.bz2 | Bin .../chronics}/12/prod_p.csv.bz2 | Bin .../chronics}/12/prod_p_forecasted.csv.bz2 | Bin .../chronics}/12/prod_v.csv.bz2 | Bin .../chronics}/12/prod_v_forecasted.csv.bz2 | Bin .../chronics}/13/hazards.csv.bz2 | Bin .../chronics}/13/load_p.csv.bz2 | Bin .../chronics}/13/load_p_forecasted.csv.bz2 | Bin .../chronics}/13/load_q.csv.bz2 | Bin .../chronics}/13/load_q_forecasted.csv.bz2 | Bin .../chronics}/13/maintenance.csv.bz2 | Bin .../13/maintenance_forecasted.csv.bz2 | Bin .../chronics}/13/prod_p.csv.bz2 | Bin .../chronics}/13/prod_p_forecasted.csv.bz2 | Bin .../chronics}/13/prod_v.csv.bz2 | Bin .../chronics}/13/prod_v_forecasted.csv.bz2 | Bin .../chronics}/14/hazards.csv.bz2 | Bin .../chronics}/14/load_p.csv.bz2 | Bin .../chronics}/14/load_p_forecasted.csv.bz2 | Bin .../chronics}/14/load_q.csv.bz2 | Bin .../chronics}/14/load_q_forecasted.csv.bz2 | Bin .../chronics}/14/maintenance.csv.bz2 | Bin .../14/maintenance_forecasted.csv.bz2 | Bin .../chronics}/14/prod_p.csv.bz2 | Bin .../chronics}/14/prod_p_forecasted.csv.bz2 | Bin .../chronics}/14/prod_v.csv.bz2 | Bin .../chronics}/14/prod_v_forecasted.csv.bz2 | Bin .../chronics}/15/hazards.csv.bz2 | Bin .../chronics}/15/load_p.csv.bz2 | Bin .../chronics}/15/load_p_forecasted.csv.bz2 | Bin .../chronics}/15/load_q.csv.bz2 | Bin .../chronics}/15/load_q_forecasted.csv.bz2 | Bin .../chronics}/15/maintenance.csv.bz2 | Bin .../15/maintenance_forecasted.csv.bz2 | Bin .../chronics}/15/prod_p.csv.bz2 | Bin .../chronics}/15/prod_p_forecasted.csv.bz2 | Bin .../chronics}/15/prod_v.csv.bz2 | Bin .../chronics}/15/prod_v_forecasted.csv.bz2 | Bin .../chronics}/16/hazards.csv.bz2 | Bin .../chronics}/16/load_p.csv.bz2 | Bin .../chronics}/16/load_p_forecasted.csv.bz2 | Bin .../chronics}/16/load_q.csv.bz2 | Bin .../chronics}/16/load_q_forecasted.csv.bz2 | Bin .../chronics}/16/maintenance.csv.bz2 | Bin .../16/maintenance_forecasted.csv.bz2 | Bin .../chronics}/16/prod_p.csv.bz2 | Bin .../chronics}/16/prod_p_forecasted.csv.bz2 | Bin .../chronics}/16/prod_v.csv.bz2 | Bin .../chronics}/16/prod_v_forecasted.csv.bz2 | Bin .../chronics}/17/hazards.csv.bz2 | Bin .../chronics}/17/load_p.csv.bz2 | Bin .../chronics}/17/load_p_forecasted.csv.bz2 | Bin .../chronics}/17/load_q.csv.bz2 | Bin .../chronics}/17/load_q_forecasted.csv.bz2 | Bin .../chronics}/17/maintenance.csv.bz2 | Bin .../17/maintenance_forecasted.csv.bz2 | Bin .../chronics}/17/prod_p.csv.bz2 | Bin .../chronics}/17/prod_p_forecasted.csv.bz2 | Bin .../chronics}/17/prod_v.csv.bz2 | Bin .../chronics}/17/prod_v_forecasted.csv.bz2 | Bin .../chronics}/18/hazards.csv.bz2 | Bin .../chronics}/18/load_p.csv.bz2 | Bin .../chronics}/18/load_p_forecasted.csv.bz2 | Bin .../chronics}/18/load_q.csv.bz2 | Bin .../chronics}/18/load_q_forecasted.csv.bz2 | Bin .../chronics}/18/maintenance.csv.bz2 | Bin .../18/maintenance_forecasted.csv.bz2 | Bin .../chronics}/18/prod_p.csv.bz2 | Bin .../chronics}/18/prod_p_forecasted.csv.bz2 | Bin .../chronics}/18/prod_v.csv.bz2 | Bin .../chronics}/18/prod_v_forecasted.csv.bz2 | Bin .../chronics}/19/hazards.csv.bz2 | Bin .../chronics}/19/load_p.csv.bz2 | Bin .../chronics}/19/load_p_forecasted.csv.bz2 | Bin .../chronics}/19/load_q.csv.bz2 | Bin .../chronics}/19/load_q_forecasted.csv.bz2 | Bin .../chronics}/19/maintenance.csv.bz2 | Bin .../19/maintenance_forecasted.csv.bz2 | Bin .../chronics}/19/prod_p.csv.bz2 | Bin .../chronics}/19/prod_p_forecasted.csv.bz2 | Bin .../chronics}/19/prod_v.csv.bz2 | Bin .../chronics}/19/prod_v_forecasted.csv.bz2 | Bin .../chronics}/2/hazards.csv.bz2 | Bin .../chronics}/2/load_p.csv.bz2 | Bin .../chronics}/2/load_p_forecasted.csv.bz2 | Bin .../chronics}/2/load_q.csv.bz2 | Bin .../chronics}/2/load_q_forecasted.csv.bz2 | Bin .../chronics}/2/maintenance.csv.bz2 | Bin .../2/maintenance_forecasted.csv.bz2 | Bin .../chronics}/2/prod_p.csv.bz2 | Bin .../chronics}/2/prod_p_forecasted.csv.bz2 | Bin .../chronics}/2/prod_v.csv.bz2 | Bin .../chronics}/2/prod_v_forecasted.csv.bz2 | Bin .../chronics}/3/hazards.csv.bz2 | Bin .../chronics}/3/load_p.csv.bz2 | Bin .../chronics}/3/load_p_forecasted.csv.bz2 | Bin .../chronics}/3/load_q.csv.bz2 | Bin .../chronics}/3/load_q_forecasted.csv.bz2 | Bin .../chronics}/3/maintenance.csv.bz2 | Bin .../3/maintenance_forecasted.csv.bz2 | Bin .../chronics}/3/prod_p.csv.bz2 | Bin .../chronics}/3/prod_p_forecasted.csv.bz2 | Bin .../chronics}/3/prod_v.csv.bz2 | Bin .../chronics}/3/prod_v_forecasted.csv.bz2 | Bin .../chronics}/4/hazards.csv.bz2 | Bin .../chronics}/4/load_p.csv.bz2 | Bin .../chronics}/4/load_p_forecasted.csv.bz2 | Bin .../chronics}/4/load_q.csv.bz2 | Bin .../chronics}/4/load_q_forecasted.csv.bz2 | Bin .../chronics}/4/maintenance.csv.bz2 | Bin .../4/maintenance_forecasted.csv.bz2 | Bin .../chronics}/4/prod_p.csv.bz2 | Bin .../chronics}/4/prod_p_forecasted.csv.bz2 | Bin .../chronics}/4/prod_v.csv.bz2 | Bin .../chronics}/4/prod_v_forecasted.csv.bz2 | Bin .../chronics}/5/hazards.csv.bz2 | Bin .../chronics}/5/load_p.csv.bz2 | Bin .../chronics}/5/load_p_forecasted.csv.bz2 | Bin .../chronics}/5/load_q.csv.bz2 | Bin .../chronics}/5/load_q_forecasted.csv.bz2 | Bin .../chronics}/5/maintenance.csv.bz2 | Bin .../5/maintenance_forecasted.csv.bz2 | Bin .../chronics}/5/prod_p.csv.bz2 | Bin .../chronics}/5/prod_p_forecasted.csv.bz2 | Bin .../chronics}/5/prod_v.csv.bz2 | Bin .../chronics}/5/prod_v_forecasted.csv.bz2 | Bin .../chronics}/6/hazards.csv.bz2 | Bin .../chronics}/6/load_p.csv.bz2 | Bin .../chronics}/6/load_p_forecasted.csv.bz2 | Bin .../chronics}/6/load_q.csv.bz2 | Bin .../chronics}/6/load_q_forecasted.csv.bz2 | Bin .../chronics}/6/maintenance.csv.bz2 | Bin .../6/maintenance_forecasted.csv.bz2 | Bin .../chronics}/6/prod_p.csv.bz2 | Bin .../chronics}/6/prod_p_forecasted.csv.bz2 | Bin .../chronics}/6/prod_v.csv.bz2 | Bin .../chronics}/6/prod_v_forecasted.csv.bz2 | Bin .../chronics}/7/hazards.csv.bz2 | Bin .../chronics}/7/load_p.csv.bz2 | Bin .../chronics}/7/load_p_forecasted.csv.bz2 | Bin .../chronics}/7/load_q.csv.bz2 | Bin .../chronics}/7/load_q_forecasted.csv.bz2 | Bin .../chronics}/7/maintenance.csv.bz2 | Bin .../7/maintenance_forecasted.csv.bz2 | Bin .../chronics}/7/prod_p.csv.bz2 | Bin .../chronics}/7/prod_p_forecasted.csv.bz2 | Bin .../chronics}/7/prod_v.csv.bz2 | Bin .../chronics}/7/prod_v_forecasted.csv.bz2 | Bin .../chronics}/8/hazards.csv.bz2 | Bin .../chronics}/8/load_p.csv.bz2 | Bin .../chronics}/8/load_p_forecasted.csv.bz2 | Bin .../chronics}/8/load_q.csv.bz2 | Bin .../chronics}/8/load_q_forecasted.csv.bz2 | Bin .../chronics}/8/maintenance.csv.bz2 | Bin .../8/maintenance_forecasted.csv.bz2 | Bin .../chronics}/8/prod_p.csv.bz2 | Bin .../chronics}/8/prod_p_forecasted.csv.bz2 | Bin .../chronics}/8/prod_v.csv.bz2 | Bin .../chronics}/8/prod_v_forecasted.csv.bz2 | Bin .../chronics}/9/hazards.csv.bz2 | Bin .../chronics}/9/load_p.csv.bz2 | Bin .../chronics}/9/load_p_forecasted.csv.bz2 | Bin .../chronics}/9/load_q.csv.bz2 | Bin .../chronics}/9/load_q_forecasted.csv.bz2 | Bin .../chronics}/9/maintenance.csv.bz2 | Bin .../9/maintenance_forecasted.csv.bz2 | Bin .../chronics}/9/prod_p.csv.bz2 | Bin .../chronics}/9/prod_p_forecasted.csv.bz2 | Bin .../chronics}/9/prod_v.csv.bz2 | Bin .../chronics}/9/prod_v_forecasted.csv.bz2 | Bin grid2op/tests/test_Action.py | 45 +-- grid2op/tests/test_Environment.py | 2 +- grid2op/tests/test_Observation.py | 28 +- setup.py | 2 +- 231 files changed, 434 insertions(+), 318 deletions(-) rename grid2op/data/{test_PandaPower => 5bus_example}/5bus_example.json (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/0/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/1/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/10/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/11/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/12/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/13/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/14/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/15/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/16/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/17/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/18/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/19/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/2/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/3/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/4/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/5/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/6/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/7/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/8/prod_v_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/hazards.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/load_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/load_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/load_q.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/load_q_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/maintenance.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/maintenance_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/prod_p.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/prod_p_forecasted.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/prod_v.csv.bz2 (100%) rename grid2op/data/{chronics_5bus_example => 5bus_example/chronics}/9/prod_v_forecasted.csv.bz2 (100%) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0a30f5fae..b1ab93d17 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ Change Log - [UPDATED] GridStateFromFile can now read the starting date and the time interval of the chronics. - [BREAKING] Action/Backend has been modified with the implementation of redispatching. If you used a custom backend, you'll have to implement the "redispatching" part. +- [BREAKING] with the introduction of redispatching, old action space and observation space, + stored as json for example, will not be usable: action size and observation size + have been modified. [0.4.2] - 2020-01-08 -------------------- diff --git a/grid2op/Action.py b/grid2op/Action.py index 7e31440ff..2538ff6f0 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -11,17 +11,16 @@ - the loads active power consumption - the loads reactive power consumption - - the status of the powerlines (connected / disconnected) - - the configuration at substations eg setting different object to different bus for example + - the status of the powerlines (connected/disconnected) + - the configuration at substations eg setting different objects to different buses for example -The Action class is an abstract class. You can implement it the way you want. If you decide to extend it, make sure -that the :class:`grid2op.Backend` class will be able to understand it. If you don't, your extension will have no effect -on the unerlying powergrid. Indeed a :class:`grid2op.Backend` will call the :func:`Action.__call__` method and should +The Action class is abstract. You can implement it the way you want. If you decide to extend it, make sure +that the :class:`grid2op.Backend` class will be able to understand it. If you don't, your extension will not affect the underlying powergrid. Indeed a :class:`grid2op.Backend` will call the :func:`Action.__call__` method and should understands its return type. In this module we derived two action class: - - :class:`Action` represents an type of action that can act on all the above mentionned objects + - :class:`Action` represents a type of action that can act on all the above-mentioned objects - :class:`TopologyAction` restricts the modification to line status modification and bus reconfiguration at substations. @@ -30,12 +29,14 @@ - :func:`Action.__str__` prints the action in a format that gives usefull information on how it will affect the powergrid - :func:`Action.effect_on` returns a dictionnary that gives information about its effect. -Finally, :class:`Action` class define some strict behaviour to follow if reimplementing them. The correctness of each +Finally, :class:`Action` class define some strict behavior to follow if reimplementing them. The correctness of each instances of Action is assessed both when calling :func:`Action.update` or with a call to :func:`Action._check_for_ambiguity` performed for example by the Backend when it must implement its effect on the powergrid through a call to :func:`Action.__call__` """ + + import numpy as np import warnings import copy @@ -66,149 +67,151 @@ class Action(GridObjects): """ - This is a base class for each :class:`Action` objects. - As stated above, an action represents in a convenient way the modifications that will affect a powergrid. - - It is not recommended to instanciate an action from scratch. The recommended way to get an action is either by - modifying an existing one using the method :func:`Action.update` or to call and :class:`HelperAction` object that - has been properly set up by an :class:`grid2op.Environment`. - - Action can be fully convert to and back from a numpy array with a **fixed** size. - - An action can modify the _grid in multiple way. - It can change : +This is a base class for each :class:`Action` objects. +As stated above, an action represents conveniently the modifications that will affect a powergrid. - - the production and voltage setpoint of the generator units - - the amount of power consumed (for both active and reactive part) for load - - disconnect powerlines - - change the topology of the _grid. +It is not recommended to instantiate an action from scratch. The recommended way to get an action is either by +modifying an existing one using the method :func:`Action.update` or to call and :class:`HelperAction` object that +has been properly set up by an :class:`grid2op.Environment`. - In order to be valid, an action should be convertible to a tuple of 5 elements: +Action can be fully converted to and back from a numpy array with a **fixed** size. - - the first element are the "injections": representing the way generator units and loads are modified - - It is in turn a dictionnary with the following keys (optional) +An action can modify the grid in multiple ways. +It can change : - - "load_p" a vector of the same size of the load, giving the modification of the loads active consumption - - "load_q" a vector of the same size of the load, giving the modification of the loads reactive consumption - - "prod_p" a vector of the same size of the generators, giving the modification of the productions active setpoint production - - "prod_v" a vector of the same size of the generators, giving the modification of the productions voltage setpoint +- the production and voltage setpoint of the generator units +- the amount of power consumed (for both active and reactive part) for load +- disconnect powerlines +- change the topology of the _grid. - - the second element is made of force line status. It is made of a vector of size :attr:`Action._n_lines` - (the number of lines in the powergrid) and is interepreted as: +To be valid, an action should be convertible to a tuple of 5 elements: - - -1 force line disconnection - - +1 force line reconnection - - 0 do nothing to this line +- the first element is the "injections" vector: representing the way generator units and loads are modified + - It is, in turn, a dictionary with the following keys (optional) - - the third element is the switch line status vector. It is made of a vector of size :attr:`Action._n_lines` and is - interpreted as: + - "load_p" a vector of the same size of the load, giving the modification of the loads active consumption + - "load_q" a vector of the same size of the load, giving the modification of the loads reactive consumption + - "prod_p" a vector of the same size of the generators, giving the modification of the productions active setpoint production + - "prod_v" a vector of the same size of the generators, giving the modification of the productions voltage setpoint - - True: change the line status - - False: don't do anything +- the second element is made of force line status. It is made of a vector of size :attr:`Action._n_lines` + (the number of lines in the powergrid) and is interpreted as: - - the fourth element set the buses to which the object is connected. It's a vector of integer with the following - interpretation: + - -1 force line disconnection + - +1 force line reconnection + - 0 do nothing to this line - - 0 -> don't change - - 1 -> connect to bus 1 - - 2 -> connect to bus 2 - - -1 -> disconnect the object. +- the third element is the switch line status vector. It is made of a vector of size :attr:`Action._n_lines` and is + interpreted as: - - the fifth element change the buses to which the object is connected. It's a boolean vector interpreted as: - - False: nothing is done - - True: change the bus eg connect it to bus 1 if it was connected to bus 2 or connect it to bus 2 if it was - connected to bus 1. NB this is only active if the system has only 2 buses per substation (that's the case for - the L2RPN challenge). + - True: change the line status + - False: don't do anything - - the sixth elements is a vector, representing the redispatching. Component of this vector are added to the - generators active setpoint value (if set) of the first elements. +- the fourth element set the buses to which the object is connected. It's a vector of integers with the following + interpretation: - **NB** the difference between :attr:`Action._set_topo_vect` and :attr:`Action._change_bus_vect` is the following: + - 0 -> don't change + - 1 -> connect to bus 1 + - 2 -> connect to bus 2 + - -1 -> disconnect the object. - - If a component of :attr:`Action._set_topo_vect` is 1, then the object (load, generator or powerline) - will be moved to bus 1 of the substation to - which it is connected. If it is already to bus 1 nothing will be done. If it's on another bus it will connect - it to bus 1. It's it's disconnected, it will reconnect it and connect it to bus 1. - - If a component of :attr:`Action._change_bus_vect` is True, then object will be moved from one bus to another. - If the object were on bus 1 - it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object were - disconnected, then this does nothing. +- the fifth element changes the buses to which the object is connected. It's a boolean vector interpreted as: + - False: nothing is done + - True: change the bus eg connect it to bus 1 if it was connected to bus 2 or connect it to bus 2 if it was + connected to bus 1. NB this is only active if the system has only 2 buses per substation (that's the case for + the L2RPN challenge). - The conversion to the action into a understandable format by the backend is performed by the "update" method, - that takes into account a dictionnary, and is responsible to convert it into this format. - It is possible to overload this class as long as the overloaded :func:`Action.__call__` operator returns the - specified format, and the :func:`Action.__init__` method have the same signature. - - This format is then digested by the backend and the powergrid is modified accordingly. - - Attributes - ---------- +- the sixth element is a vector, representing the redispatching. Component of this vector is added to the + generators active setpoint value (if set) of the first elements. - _set_line_status: :class:`numpy.ndarray`, dtype:int - For each powerlines, it gives the effect of the action on the status of it. It should be understand as: +**NB** the difference between :attr:`Action._set_topo_vect` and :attr:`Action._change_bus_vect` is the following: - - -1 : disconnect the powerline - - 0 : don't affect the powerline - - +1 : reconnect the powerline + - If a component of :attr:`Action._set_topo_vect` is 1, then the object (load, generator or powerline) + will be moved to bus 1 of the substation to which it is connected. If it is already to bus 1 nothing will be done. If it's on another bus it will connect it to bus 1. It's disconnected, it will reconnect it and connect it to bus 1. + - If a component of :attr:`Action._change_bus_vect` is True, then the object will be moved from one bus to another. + If the object were on bus 1 + it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object were disconnected, then this does nothing. - _switch_line_status: :class:`numpy.ndarray`, dtype:bool - For each powerline, it informs whether the action will switch the status of a powerline of not. It should be - understood as followed: +The conversion to the action into an understandable format by the backend is performed by the "update" method, +that takes into account a dictionary and is responsible to convert it into this format. +It is possible to overload this class as long as the overloaded :func:`Action.__call__` operator returns the +specified format, and the :func:`Action.__init__` method has the same signature. - - ``False`` : the action doesn't affect the powerline - - ``True`` : the action affect the powerline. If it was connected, it will disconnect it. If it was - disconnected, it will reconnect it. +This format is then digested by the backend and the powergrid is modified accordingly. - _dict_inj: ``dict`` - Represents the modification of the injection (productions and loads) of the power _grid. This dictionnary can - have the optional keys: +Attributes +---------- - - "load_p" to set the active load values (this is a np array with the same size as the number of load - in the power _grid with Nan: don't change anything, else set the value - - "load_q" : same as above but for the load reactive values - - "prod_p" : same as above but for the generator active setpoint values. It has the size corresponding - to the number of generators in the test case. - - "prod_v" : same as above but set the voltage setpoint of generator units. +_set_line_status: :class:`numpy.ndarray`, dtype:int + For each powerline, it gives the effect of the action on the status of it. It should be understood as: - _set_topo_vect: :class:`numpy.ndarray`, dtype:int - Similar to :attr:`Action._set_line_status` but instead of affecting the status of powerlines, it affects the - bus connectivity at substation. It has the same size as the full topological vector (:attr:`Action._dim_topo`) - and for each element it should be understood as: + - -1: disconnect the powerline + - 0: don't affect the powerline + - +1: reconnect the powerline - - 0 : nothing is changed for this element - - +1 : this element is affected to bus 1 - - -1 : this element is affected to bus 2 +_switch_line_status: :class:`numpy.ndarray`, dtype:bool + For each powerline, it informs whether the action will switch the status of a powerline of not. It should be + understood as followed: - _change_bus_vect: :class:`numpy.ndarray`, dtype:bool - Similar to :attr:`Action._switch_line_status` but it affects the topology at substations instead of the status - of the powerline. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and each - component should means: + - ``False``: the action doesn't affect the powerline + - ``True``: the action affects the powerline. If it was connected, it will disconnect it. If it was + disconnected, it will reconnect it. - - ``False`` : the object is not affected - - ``True`` : the object will be moved to another bus. If it was on bus 1 it will be moved on bus 2, and if - it was on bus 2 it will be moved on bus 1. +_dict_inj: ``dict`` + Represents the modification of the injection (productions and loads) of the power _grid. This dictionary can + have the optional keys: - authorized_keys: :class:`set` - The set indicating which keys the actions is able to understand when calling :func:`Action.update` + - "load_p" to set the active load values (this is a numpy array with the same size as the number of load + in the power _grid with Nan: don't change anything, else set the value + - "load_q": same as above but for the load reactive values + - "prod_p": same as above but for the generator active setpoint values. It has the size corresponding + to the number of generators in the test case. + - "prod_v": same as above but set the voltage setpoint of generator units. - _subs_impacted: :class:`numpy.ndarray`, dtype:bool - This attributes is either not initialized (set to ``None``) or it tells, for each substation, if it is impacted - by the action (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``True``) or not - (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``False``) +_set_topo_vect: :class:`numpy.ndarray`, dtype:int + Similar to :attr:`Action._set_line_status` but instead of affecting the status of powerlines, it affects the + bus connectivity at a substation. It has the same size as the full topological vector (:attr:`Action._dim_topo`) + and for each element it should be understood as: - _lines_impacted: :class:`numpy.ndarray`, dtype:bool - This attributes is either not initialized (set to ``None``) or it tells, for each powerline, if it is impacted - by the action (in this case :attr:`Action._lines_impacted`\[line_id\] is ``True``) or not - (in this case :attr:`Action._subs_impacted`\[line_id\] is ``False``) + - 0: nothing is changed for this element + - +1: this element is affected to bus 1 + - -1: this element is affected to bus 2 + +_change_bus_vect: :class:`numpy.ndarray`, dtype:bool + Similar to :attr:`Action._switch_line_status` but it affects the topology at substations instead of the status of + the powerline. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and each + component should mean: - vars_action: ``list``, static - Authorized key that are processed by :func:`Action.__call__` to modify the injections + - ``False``: the object is not affected + - ``True``: the object will be moved to another bus. If it was on bus 1 it will be moved on bus 2, and if + it was on bus 2 it will be moved on bus 1. - vars_action_set: ``set``, static - Authorized key that are processed by :func:`Action.__call__` to modify the injections +authorized_keys: :class:`set` + The set indicating which keys the actions can understand when calling :func:`Action.update` + +_subs_impacted: :class:`numpy.ndarray`, dtype:bool + This attributes is either not initialized (set to ``None``) or it tells, for each substation, if it is impacted + by the action (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``True``) or not + (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``False``) - _redispatch: :class:`numpy.ndarray`, dtype:bool - TODO code that and to the documentation +_lines_impacted: :class:`numpy.ndarray`, dtype:bool + This attributes is either not initialized (set to ``None``) or it tells, for each powerline, if it is impacted + by the action (in this case :attr:`Action._lines_impacted`\[line_id\] is ``True``) or not + (in this case :attr:`Action._subs_impacted`\[line_id\] is ``False``) + +vars_action: ``list``, static + The authorized key that are processed by :func:`Action.__call__` to modify the injections + +vars_action_set: ``set``, static + The authorized key that is processed by :func:`Action.__call__` to modify the injections + +_redispatch: :class:`numpy.ndarray`, dtype:float + Amount of redispatching that this action will perform. Redispatching will increase the generator's active setpoint + value. This will be added to the value of the generators. The Environment will make sure that every physical + constraint is met. This means that the agent provides a setpoint, but there is no guarantee that the setpoint + will be achievable. Redispatching action is cumulative, this means that if at a given timestep you ask +10 MW + on a generator, and on another you ask +10 MW then the total setpoint for this generator that the environment + will try to implement is +20MW. """ diff --git a/grid2op/Environment.py b/grid2op/Environment.py index 888ed54c7..f4122b09b 100644 --- a/grid2op/Environment.py +++ b/grid2op/Environment.py @@ -559,6 +559,138 @@ def _update_time_reconnection_hazards_maintenance(self): self.time_remaining_before_line_reconnection = np.maximum(self.time_remaining_before_line_reconnection, self._hazard_duration) + def _get_redisp_zero_sum(self, action, redisp_act, new_p): + """ + + Parameters + ---------- + action + redisp_act: + the redispatching part of the action + new_p: + the new target generation for each generator + + Returns + ------- + + """ + is_illegal = False + + # make the target dispatch a 0-sum vector (using only dispatchable unit, not dispatched) + # dispatch only the generator that are at zero + avail_gen = self.target_dispatch == 0. # generators with a redispatching target cannot be redispatched again + avail_gen = avail_gen & self.gen_redispatchable # i can only redispatched dispatchable generators + avail_gen = avail_gen & (new_p > 0.) + + # # another need to cut: it would result in above pmin or bellow pmax, in this case (but in the case it's valid) + # # we choose to cut down the redispatching action. + # curtail_generation = 1. * new_p + # mask_min = (new_p + redisp_act < self.gen_pmin + self._epsilon_poly) & avail_gen + # curtail_generation[mask_min] = self.gen_pmin[mask_min] + self._epsilon_poly + # mask_max = (new_p + redisp_act > self.gen_pmax - self._epsilon_poly) & avail_gen + # curtail_generation[mask_max] = self.gen_pmax[mask_max] - self._epsilon_poly + # + # diff_th_imp = new_p - curtail_generation + # redisp_act[mask_min] += diff_th_imp[mask_min] + # redisp_act[mask_max] += diff_th_imp[mask_max] + # + # # get the maximum difference possible + # # to do change the name !!! + # gen_min = np.maximum(curtail_generation - self.gen_pmin, - self.gen_max_ramp_down) + # gen_max = np.minimum(curtail_generation - self.gen_pmax, self.gen_max_ramp_up) + + # gen_min = np.maximum(self.gen_pmin - new_p, - self.gen_max_ramp_down) + # gen_max = np.minimum(self.gen_pmax - new_p, self.gen_max_ramp_up) + gen_min = -self.gen_max_ramp_down + gen_max = self.gen_max_ramp_up + + if not np.sum(avail_gen): + # TODO reason for that in info + is_illegal = True + action = self.helper_action_player({}) + return action._redispatch, is_illegal, action + + try: + t_zerosum = self._get_t(redisp_act[avail_gen], + pmin=np.sum(gen_min[avail_gen]), + pmax=np.sum(gen_max[avail_gen]), + total_dispatch=-np.sum(redisp_act)) + except: + # i can't implement redispatching due to impossibility to dispatch on the other generator + # it's a non valid action + is_illegal = True + return action._redispatch, is_illegal, action + actual_dispatch_tmp = self._get_poly(t=t_zerosum, + pmax=gen_max[avail_gen], + pmin=gen_min[avail_gen], + tmp_p=redisp_act[avail_gen]) + self.actual_dispatch[avail_gen] = actual_dispatch_tmp + return redisp_act, is_illegal, action + + def _compute_actual_dispatch(self, new_p): + # get the maximum ranged of the generator possible with the chronics + # gen_min = np.maximum(self.gen_pmin, self.gen_activeprod_t - self.gen_max_ramp_down) + # gen_max = np.minimum(self.gen_pmax, self.gen_activeprod_t + self.gen_max_ramp_up) + + # below is the part emulating the active balancing part in a powergrid. It's not handled by operators + # but by an automaton. + + # this automated conrol only affect turned-on generators that are dispatchable + turned_on_gen = new_p > 0. + gen_redispatchable = self.gen_redispatchable & turned_on_gen + + # add the redispatching to the nex target production value + new_p_if_redisp_ok = new_p + self.actual_dispatch + # the new_p vector now stores the new target value for the generators. + + # get the maxium generator consistent values + gen_min = np.maximum(self.gen_pmin, self.gen_activeprod_t - self.gen_max_ramp_down) + gen_max = np.minimum(self.gen_pmax, self.gen_activeprod_t + self.gen_max_ramp_up) + + actual_ramp = new_p_if_redisp_ok - self.gen_activeprod_t + if (np.any(new_p_if_redisp_ok > self.gen_pmax) or np.any(new_p_if_redisp_ok < self.gen_pmin) or \ + np.any(actual_ramp > self.gen_max_ramp_up) or np.any(actual_ramp < -self.gen_max_ramp_down)) and \ + np.any(self.gen_activeprod_t != 0.): + # i am in a case where the target redispatching is not possible, due to the new values + # i need to come up with a solution to fix that + # note that the condition "np.any(self.gen_activeprod_t != 0.)" is added because at the first time + # step there is no need to check all that. + # but take into account pmin and pmax + curtail_generation = 1. * new_p_if_redisp_ok + mask_min = (new_p_if_redisp_ok < gen_min + self._epsilon_poly) & gen_redispatchable + mask_max = (new_p_if_redisp_ok > gen_max - self._epsilon_poly) & gen_redispatchable + if np.any(mask_min) or np.any(mask_max): + # modify the implemented redispatching to take into account this "curtailement" + # due to physical limitation + new_dispatch = 1.* self.actual_dispatch + curtail_generation[mask_min] = gen_min[mask_min] + self._epsilon_poly + curtail_generation[mask_max] = gen_max[mask_max] - self._epsilon_poly + diff_th_imp = new_p_if_redisp_ok - curtail_generation + new_dispatch[mask_min] += diff_th_imp[mask_min] + new_dispatch[mask_max] -= diff_th_imp[mask_max] + + gen_dispatch_min = np.maximum(self.gen_pmin - new_p, - self.gen_max_ramp_down) + gen_dispatch_max = np.minimum(self.gen_pmax - new_p, self.gen_max_ramp_up) + + # for polynomial stability + gen_dispatch_min[mask_max] = new_dispatch[mask_max] - self._epsilon_poly + gen_dispatch_max[mask_min] = new_dispatch[mask_max] + self._epsilon_poly + # make sure the new redispatching sum to 0 + t_dispatch = 0. + try: + t_dispatch = self._get_t(new_dispatch[gen_redispatchable], + pmin=np.sum(gen_dispatch_min[gen_redispatchable]), + pmax=np.sum(gen_dispatch_max[gen_redispatchable]), + total_dispatch=0.) + except Exception as e: + # TODO check that it canont happen if the chronics are correct. + print("epic fail in redispatching with error {}".format(e)) + pass + self.actual_dispatch[gen_redispatchable] = self._get_poly(t=t_dispatch, + pmax=gen_dispatch_max[gen_redispatchable], + pmin=gen_dispatch_min[gen_redispatchable], + tmp_p=new_dispatch[gen_redispatchable]) + def _simulate_automaton_redispatching(self, action): # Redispatching process the redispatching actions here, get a redispatching vector with 0-sum # from the environment. @@ -569,7 +701,7 @@ def _simulate_automaton_redispatching(self, action): redisp_act_init = 1. * action._redispatch redisp_act = 1. * action._redispatch - if np.all(redisp_act == 0.): + if np.all(redisp_act == 0.) and np.all(self.target_dispatch == 0.): return redisp_act, is_illegal, action # make sure the dispatching action is not implemented "as is" by the backend. @@ -614,117 +746,22 @@ def _simulate_automaton_redispatching(self, action): indx_ok = np.isfinite(tmp) new_p[indx_ok] = tmp[indx_ok] - # action is "capped", a generator has been redispatched, but it's turned off by the environment redisp_act[new_p == 0.] = 0. # TODO add a flag here too, like before (the action has been "cut") - # make the target dispatch a 0-sum vector (using only dispatchable unit, not dispatched) - # dispatch only the generator that are at zero - # avail_gen = redisp_act == 0. # generators with a redispatching target cannot be redispatched again - avail_gen = self.gen_redispatchable # i can only redispatched dispatchable generators - avail_gen = avail_gen & (new_p > 0.) - - # another need to cut: it would result in above pmin or bellow pmax, in this case (but in the case it's valid) - # we choose to cut down the redispatching action. - curtail_generation = 1. * new_p - mask_min = (new_p + redisp_act < self.gen_pmin + self._epsilon_poly) & avail_gen - curtail_generation[mask_min] = self.gen_pmin[mask_min] + self._epsilon_poly - mask_max = (new_p + redisp_act > self.gen_pmax - self._epsilon_poly) & avail_gen - curtail_generation[mask_max] = self.gen_pmax[mask_max] - self._epsilon_poly - - diff_th_imp = new_p - curtail_generation - redisp_act[mask_min] += diff_th_imp[mask_min] - redisp_act[mask_max] += diff_th_imp[mask_max] - - # get the maximum difference possible - # to do change the name !!! - gen_min = np.maximum(curtail_generation - self.gen_pmin, - self.gen_max_ramp_down) - gen_max = np.minimum(curtail_generation - self.gen_pmax, self.gen_max_ramp_up) - - if not np.sum(avail_gen): - # TODO reason for that in info - is_illegal = True - action = self.helper_action_player({}) - return action._redispatch, is_illegal, action - - try: - t_zerosum = self._get_t(redisp_act[avail_gen], - pmin=np.sum(gen_min[avail_gen]), - pmax=np.sum(gen_max[avail_gen]), - total_dispatch=-np.sum(redisp_act)) - except: - # i can't implement redispatching due to impossibility to dispatch on the other generator - # it's a non valid action - return action._redispatch, is_illegal, action - # get the target redispatching (cumulation starting from the first element of the scenario) self.target_dispatch += redisp_act self.actual_dispatch = 1. * self.target_dispatch - actual_dispatch_tmp = self._get_poly(t=t_zerosum, - pmax=gen_max[avail_gen], - pmin=gen_min[avail_gen], - tmp_p=redisp_act[avail_gen]) - self.actual_dispatch[avail_gen] = actual_dispatch_tmp - pdb.set_trace() - return redisp_act_init, is_illegal, action - - ## TODO check that this part is not needed anymore!!!! - # get the maximum ranged of the generator possible with the chronics - # gen_min = np.maximum(self.gen_pmin, self.gen_activeprod_t - self.gen_max_ramp_down) - # gen_max = np.minimum(self.gen_pmax, self.gen_activeprod_t + self.gen_max_ramp_up) - - # below is the part emulating the active balancing part in a powergrid. It's not handled by operators - # but by an automaton. - - # this automated conrol only affect turned-on generators that are dispatchable - turned_on_gen = new_p > 0. - gen_redispatchable = self.gen_redispatchable & turned_on_gen - - # add the redispatching to the nex target production value - new_p += self.actual_dispatch - # the new_p vector now stores the new target value for the generators. - - # todo this might be the root of all evil!!!! - gen_min = np.maximum(self.gen_pmin - self.gen_activeprod_t, - self.gen_max_ramp_down) - gen_max = np.minimum(self.gen_pmax - self.gen_activeprod_t, self.gen_max_ramp_up) + if np.abs(np.sum(redisp_act)) >= 1e-6: + # make sure the redispatching action is zero sum + redisp_act, is_illegal, action = self._get_redisp_zero_sum(action, redisp_act, new_p) + if is_illegal: + return redisp_act, is_illegal, action - actual_ramp = new_p - self.gen_activeprod_t - if (np.any(new_p > self.gen_pmax) or np.any(new_p < self.gen_pmin) or \ - np.any(actual_ramp > self.gen_max_ramp_up) or np.any(actual_ramp < -self.gen_max_ramp_down)) and \ - np.any(self.gen_activeprod_t != 0.): - # i am in a case where the target redispatching is not possible, due to the new values - # i need to come up with a solution to fix that - # note that the condition "np.any(self.gen_activeprod_t != 0.)" is added because at the first time - # step there is no need to check all that. - # but take into account pmin and pmax - curtail_generation = 1. * new_p - mask_min = (new_p < gen_min + self._epsilon_poly) & gen_redispatchable - curtail_generation[mask_min] = gen_min[mask_min] + self._epsilon_poly - mask_max = (new_p > gen_max - self._epsilon_poly) & gen_redispatchable - curtail_generation[mask_max] = gen_max[mask_max] - self._epsilon_poly - - # modify the implemented redispatching to take into account this "curtailement" due to physical limitation - diff_th_imp = new_p - curtail_generation - self.actual_dispatch[mask_min] += diff_th_imp[mask_min] - self.actual_dispatch[mask_max] -= diff_th_imp[mask_max] - - # make sure the new redispatching sum to 0 - t_dispatch = 0. - try: - t_dispatch = self._get_t(self.actual_dispatch[gen_redispatchable], - pmin=np.sum(gen_min), - pmax=np.sum(gen_max), - total_dispatch=-np.sum(self.actual_dispatch[gen_redispatchable])) - except Exception as e: - # TODO check that it canont happen if the chronics are correct. - print("epic fail in redispatching with error {}".format(e)) - pass - - self.actual_dispatch[gen_redispatchable] = self._get_poly(t=t_dispatch, - pmax=gen_max[gen_redispatchable], - pmin=gen_min[gen_redispatchable], - tmp_p=self.actual_dispatch[gen_redispatchable]) + # and now compute the actual dispatch that is consistent with pmin, pmax, ramp min, ramp max + # this emulates the "frequency control" that is automatic. + self._compute_actual_dispatch(new_p) return redisp_act_init, is_illegal, action @@ -763,7 +800,7 @@ def _handle_updown_times(self, action, gen_up_before, redisp_act): action = self.helper_action_player({}) return is_illegal, action else: - self.gen_downtime[gen_connected_this_timestep] = -1 + self.gen_downtime[gen_connected_this_timestep] = 0 self.gen_uptime[gen_connected_this_timestep] = 1 self.gen_uptime[gen_still_connected] += 1 self.gen_downtime[gen_still_disconnected] += 1 @@ -823,14 +860,15 @@ def step(self, action): # get the modification of generator active setpoint from the environment self.env_modification = self._update_actions() - # remember generator that were "up" before the action - gen_up_before = self.gen_activeprod_t > 0. + if self.redispatching_unit_commitment_availble: + # remember generator that were "up" before the action + gen_up_before = self.gen_activeprod_t > 0. - # compute the redispatching and the new productions active setpoint - redisp_act, is_illegal_redisp, action = self._simulate_automaton_redispatching(action) + # compute the redispatching and the new productions active setpoint + redisp_act, is_illegal_redisp, action = self._simulate_automaton_redispatching(action) - # check the validity of min downtime and max uptime - is_illegal_reco, action = self._handle_updown_times(action, gen_up_before, redisp_act) + # check the validity of min downtime and max uptime + is_illegal_reco, action = self._handle_updown_times(action, gen_up_before, redisp_act) try: self.backend.apply_action(action) @@ -838,7 +876,9 @@ def step(self, action): # action has not been implemented on the powergrid because it's ambiguous, it's equivalent to # "do nothing" is_ambiguous = True - action._redispatch = 1. * redisp_act + + if self.redispatching_unit_commitment_availble: + action._redispatch = 1. * redisp_act # now get the new generator voltage setpoint # TODO "automaton" to do that! diff --git a/grid2op/Observation.py b/grid2op/Observation.py index 72eae3731..4376da974 100644 --- a/grid2op/Observation.py +++ b/grid2op/Observation.py @@ -68,7 +68,7 @@ def forecasts(self): return [] -class ObsEnv(object): +class ObsEnv(GridObjects): """ This class is an 'Emulator' of a :class:`grid2op.Environment` used to be able to 'simulate' forecasted grid states. It should not be used outside of an :class:`grid2op.Observation` instance, or one of its derivative. @@ -78,6 +78,9 @@ class ObsEnv(object): This class is reserved for internal use. Do not attempt to do anything with it. """ def __init__(self, backend_instanciated, parameters, reward_helper, obsClass, action_helper): + GridObjects.__init__(self) + self.init_grid(backend_instanciated) + self.timestep_overflow = None # self.action_helper = action_helper self.hard_overflow_threshold = parameters.HARD_OVERFLOW_THRESHOLD @@ -99,13 +102,20 @@ def __init__(self, backend_instanciated, parameters, reward_helper, obsClass, ac self.chronics_handler = ObsCH() - self.times_before_line_status_actionable = np.zeros(shape=(self.backend.n_line,), dtype=np.int) - self.times_before_topology_actionable = np.zeros(shape=(self.backend.n_sub,), dtype=np.int) - self.time_remaining_before_line_reconnection = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self.times_before_line_status_actionable = np.zeros(shape=(self.n_line,), dtype=np.int) + self.times_before_topology_actionable = np.zeros(shape=(self.n_sub,), dtype=np.int) + self.time_remaining_before_line_reconnection = np.zeros(shape=(self.n_line,), dtype=np.int) # TODO handle that in forecast! - self.time_next_maintenance = np.zeros(shape=(self.backend.n_line,), dtype=np.int) - 1 - self.duration_next_maintenance = np.zeros(shape=(self.backend.n_line,), dtype=np.int) + self.time_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) - 1 + self.duration_next_maintenance = np.zeros(shape=(self.n_line,), dtype=np.int) + + # TOD handle that too !!! + self.target_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.actual_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) + self.gen_uptime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_downtime = np.full(shape=self.n_gen, dtype=np.int, fill_value=0) + self.gen_activeprod_t = np.zeros(self.n_gen, dtype=np.float) def copy(self): """ diff --git a/grid2op/Settings_5busExample.py b/grid2op/Settings_5busExample.py index 8de346054..a69b052a6 100644 --- a/grid2op/Settings_5busExample.py +++ b/grid2op/Settings_5busExample.py @@ -9,5 +9,5 @@ # the reference powergrid was different than the default case14 of the litterature. EXAMPLE_CASEFILE = os.path.abspath(os.path.join(pkg_resources.resource_filename(__name__, "data"), - "test_PandaPower", "5bus_example.json")) -EXAMPLE_CHRONICSPATH = os.path.join(pkg_resources.resource_filename(__name__, "data"), "chronics_5bus_example") + "5bus_example", "5bus_example.json")) +EXAMPLE_CHRONICSPATH = os.path.join(pkg_resources.resource_filename(__name__, "data"), "5bus_example", "chronics") diff --git a/grid2op/Space.py b/grid2op/Space.py index 917c4231f..e19662112 100644 --- a/grid2op/Space.py +++ b/grid2op/Space.py @@ -334,6 +334,12 @@ def __init__(self): # for redispatching / unit commitment TODO = "TODO COMPLETE THAT BELLOW!!! AND UPDATE THE init methods" + self._li_attr_disp = ["gen_type", "gen_pmin", "gen_pmax", "gen_redispatchable", "gen_max_ramp_up", + "gen_max_ramp_down", "gen_min_uptime", "gen_min_downtime", "gen_cost_per_MW", + "gen_startup_cost", "gen_shutdown_cost"] + + self._type_attr_disp = [str, float, float, bool, float, float, int, int, float, float, float] + self.gen_type = None self.gen_pmin = None self.gen_pmax = None @@ -1209,6 +1215,87 @@ def get_loads_id(self, sub_id): return res + def to_dict(self): + """ + Convert the object as a dictionnary. + Note that unless this method is overidden, a call to it will only output the + Returns + ------- + + """ + res = {} + save_to_dict(res, self, "name_gen", lambda li: [str(el) for el in li]) + save_to_dict(res, self, "name_load", lambda li: [str(el) for el in li]) + save_to_dict(res, self, "name_line", lambda li: [str(el) for el in li]) + save_to_dict(res, self, "name_sub", lambda li: [str(el) for el in li]) + + save_to_dict(res, self, "sub_info", lambda li: [int(el) for el in li]) + + save_to_dict(res, self, "load_to_subid", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "gen_to_subid", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_or_to_subid", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_ex_to_subid", lambda li: [int(el) for el in li]) + + save_to_dict(res, self, "load_to_sub_pos", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "gen_to_sub_pos", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_or_to_sub_pos", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_ex_to_sub_pos", lambda li: [int(el) for el in li]) + + save_to_dict(res, self, "load_pos_topo_vect", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "gen_pos_topo_vect", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_or_pos_topo_vect", lambda li: [int(el) for el in li]) + save_to_dict(res, self, "line_ex_pos_topo_vect", lambda li: [int(el) for el in li]) + + # redispatching + if self.redispatching_unit_commitment_availble: + for nm_attr, type_attr in zip(self._li_attr_disp, self._type_attr_disp): + save_to_dict(res, self, nm_attr, lambda li: [type_attr(el) for el in li]) + else: + for nm_attr in self._li_attr_disp: + res[nm_attr] = None + return res + + @staticmethod + def from_dict(dict_): + res = GridObjects() + res.name_gen = extract_from_dict(dict_, "name_gen", lambda x: np.array(x).astype(str)) + res.name_load = extract_from_dict(dict_, "name_load", lambda x: np.array(x).astype(str)) + res.name_line = extract_from_dict(dict_, "name_line", lambda x: np.array(x).astype(str)) + res.name_sub = extract_from_dict(dict_, "name_sub", lambda x: np.array(x).astype(str)) + + res.sub_info = extract_from_dict(dict_, "sub_info", lambda x: np.array(x).astype(np.int)) + res.load_to_subid = extract_from_dict(dict_, "load_to_subid", lambda x: np.array(x).astype(np.int)) + res.gen_to_subid = extract_from_dict(dict_, "gen_to_subid", lambda x: np.array(x).astype(np.int)) + res.line_or_to_subid = extract_from_dict(dict_, "line_or_to_subid", lambda x: np.array(x).astype(np.int)) + res.line_ex_to_subid = extract_from_dict(dict_, "line_ex_to_subid", lambda x: np.array(x).astype(np.int)) + + res.load_to_sub_pos = extract_from_dict(dict_, "load_to_sub_pos", lambda x: np.array(x).astype(np.int)) + res.gen_to_sub_pos = extract_from_dict(dict_, "gen_to_sub_pos", lambda x: np.array(x).astype(np.int)) + res.line_or_to_sub_pos = extract_from_dict(dict_, "line_or_to_sub_pos", lambda x: np.array(x).astype(np.int)) + res.line_ex_to_sub_pos = extract_from_dict(dict_, "line_ex_to_sub_pos", lambda x: np.array(x).astype(np.int)) + + res.load_pos_topo_vect = extract_from_dict(dict_, "load_pos_topo_vect", lambda x: np.array(x).astype(np.int)) + res.gen_pos_topo_vect = extract_from_dict(dict_, "gen_pos_topo_vect", lambda x: np.array(x).astype(np.int)) + res.line_or_pos_topo_vect = extract_from_dict(dict_, "line_or_pos_topo_vect", lambda x: np.array(x).astype(np.int)) + res.line_ex_pos_topo_vect = extract_from_dict(dict_, "line_ex_pos_topo_vect", lambda x: np.array(x).astype(np.int)) + + res.n_gen = len(res.name_gen) + res.n_load = len(res.name_load) + res.n_line = len(res.name_line) + res.n_sub = len(res.name_sub) + res.dim_topo = np.sum(res.sub_info) + + if dict_["gen_type"] is None: + res.redispatching_unit_commitment_availble = False + # and no need to make anything else, because everything is already initialized at None + else: + res.redispatching_unit_commitment_availble = True + type_attr_disp = [str, np.float, np.float, np.bool, np.float, np.float, + np.int, np.int, np.float, np.float, np.float] + for nm_attr, type_attr in zip(res._li_attr_disp, type_attr_disp): + res.__dict__[nm_attr] = extract_from_dict(dict_, nm_attr, lambda x: np.array(x).astype(type_attr)) + return res + class SerializableSpace(GridObjects): """ @@ -1314,26 +1401,7 @@ def from_dict(dict_): with open(path, "r", encoding="utf-8") as f: dict_ = json.load(fp=f) - name_prod = extract_from_dict(dict_, "name_gen", lambda x: np.array(x).astype(str)) - name_load = extract_from_dict(dict_, "name_load", lambda x: np.array(x).astype(str)) - name_line = extract_from_dict(dict_, "name_line", lambda x: np.array(x).astype(str)) - name_sub = extract_from_dict(dict_, "name_sub", lambda x: np.array(x).astype(str)) - - sub_info = extract_from_dict(dict_, "sub_info", lambda x: np.array(x).astype(np.int)) - load_to_subid = extract_from_dict(dict_, "load_to_subid", lambda x: np.array(x).astype(np.int)) - gen_to_subid = extract_from_dict(dict_, "gen_to_subid", lambda x: np.array(x).astype(np.int)) - line_or_to_subid = extract_from_dict(dict_, "line_or_to_subid", lambda x: np.array(x).astype(np.int)) - line_ex_to_subid = extract_from_dict(dict_, "line_ex_to_subid", lambda x: np.array(x).astype(np.int)) - - load_to_sub_pos = extract_from_dict(dict_, "load_to_sub_pos", lambda x: np.array(x).astype(np.int)) - gen_to_sub_pos = extract_from_dict(dict_, "gen_to_sub_pos", lambda x: np.array(x).astype(np.int)) - line_or_to_sub_pos = extract_from_dict(dict_, "line_or_to_sub_pos", lambda x: np.array(x).astype(np.int)) - line_ex_to_sub_pos = extract_from_dict(dict_, "line_ex_to_sub_pos", lambda x: np.array(x).astype(np.int)) - - load_pos_topo_vect = extract_from_dict(dict_, "load_pos_topo_vect", lambda x: np.array(x).astype(np.int)) - gen_pos_topo_vect = extract_from_dict(dict_, "gen_pos_topo_vect", lambda x: np.array(x).astype(np.int)) - line_or_pos_topo_vect = extract_from_dict(dict_, "line_or_pos_topo_vect", lambda x: np.array(x).astype(np.int)) - line_ex_pos_topo_vect = extract_from_dict(dict_, "line_ex_pos_topo_vect", lambda x: np.array(x).astype(np.int)) + gridobj = GridObjects.from_dict(dict_) actionClass_str = extract_from_dict(dict_, "subtype", str) actionClass_li = actionClass_str.split('.') @@ -1372,12 +1440,6 @@ def from_dict(dict_): msg_err_ = msg_err_.format(actionClass_str) raise Grid2OpException(msg_err_) - gridobj = GridObjects() - gridobj.init_grid_vect(name_prod, name_load, name_line, name_sub, sub_info, - load_to_subid, gen_to_subid, line_or_to_subid, line_ex_to_subid, - load_to_sub_pos, gen_to_sub_pos, line_or_to_sub_pos, line_ex_to_sub_pos, - load_pos_topo_vect, gen_pos_topo_vect, line_or_pos_topo_vect, - line_ex_pos_topo_vect) res = SerializableSpace(gridobj=gridobj, subtype=subtype) return res @@ -1392,26 +1454,7 @@ def to_dict(self): A dictionnary representing this object content. It can be loaded back with :func:`SerializableObservationSpace.from_dict` """ - res = {} - save_to_dict(res, self, "name_gen", lambda li: [str(el) for el in li]) - save_to_dict(res, self, "name_load", lambda li: [str(el) for el in li]) - save_to_dict(res, self, "name_line", lambda li: [str(el) for el in li]) - save_to_dict(res, self, "name_sub", lambda li: [str(el) for el in li]) - save_to_dict(res, self, "sub_info", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "load_to_subid", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "gen_to_subid", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_or_to_subid", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_ex_to_subid", lambda li: [int(el) for el in li]) - - save_to_dict(res, self, "load_to_sub_pos", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "gen_to_sub_pos", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_or_to_sub_pos", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_ex_to_sub_pos", lambda li: [int(el) for el in li]) - - save_to_dict(res, self, "load_pos_topo_vect", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "gen_pos_topo_vect", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_or_pos_topo_vect", lambda li: [int(el) for el in li]) - save_to_dict(res, self, "line_ex_pos_topo_vect", lambda li: [int(el) for el in li]) + res = super().to_dict() save_to_dict(res, self, "subtype", lambda x: re.sub("()", "", "{}".format(x))) @@ -1427,13 +1470,13 @@ def size(self): """ return self.n - def from_vect(self, act): + def from_vect(self, obj_as_vect): """ Convert an action, represented as a vector to a valid :class:`Action` instance Parameters ---------- - act: ``numpy.ndarray`` + obj_as_vect: ``numpy.ndarray`` A object living in a space represented as a vector (typically an :class:`grid2op.Action.Action` or an :class:`grid2op.Observation.Observation` represented as a numpy vector) @@ -1445,5 +1488,5 @@ def from_vect(self, act): """ res = copy.deepcopy(self.template_obj) - res.from_vect(act) + res.from_vect(obj_as_vect) return res diff --git a/grid2op/data/test_PandaPower/5bus_example.json b/grid2op/data/5bus_example/5bus_example.json similarity index 100% rename from grid2op/data/test_PandaPower/5bus_example.json rename to grid2op/data/5bus_example/5bus_example.json diff --git a/grid2op/data/chronics_5bus_example/0/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/0/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/0/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/0/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/0/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/0/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/0/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/0/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/0/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/0/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/0/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/0/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/0/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/0/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/0/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/1/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/1/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/1/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/1/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/1/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/1/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/1/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/1/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/1/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/1/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/1/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/1/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/1/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/1/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/10/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/10/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/10/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/10/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/10/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/10/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/10/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/10/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/10/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/10/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/10/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/10/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/10/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/10/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/11/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/11/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/11/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/11/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/11/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/11/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/11/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/11/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/11/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/11/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/11/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/11/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/11/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/11/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/12/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/12/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/12/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/12/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/12/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/12/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/12/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/12/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/12/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/12/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/12/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/12/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/12/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/12/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/13/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/13/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/13/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/13/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/13/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/13/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/13/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/13/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/13/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/13/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/13/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/13/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/13/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/13/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/14/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/14/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/14/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/14/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/14/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/14/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/14/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/14/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/14/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/14/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/14/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/14/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/14/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/14/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/15/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/15/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/15/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/15/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/15/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/15/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/15/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/15/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/15/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/15/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/15/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/15/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/15/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/15/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/16/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/16/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/16/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/16/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/16/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/16/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/16/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/16/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/16/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/16/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/16/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/16/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/16/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/16/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/17/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/17/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/17/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/17/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/17/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/17/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/17/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/17/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/17/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/17/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/17/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/17/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/17/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/17/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/18/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/18/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/18/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/18/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/18/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/18/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/18/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/18/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/18/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/18/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/18/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/18/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/18/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/18/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/19/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/19/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/19/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/19/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/19/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/19/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/19/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/19/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/19/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/19/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/19/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/19/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/19/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/19/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/2/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/2/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/2/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/2/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/2/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/2/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/2/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/2/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/2/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/2/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/2/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/2/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/2/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/2/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/3/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/3/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/3/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/3/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/3/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/3/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/3/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/3/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/3/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/3/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/3/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/3/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/3/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/3/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/4/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/4/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/4/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/4/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/4/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/4/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/4/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/4/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/4/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/4/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/4/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/4/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/4/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/4/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/5/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/5/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/5/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/5/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/5/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/5/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/5/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/5/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/5/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/5/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/5/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/5/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/5/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/5/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/6/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/6/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/6/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/6/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/6/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/6/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/6/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/6/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/6/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/6/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/6/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/6/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/6/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/6/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/7/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/7/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/7/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/7/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/7/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/7/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/7/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/7/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/7/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/7/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/7/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/7/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/7/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/7/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/8/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/8/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/8/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/8/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/8/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/8/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/8/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/8/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/8/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/8/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/8/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/8/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/8/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/8/prod_v_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/hazards.csv.bz2 b/grid2op/data/5bus_example/chronics/9/hazards.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/hazards.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/hazards.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/load_p.csv.bz2 b/grid2op/data/5bus_example/chronics/9/load_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/load_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/load_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/load_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/9/load_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/load_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/load_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/load_q.csv.bz2 b/grid2op/data/5bus_example/chronics/9/load_q.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/load_q.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/load_q.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/load_q_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/9/load_q_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/load_q_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/load_q_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/maintenance.csv.bz2 b/grid2op/data/5bus_example/chronics/9/maintenance.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/maintenance.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/maintenance.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/maintenance_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/9/maintenance_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/maintenance_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/maintenance_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/prod_p.csv.bz2 b/grid2op/data/5bus_example/chronics/9/prod_p.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/prod_p.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/prod_p.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/prod_p_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/9/prod_p_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/prod_p_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/prod_p_forecasted.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/prod_v.csv.bz2 b/grid2op/data/5bus_example/chronics/9/prod_v.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/prod_v.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/prod_v.csv.bz2 diff --git a/grid2op/data/chronics_5bus_example/9/prod_v_forecasted.csv.bz2 b/grid2op/data/5bus_example/chronics/9/prod_v_forecasted.csv.bz2 similarity index 100% rename from grid2op/data/chronics_5bus_example/9/prod_v_forecasted.csv.bz2 rename to grid2op/data/5bus_example/chronics/9/prod_v_forecasted.csv.bz2 diff --git a/grid2op/tests/test_Action.py b/grid2op/tests/test_Action.py index 1a0a257ed..de73b345c 100644 --- a/grid2op/tests/test_Action.py +++ b/grid2op/tests/test_Action.py @@ -55,32 +55,35 @@ def setUp(self): self.helper_action = HelperAction(self.gridobj, game_rules=self.game_rules) - self.helper_action_env = HelperAction(self.gridobj, game_rules=self.game_rules, actionClass=Action) self.res = {'name_gen': ['gen_0', 'gen_1', 'gen_2', 'gen_3', 'gen_4'], - 'name_load': ['load_0', 'load_1', 'load_2', 'load_3', 'load_4', 'load_5', 'load_6', - 'load_7', 'load_8', 'load_9', 'load_10'], - 'name_line': ['line_0', 'line_1', 'line_2', 'line_3', 'line_4', 'line_5', 'line_6', 'line_7', - 'line_8', 'line_9', 'line_10', 'line_11', 'line_12', 'line_13', 'line_14', 'line_15', - 'line_16', 'line_17', 'line_18', 'line_19'], - 'name_sub': ["sub_0", "sub_1", "sub_2", "sub_3", "sub_4", "sub_5", "sub_6", "sub_7", "sub_8", "sub_9", - "sub_10", "sub_11", "sub_12", "sub_13"], - 'sub_info': [3, 6, 4, 6, 5, 6, 3, 2, 5, 3, 3, 3, 4, 3], - 'load_to_subid': [1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13], - 'gen_to_subid': [0, 1, 2, 5, 7], - 'line_or_to_subid': [0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 6, 8, 8, 9, 11, 12], - 'line_ex_to_subid': [1, 4, 2, 3, 4, 3, 4, 6, 8, 5, 10, 11, 12, 7, 8, 9, 13, 10, 12, 13], - 'load_to_sub_pos': [4, 2, 5, 4, 4, 4, 1, 1, 1, 2, 1], 'gen_to_sub_pos': [2, 5, 3, 5, 1], - 'line_or_to_sub_pos': [0, 1, 1, 2, 3, 1, 2, 3, 4, 3, 1, 2, 3, 1, 2, 2, 3, 0, 0, 1], - 'line_ex_to_sub_pos': [0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 3, 0, 1, 2, 2, 0, 0, 0], - 'load_pos_topo_vect': [7, 11, 18, 23, 28, 39, 41, 44, 47, 51, 54], - 'gen_pos_topo_vect': [2, 8, 12, 29, 34], - 'line_or_pos_topo_vect': [0, 1, 4, 5, 6, 10, 15, 16, 17, 22, 25, 26, 27, 31, 32, 37, 38, 40, 46, 50], - 'line_ex_pos_topo_vect': [3, 19, 9, 13, 20, 14, 21, 30, 35, 24, 45, 48, 52, 33, 36, 42, 55, 43, 49, 53], - 'subtype': 'Action.Action'} + 'name_load': ['load_0', 'load_1', 'load_2', 'load_3', 'load_4', 'load_5', 'load_6', + 'load_7', 'load_8', 'load_9', 'load_10'], + 'name_line': ['line_0', 'line_1', 'line_2', 'line_3', 'line_4', 'line_5', 'line_6', 'line_7', + 'line_8', 'line_9', 'line_10', 'line_11', 'line_12', 'line_13', 'line_14', + 'line_15', 'line_16', 'line_17', 'line_18', 'line_19'], + 'name_sub': ['sub_0', 'sub_1', 'sub_2', 'sub_3', 'sub_4', 'sub_5', 'sub_6', 'sub_7', 'sub_8', + 'sub_9', 'sub_10', 'sub_11', 'sub_12', 'sub_13'], + 'sub_info': [3, 6, 4, 6, 5, 6, 3, 2, 5, 3, 3, 3, 4, 3], + 'load_to_subid': [1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13], + 'gen_to_subid': [0, 1, 2, 5, 7], + 'line_or_to_subid': [0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 6, 8, 8, 9, 11, 12], + 'line_ex_to_subid': [1, 4, 2, 3, 4, 3, 4, 6, 8, 5, 10, 11, 12, 7, 8, 9, 13, 10, 12, 13], + 'load_to_sub_pos': [4, 2, 5, 4, 4, 4, 1, 1, 1, 2, 1], + 'gen_to_sub_pos': [2, 5, 3, 5, 1], + 'line_or_to_sub_pos': [0, 1, 1, 2, 3, 1, 2, 3, 4, 3, 1, 2, 3, 1, 2, 2, 3, 0, 0, 1], + 'line_ex_to_sub_pos': [0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 3, 0, 1, 2, 2, 0, 0, 0], + 'load_pos_topo_vect': [7, 11, 18, 23, 28, 39, 41, 44, 47, 51, 54], + 'gen_pos_topo_vect': [2, 8, 12, 29, 34], + 'line_or_pos_topo_vect': [0, 1, 4, 5, 6, 10, 15, 16, 17, 22, 25, 26, 27, 31, 32, 37, 38, 40, 46, 50], + 'line_ex_pos_topo_vect': [3, 19, 9, 13, 20, 14, 21, 30, 35, 24, 45, 48, 52, 33, 36, 42, 55, 43, 49, 53], + 'gen_type': None, 'gen_pmin': None, 'gen_pmax': None, 'gen_redispatchable': None, + 'gen_max_ramp_up': None, 'gen_max_ramp_down': None, 'gen_min_uptime': None, 'gen_min_downtime': None, + 'gen_cost_per_MW': None, 'gen_startup_cost': None, 'gen_shutdown_cost': None, + 'subtype': 'Action.Action'} self.size_act = 229 diff --git a/grid2op/tests/test_Environment.py b/grid2op/tests/test_Environment.py index 1406fa67c..33fa1e1a7 100644 --- a/grid2op/tests/test_Environment.py +++ b/grid2op/tests/test_Environment.py @@ -80,7 +80,7 @@ def compare_vect(self, pred, true): def test_step_doesnt_change_action(self): # TODO THIS TEST - act = self.env.acttion_space() + act = self.env.action_space() act_init = copy.deepcopy(act) res = self.env.step(act) assert act == act_init diff --git a/grid2op/tests/test_Observation.py b/grid2op/tests/test_Observation.py index 3b74d54ff..6ef5e6a12 100644 --- a/grid2op/tests/test_Observation.py +++ b/grid2op/tests/test_Observation.py @@ -117,7 +117,19 @@ def setUp(self): 32], 'line_ex_pos_topo_vect': [3, 19, 40, 53, 43, 49, 54, 9, 13, 20, 14, 21, 44, 47, 51, 30, 37, 27, 33, 38], + 'gen_type': ['solar', 'nuclear', 'nuclear', 'nuclear', 'thermal'], + 'gen_pmin': [0.0, 0.0, 0.0, 0.0, 0.0], + 'gen_pmax': [40.0, 60.0, 80.0, 100.0, 170.0], + 'gen_redispatchable': [False, True, True, True, True], + 'gen_max_ramp_up': [40.0, 60.0, 80.0, 100.0, 170.0], + 'gen_max_ramp_down': [40.0, 60.0, 80.0, 100.0, 170.0], + 'gen_min_uptime': [0, 0, 0, 0, 0], + 'gen_min_downtime': [0, 1, 0, 0, 0], + 'gen_cost_per_MW': [0.0, 0.0, 0.0, 0.0, 10.0], + 'gen_startup_cost': [0.0, 0.0, 0.0, 0.0, 0.0], + 'gen_shutdown_cost': [0.0, 0.0, 0.0, 0.0, 0.0], 'subtype': 'Observation.CompleteObservation'} + self.dtypes = np.array([dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), @@ -126,22 +138,24 @@ def setUp(self): dtype('float64'), dtype('float64'), dtype('float64'), dtype('float64'), dtype('bool'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), dtype('int64'), - dtype('int64'), dtype('int64')], dtype=object) + dtype('int64'), dtype('int64'), dtype('float64'), dtype('float64')], + dtype=object) self.shapes = np.array([ 1, 1, 1, 1, 1, 1, 5, 5, 5, 11, 11, 11, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 56, 20, 20, 14, 20, 20, 20]) - self.size_obs = 4242 # TODO + 20, 20, 20, 20, 20, 20, 56, 20, 20, 14, 20, 20, 20, + 5, 5]) + self.size_obs = 454 def test_sum_shape_equal_size(self): obs = self.env.helper_observation(self.env) assert obs.size() == np.sum(obs.shape()) def test_size(self): - action = self.env.helper_observation(self.env) - action.size() + obs = self.env.helper_observation(self.env) + obs.size() def test_proper_size(self): - action = self.env.helper_observation(self.env) - assert action.size() == self.size_obs + obs = self.env.helper_observation(self.env) + assert obs.size() == self.size_obs def test_size_action_space(self): assert self.env.helper_observation.size() == self.size_obs diff --git a/setup.py b/setup.py index bb9d4b137..691705b33 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ include_package_data=True, # package_data={"": ["./data/chronics/*", "./data/test_multi_chronics/1/*", "./data/test_multi_chronics/2/*", # "./data/test_multi_chronics/chronics/*", "./data/test_PandaPower/*", - # "data/chronics_5bus_example"]}, + # "data/chronics"]}, install_requires=["numpy", "pandas", "pandapower"], extras_require=extras, zip_safe=False, From 84e6593b9fc0d8b9501d3edc04de93790e7ab562 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Thu, 16 Jan 2020 17:56:30 +0100 Subject: [PATCH 07/14] improve the documentation of Action: spell check and examples added --- CHANGELOG.rst | 1 + grid2op/Action.py | 751 +++++++++++++++++++++++++++++----------------- 2 files changed, 476 insertions(+), 276 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1ab93d17..bdd921586 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,7 @@ Change Log ============= [0.5.0] - 2020-01-xx -------------------- +- [UPDATED] more complete documentation of the Action class (with some examples) - [ADDED] more complete documentation of the representation of the powergrid (see documentation of `Space`) - [UPDATED] more unit test for observations diff --git a/grid2op/Action.py b/grid2op/Action.py index 2538ff6f0..c662e197e 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -15,7 +15,8 @@ - the configuration at substations eg setting different objects to different buses for example The Action class is abstract. You can implement it the way you want. If you decide to extend it, make sure -that the :class:`grid2op.Backend` class will be able to understand it. If you don't, your extension will not affect the underlying powergrid. Indeed a :class:`grid2op.Backend` will call the :func:`Action.__call__` method and should +that the :class:`grid2op.Backend` class will be able to understand it. If you don't, your extension will not affect the +underlying powergrid. Indeed a :class:`grid2op.Backend` will call the :func:`Action.__call__` method and should understands its return type. In this module we derived two action class: @@ -67,151 +68,161 @@ class Action(GridObjects): """ -This is a base class for each :class:`Action` objects. -As stated above, an action represents conveniently the modifications that will affect a powergrid. + This is a base class for each :class:`Action` objects. + As stated above, an action represents conveniently the modifications that will affect a powergrid. -It is not recommended to instantiate an action from scratch. The recommended way to get an action is either by -modifying an existing one using the method :func:`Action.update` or to call and :class:`HelperAction` object that -has been properly set up by an :class:`grid2op.Environment`. + It is not recommended to instantiate an action from scratch. The recommended way to get an action is either by + modifying an existing one using the method :func:`Action.update` or to call and :class:`HelperAction` object that + has been properly set up by an :class:`grid2op.Environment`. -Action can be fully converted to and back from a numpy array with a **fixed** size. + Action can be fully converted to and back from a numpy array with a **fixed** size. -An action can modify the grid in multiple ways. -It can change : + An action can modify the grid in multiple ways. + It can change : -- the production and voltage setpoint of the generator units -- the amount of power consumed (for both active and reactive part) for load -- disconnect powerlines -- change the topology of the _grid. + - the production and voltage setpoint of the generator units + - the amount of power consumed (for both active and reactive part) for load + - disconnect powerlines + - change the topology of the _grid. -To be valid, an action should be convertible to a tuple of 5 elements: + To be valid, an action should be convertible to a tuple of 5 elements: -- the first element is the "injections" vector: representing the way generator units and loads are modified - - It is, in turn, a dictionary with the following keys (optional) + - the first element is the "injections" vector: representing the way generator units and loads are modified + - It is, in turn, a dictionary with the following keys (optional) - - "load_p" a vector of the same size of the load, giving the modification of the loads active consumption - - "load_q" a vector of the same size of the load, giving the modification of the loads reactive consumption - - "prod_p" a vector of the same size of the generators, giving the modification of the productions active setpoint production - - "prod_v" a vector of the same size of the generators, giving the modification of the productions voltage setpoint + - "load_p" a vector of the same size of the load, giving the modification of the loads active consumption + - "load_q" a vector of the same size of the load, giving the modification of the loads reactive consumption + - "prod_p" a vector of the same size of the generators, giving the modification of the productions active + setpoint production + - "prod_v" a vector of the same size of the generators, giving the modification of the productions voltage + setpoint -- the second element is made of force line status. It is made of a vector of size :attr:`Action._n_lines` - (the number of lines in the powergrid) and is interpreted as: + - the second element is made of force line status. It is made of a vector of size :attr:`Action._n_lines` + (the number of lines in the powergrid) and is interpreted as: - - -1 force line disconnection - - +1 force line reconnection - - 0 do nothing to this line + - -1 force line disconnection + - +1 force line reconnection + - 0 do nothing to this line -- the third element is the switch line status vector. It is made of a vector of size :attr:`Action._n_lines` and is - interpreted as: + - the third element is the switch line status vector. It is made of a vector of size :attr:`Action._n_lines` and is + interpreted as: - - True: change the line status - - False: don't do anything + - ``True``: change the line status + - ``False``: don't do anything -- the fourth element set the buses to which the object is connected. It's a vector of integers with the following - interpretation: + - the fourth element set the buses to which the object is connected. It's a vector of integers with the following + interpretation: - - 0 -> don't change - - 1 -> connect to bus 1 - - 2 -> connect to bus 2 - - -1 -> disconnect the object. + - 0 -> don't change + - 1 -> connect to bus 1 + - 2 -> connect to bus 2 + - -1 -> disconnect the object. -- the fifth element changes the buses to which the object is connected. It's a boolean vector interpreted as: - - False: nothing is done - - True: change the bus eg connect it to bus 1 if it was connected to bus 2 or connect it to bus 2 if it was - connected to bus 1. NB this is only active if the system has only 2 buses per substation (that's the case for - the L2RPN challenge). + - the fifth element changes the buses to which the object is connected. It's a boolean vector interpreted as: + - ``False``: nothing is done + - ``True``: change the bus eg connect it to bus 1 if it was connected to bus 2 or connect it to bus 2 if it was + connected to bus 1. NB this is only active if the system has only 2 buses per substation (that's the case for + the L2RPN challenge). -- the sixth element is a vector, representing the redispatching. Component of this vector is added to the - generators active setpoint value (if set) of the first elements. + - the sixth element is a vector, representing the redispatching. Component of this vector is added to the + generators active setpoint value (if set) of the first elements. -**NB** the difference between :attr:`Action._set_topo_vect` and :attr:`Action._change_bus_vect` is the following: + **NB** the difference between :attr:`Action._set_topo_vect` and :attr:`Action._change_bus_vect` is the following: - - If a component of :attr:`Action._set_topo_vect` is 1, then the object (load, generator or powerline) - will be moved to bus 1 of the substation to which it is connected. If it is already to bus 1 nothing will be done. If it's on another bus it will connect it to bus 1. It's disconnected, it will reconnect it and connect it to bus 1. - - If a component of :attr:`Action._change_bus_vect` is True, then the object will be moved from one bus to another. - If the object were on bus 1 - it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object were disconnected, then this does nothing. + - If a component of :attr:`Action._set_topo_vect` is 1, then the object (load, generator or powerline) + will be moved to bus 1 of the substation to which it is connected. If it is already to bus 1 nothing will be + done. + If it's on another bus it will connect it to bus 1. It's disconnected, it will reconnect it and connect it + to bus 1. + - If a component of :attr:`Action._change_bus_vect` is True, then the object will be moved from one bus to + another. + If the object were on bus 1 + it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object were + disconnected, + then this does nothing. -The conversion to the action into an understandable format by the backend is performed by the "update" method, -that takes into account a dictionary and is responsible to convert it into this format. -It is possible to overload this class as long as the overloaded :func:`Action.__call__` operator returns the -specified format, and the :func:`Action.__init__` method has the same signature. + The conversion to the action into an understandable format by the backend is performed by the "update" method, + that takes into account a dictionary and is responsible to convert it into this format. + It is possible to overload this class as long as the overloaded :func:`Action.__call__` operator returns the + specified format, and the :func:`Action.__init__` method has the same signature. -This format is then digested by the backend and the powergrid is modified accordingly. + This format is then digested by the backend and the powergrid is modified accordingly. -Attributes ----------- - -_set_line_status: :class:`numpy.ndarray`, dtype:int - For each powerline, it gives the effect of the action on the status of it. It should be understood as: - - - -1: disconnect the powerline - - 0: don't affect the powerline - - +1: reconnect the powerline - -_switch_line_status: :class:`numpy.ndarray`, dtype:bool - For each powerline, it informs whether the action will switch the status of a powerline of not. It should be - understood as followed: - - - ``False``: the action doesn't affect the powerline - - ``True``: the action affects the powerline. If it was connected, it will disconnect it. If it was - disconnected, it will reconnect it. - -_dict_inj: ``dict`` - Represents the modification of the injection (productions and loads) of the power _grid. This dictionary can - have the optional keys: - - - "load_p" to set the active load values (this is a numpy array with the same size as the number of load - in the power _grid with Nan: don't change anything, else set the value - - "load_q": same as above but for the load reactive values - - "prod_p": same as above but for the generator active setpoint values. It has the size corresponding - to the number of generators in the test case. - - "prod_v": same as above but set the voltage setpoint of generator units. - -_set_topo_vect: :class:`numpy.ndarray`, dtype:int - Similar to :attr:`Action._set_line_status` but instead of affecting the status of powerlines, it affects the - bus connectivity at a substation. It has the same size as the full topological vector (:attr:`Action._dim_topo`) - and for each element it should be understood as: - - - 0: nothing is changed for this element - - +1: this element is affected to bus 1 - - -1: this element is affected to bus 2 - -_change_bus_vect: :class:`numpy.ndarray`, dtype:bool - Similar to :attr:`Action._switch_line_status` but it affects the topology at substations instead of the status of - the powerline. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and each - component should mean: - - - ``False``: the object is not affected - - ``True``: the object will be moved to another bus. If it was on bus 1 it will be moved on bus 2, and if - it was on bus 2 it will be moved on bus 1. - -authorized_keys: :class:`set` - The set indicating which keys the actions can understand when calling :func:`Action.update` - -_subs_impacted: :class:`numpy.ndarray`, dtype:bool - This attributes is either not initialized (set to ``None``) or it tells, for each substation, if it is impacted - by the action (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``True``) or not - (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``False``) - -_lines_impacted: :class:`numpy.ndarray`, dtype:bool - This attributes is either not initialized (set to ``None``) or it tells, for each powerline, if it is impacted - by the action (in this case :attr:`Action._lines_impacted`\[line_id\] is ``True``) or not - (in this case :attr:`Action._subs_impacted`\[line_id\] is ``False``) + Attributes + ---------- -vars_action: ``list``, static - The authorized key that are processed by :func:`Action.__call__` to modify the injections - -vars_action_set: ``set``, static - The authorized key that is processed by :func:`Action.__call__` to modify the injections - -_redispatch: :class:`numpy.ndarray`, dtype:float - Amount of redispatching that this action will perform. Redispatching will increase the generator's active setpoint - value. This will be added to the value of the generators. The Environment will make sure that every physical - constraint is met. This means that the agent provides a setpoint, but there is no guarantee that the setpoint - will be achievable. Redispatching action is cumulative, this means that if at a given timestep you ask +10 MW - on a generator, and on another you ask +10 MW then the total setpoint for this generator that the environment - will try to implement is +20MW. + _set_line_status: :class:`numpy.ndarray`, dtype:int + For each powerline, it gives the effect of the action on the status of it. It should be understood as: + + - -1: disconnect the powerline + - 0: don't affect the powerline + - +1: reconnect the powerline + + _switch_line_status: :class:`numpy.ndarray`, dtype:bool + For each powerline, it informs whether the action will switch the status of a powerline of not. It should be + understood as followed: + + - ``False``: the action doesn't affect the powerline + - ``True``: the action affects the powerline. If it was connected, it will disconnect it. If it was + disconnected, it will reconnect it. + + _dict_inj: ``dict`` + Represents the modification of the injection (productions and loads) of the power _grid. This dictionary can + have the optional keys: + + - "load_p" to set the active load values (this is a numpy array with the same size as the number of load + in the power _grid with Nan: don't change anything, else set the value + - "load_q": same as above but for the load reactive values + - "prod_p": same as above but for the generator active setpoint values. It has the size corresponding + to the number of generators in the test case. + - "prod_v": same as above but set the voltage setpoint of generator units. + + _set_topo_vect: :class:`numpy.ndarray`, dtype:int + Similar to :attr:`Action._set_line_status` but instead of affecting the status of powerlines, it affects the + bus connectivity at a substation. It has the same size as the full topological vector (:attr:`Action._dim_topo`) + and for each element it should be understood as: + + - 0: nothing is changed for this element + - +1: this element is affected to bus 1 + - -1: this element is affected to bus 2 + + _change_bus_vect: :class:`numpy.ndarray`, dtype:bool + Similar to :attr:`Action._switch_line_status` but it affects the topology at substations instead of the status + of + the powerline. It has the same size as the full topological vector (:attr:`Action._dim_topo`) and each + component should mean: + + - ``False``: the object is not affected + - ``True``: the object will be moved to another bus. If it was on bus 1 it will be moved on bus 2, and if + it was on bus 2 it will be moved on bus 1. + + authorized_keys: :class:`set` + The set indicating which keys the actions can understand when calling :func:`Action.update` + + _subs_impacted: :class:`numpy.ndarray`, dtype:bool + This attributes is either not initialized (set to ``None``) or it tells, for each substation, if it is impacted + by the action (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``True``) or not + (in this case :attr:`Action._subs_impacted`\[sub_id\] is ``False``) + + _lines_impacted: :class:`numpy.ndarray`, dtype:bool + This attributes is either not initialized (set to ``None``) or it tells, for each powerline, if it is impacted + by the action (in this case :attr:`Action._lines_impacted`\[line_id\] is ``True``) or not + (in this case :attr:`Action._subs_impacted`\[line_id\] is ``False``) + + vars_action: ``list``, static + The authorized key that are processed by :func:`Action.__call__` to modify the injections + + vars_action_set: ``set``, static + The authorized key that is processed by :func:`Action.__call__` to modify the injections + + _redispatch: :class:`numpy.ndarray`, dtype:float + Amount of redispatching that this action will perform. Redispatching will increase the generator's active + setpoint + value. This will be added to the value of the generators. The Environment will make sure that every physical + constraint is met. This means that the agent provides a setpoint, but there is no guarantee that the setpoint + will be achievable. Redispatching action is cumulative, this means that if at a given timestep you ask +10 MW + on a generator, and on another you ask +10 MW then the total setpoint for this generator that the environment + will try to implement is +20MW. """ @@ -297,28 +308,32 @@ def check_space_legit(self): def get_set_line_status_vect(self): """ - Computes and return a vector that can be used in the "set_status" keyword if building an :class:`Action`. + Computes and returns a vector that can be used in the :func:`Action.__call__` with the keyword + "set_status" if building an :class:`Action`. - **NB** this vector is not the internal vector of this action, but corresponds to "do nothing" action. + **NB** this vector is not the internal vector of this action but corresponds to "do nothing" action. Returns ------- res: :class:`numpy.array`, dtype:np.int - A vector that doesn't affect the grid, but can be used in "set_status" + A vector that doesn't affect the grid, but can be used in :func:`Action.__call__` with the keyword + "set_status" if building an :class:`Action`. """ return np.full(shape=self.n_line, fill_value=0, dtype=np.int) def get_change_line_status_vect(self): """ - Computes and return a vector that can be used in the "change_status" keyword if building an :class:`Action` + Computes and returns a vector that can be used in the :func:`Action.__call__` with the keyword + "set_status" if building an :class:`Action`. - **NB** this vector is not the internal vector of this action, but corresponds to "do nothing" action. + **NB** this vector is not the internal vector of this action but corresponds to "do nothing" action. Returns ------- res: :class:`numpy.array`, dtype:np.bool - A vector that doesn't affect the grid, but can be used in "change_status" + A vector that doesn't affect the grid, but can be used in :func:`Action.__call__` with the keyword + "set_status" if building an :class:`Action`. """ return np.full(shape=self.n_line, fill_value=False, dtype=np.bool) @@ -327,22 +342,22 @@ def __eq__(self, other) -> bool: """ Test the equality of two actions. - 2 actions are said to be identical if the have the same impact on the powergrid. This is unrelated to their + 2 actions are said to be identical if they have the same impact on the powergrid. This is unrelated to their respective class. For example, if an Action is of class :class:`Action` and doesn't act on the injection, it - can be equal to a an Action of derived class :class:`TopologyAction` (if the topological modification are the + can be equal to an Action of the derived class :class:`TopologyAction` (if the topological modifications are the same of course). This implies that the attributes :attr:`Action.authorized_keys` is not checked in this method. - Note that if 2 actions doesn't act on the same powergrid, or on the same backend (eg number of loads, or - generators is not the same in *self* and *other*, or they are not in the same order) then action will be + Note that if 2 actions don't act on the same powergrid, or on the same backend (eg number of loads, or + generators are not the same in *self* and *other*, or they are not in the same order) then action will be declared as different. - **Known issue** if two backend are different, but the description of the _grid are identical (ie all + **Known issue** if two backends are different, but the description of the _grid are identical (ie all n_gen, n_load, n_line, sub_info, dim_topo, all vectors \*_to_subid, and \*_pos_topo_vect are identical) then this method will not detect the backend are different, and the action could be declared - as identical. For now, this is only a theoretical behaviour: if everything is the same, then probably, up to - the naming convention, then the powergrid are identical too. + as identical. For now, this is only a theoretical behavior: if everything is the same, then probably, up to + the naming convention, then the power grids are identical too. Parameters ---------- @@ -421,24 +436,24 @@ def get_topological_impact(self): will be impacted. For examples: - If an action from an :class:`grid2op.Agent` reconnect a powerline, but this powerline is being - disconnected by a hazards at the same time step, then this action will not be implemented on the grid. + disconnected by a hazard at the same time step, then this action will not be implemented on the grid. However, it this powerline couldn't be reconnected for some reason (for example it was already out of order) the action will still be declared illegal, even if it has NOT impacted the powergrid. - If an action tries to disconnect a powerline already disconnected, it will "impact" this powergrid. This - means that even if the action will basically do nothing, it disconnecting this powerline is against the + means that even if the action will do nothing, it disconnecting this powerline is against the rules, then the action will be illegal. - If an action tries to change the topology of a substation, but this substation is already at the target - topology, the same mechanism applies. The action will "impact" the substation, even if, at the end, it + topology, the same mechanism applies. The action will "impact" the substation, even if, in the end, it consists of doing nothing. - Any such "change" that would be illegal are declared as "illegal" regardless of the real impact of this action + Any such "change" that would be illegal is declared as "illegal" regardless of the real impact of this action on the powergrid. Returns ------- lines_impacted: :class:`numpy.array`, dtype:np.bool - A vector with the same size as the number of powerline in the grid (:attr:`Action.n_line`) with for each + A vector with the same size as the number of powerlines in the grid (:attr:`Action.n_line`) with for each component ``True`` if the line STATUS is impacted by the action, and ``False`` otherwise. See :attr:`Action._lines_impacted` for more information. @@ -496,7 +511,7 @@ def reset(self): def __call__(self): """ This method is used to return the effect of the current action in a format understandable by the backend. - This format is detailed bellow. + This format is detailed below. This function must also integrate the redispatching strategy for the Action. @@ -529,8 +544,9 @@ def __call__(self): Raises ------- - AmbiguousAction + :class:`grid2op.Exceptions.AmbiguousAction` Or one of its derivate class. + """ self._check_for_ambiguity() @@ -812,54 +828,54 @@ def _digest_redispatching(self, dict_): def update(self, dict_): """ - Update the action with a comprehensible format specified by a dictionnary. + Update the action with a comprehensible format specified by a dictionary. - Preferably, if a keys of the argument *dict_* is not found in :attr:`Action.authorized_keys` it should throw a + Preferably, if a key of the argument *dict_* is not found in :attr:`Action.authorized_keys` it should throw a warning. This argument will be completely ignored. This method also reset the attributes :attr:`Action._vectorized` :attr:`Action._lines_impacted` and :attr:`Action._subs_impacted` to ``None`` regardless of the argument in input. - If an action consist in "reconnecting" a powerline, and this same powerline is affected by a maintenance or a + If an action consists of "reconnecting" a powerline, and this same powerline is affected by maintenance or a hazard, it will be erased without any warning. "hazards" and "maintenance" have the priority. This is a - requirements for all proper :class:`Action` subclass. + requirement for all proper :class:`Action` subclass. Parameters ---------- dict_: :class:`dict` If it's ``None`` or empty it does nothing. Otherwise, it can contain the following (optional) keys: - - "*injection*" if the action will modify the injections (generator setpoint / load value - active or + - "*injection*" if the action will modify the injections (generator setpoint/load value - active or reactive) of the powergrid. It has optionally one of the following keys: - - "load_p" to set the active load values (this is a np array with the same size as the number of + - "load_p": to set the active load values (this is a numpy array with the same size as the number of load in the power _grid with Nan: don't change anything, else set the value - - "load_q" : same as above but for the load reactive values - - "prod_p" : same as above but for the generator active setpoint values. It has the size + - "load_q": same as above but for the load reactive values + - "prod_p": same as above but for the generator active setpoint values. It has the size corresponding to the number of generators in the test case. - - "prod_v" : same as above but set the voltage setpoint of generator units. + - "prod_v": same as above but set the voltage setpoint of generator units. - "*hazards*": represents the hazards that the line might suffer (boolean vector) False: no hazard, nothing - is done, True: an hazard, the powerline is disconnected + is done, True: a hazard, the powerline is disconnected - "*maintenance*": represents the maintenance operation performed on each powerline (boolean vector) False: no maintenance, nothing is done, True: a maintenance is scheduled, the powerline is disconnected - "*set_line_status*": a vector (int or float) to set the status of the powerline status (connected / disconnected) with the following interpretation: - - 0 : nothing is changed, - - -1 : disconnect the powerline, - - +1 : reconnect the powerline. If an action consist in "reconnect" a powerline, and this same + - 0: nothing is changed, + - -1: disconnect the powerline, + - +1: reconnect the powerline. If an action consists in "reconnecting" a powerline, and this same powerline is affected by a maintenance or a hazard, it will be erased without any warning. "hazards" and "maintenance" have the priority. - - "change_line_status": a vector (bool) to change the status of the powerline. This vector should be interpreted - as: + - "change_line_status": a vector (bool) to change the status of the powerline. This vector should be + interpreted as: - - False: do nothing - - True: change the status of the powerline: disconnect it if it was connected, connect it if it was + - ``False``: do nothing + - ``True``: change the status of the powerline: disconnect it if it was connected, connect it if it was disconnected - - "set_bus": (numpy int vector or dict) will set the buses to which the objects is connected. It follows a + - "set_bus": (numpy int vector or dictionary) will set the buses to which the objects are connected. It follows a similar interpretation than the line status vector: - 0 -> don't change anything @@ -867,7 +883,7 @@ def update(self, dict_): - +2 -> set to bus 2, etc. - -1: You can use this method to disconnect an object by setting the value to -1. - - "change_bus": (numpy bool vector or dict) will change the bus to which the object is connected. True will + - "change_bus": (numpy bool vector or dictionary) will change the bus to which the object is connected. True will change it (eg switch it from bus 1 to bus 2 or from bus 2 to bus 1). NB this is only active if the system has only 2 buses per substation. @@ -877,27 +893,92 @@ def update(self, dict_): - If "set_bus" is 1, then the object (load, generator or powerline) will be moved to bus 1 of the substation to which it is connected. If it is already to bus 1 nothing will be done. If it's on another - bus it will connect it to bus 1. It's it's disconnected, it will reconnect it and connect it to bus 1. - - If "change_bus" is True, then object will be moved from one bus to another. If the object where on bus 1 + bus it will connect it to bus 1. It's disconnected, it will reconnect it and connect it to bus 1. + - If "change_bus" is True, then objects will be moved from one bus to another. If the object were on bus 1 then it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object is disconnected then the action is ambiguous, and calling it will throw an AmbiguousAction exception. **NB**: if a powerline is reconnected, it should be specified on the "set_bus" action at which buses it - should be reconnected. Otherwise, action cannot be used. Trying to apply the action to the _grid will - lead to a "AmbiguousAction" exception. + should be reconnected. Otherwise, action cannot be used. Trying to apply the action to the grid will + lead to an "AmbiguousAction" exception. **NB**: if for a given powerline, both switch_line_status and set_line_status is set, the action will not be usable. This will lead to an :class:`grid2op.Exception.AmbiguousAction` exception. - **NB**: length of vector here are NOT check in this function. This method can be "chained" and only on the final - action, when used, eg. in the Backend, i checked. + **NB**: The length of vectors provided here is NOT check in this function. This method can be "chained" and only on the + final + action, when used, eg. in the Backend, is checked. **NB**: If a powerline is disconnected, on maintenance, or suffer an outage, the associated "set_bus" will be ignored. - Disconnection has the priority on anything. This priority is given because in case of hazard, the hazard has + Disconnection has the priority on anything. This priority is given because, in case of hazard, the hazard has the priority over the possible actions. + Examples + -------- + Here are short examples on how to update an action *eg.* how to create a valid :class:`Action` object that + be used to modify a :class:`grid2op.Backend.Backend`. + + In all the following examples, we suppose that a valid grid2op environment is created, for example with: + .. code-block:: python + + import grid2op + # create a simple environment + # and make sure every type of action can be used. + env = grid2op.make(action_class=grid2op.Action.Action) + + *Example 1*: modify the load active values to set them all to 1. You can replace "load_p" by "load_q", + "prod_p" or "prod_v" to change the load reactive value, the generator active setpoint or the generator + voltage magnitude setpoint. + + .. code-block:: python + + new_load = np.ones(env.action_space.n_load) + modify_load_active_value = env.action_space({"injection": {"load_p": new_load}}) + print(modify_load_active_value) + + *Example 2*: disconnect the powerline of id 1: + + .. code-block:: python + + disconnect_powerline = env.action_space({"set_line_status": [(1, -1)]}) + print(disconnect_powerline) + # there is a shortcut to do that: + disconnect_powerline2 = env.disconnect_powerline(line_id=1) + + *Example 3*: force the reconnection of the powerline of id 5 by connected it to bus 1 on its origin end and + bus 2 on its extremity end. Note that this is mandatory to specify on which bus to reconnect each + extremity of the powerline. Otherwise it's an ambiguous action. + + .. code-block:: python + + reconnect_powerline = env.action_space({"set_line_status": [(5, 1)], + "set_bus": {"lines_or_id": [(5, 1)]}, + "set_bus": {"lines_ex_id": [(5, 2)]} + }) + print(reconnect_powerline) + # and the shorter method: + reconnect_powerline = env.action.space.reconnect_powerline(line_id=5, bus_or=1, bus_ex=2) + + *Example 4*: change the bus to which load 4 is connected: + + .. code-block:: python + + change_load_bus = env.action_space({"set_bus": {"loads_id": [(4, 1)]} }) + print(change_load_bus) + + *Example 5*: reconfigure completely substation 5, and connect the first 3 elements to bus 1 and the last 3 + elements to bus 2 + + .. code-block:: python + + sub_id = 5 + target_topology = np.ones(env.sub_info[sub_id], dtype=np.int) + target_topology[3:] = 2 + reconfig_sub = env.action_space({"set_bus": {"substations_id": [(sub_id, target_topology)] } }) + print(reconfig_sub) + Returns ------- self: :class:`Action` @@ -928,12 +1009,12 @@ def update(self, dict_): def _check_for_ambiguity(self): """ - This method check if an action is ambiguous or not. If the instance is ambiguous, an + This method checks if an action is ambiguous or not. If the instance is ambiguous, an :class:`grid2op.Exceptions.AmbiguousAction` is raised. An action can be ambiguous in the following context: - - It affects the injections in an incorrect way: + - It incorrectly affects the injections: - :code:`self._dict_inj["load_p"]` doesn't have the same size as the number of loads on the _grid. - :code:`self._dict_inj["load_q"]` doesn't have the same size as the number of loads on the _grid. @@ -950,7 +1031,7 @@ def _check_for_ambiguity(self): - the status of some powerline is both *changed* (:code:`self._switch_line_status[i] == True` for some *i*) and *set* (:code:`self._set_line_status[i]` for the same *i* is not 0) - - It has an ambiguous behaviour concerning the topology of some substations + - It has an ambiguous behavior concerning the topology of some substations - the state of some bus for some element is both *changed* (:code:`self._change_bus_vect[i] = True` for some *i*) and *set* (:code:`self._set_topo_vect[i]` for the same *i* is not 0) @@ -972,20 +1053,18 @@ def _check_for_ambiguity(self): In case of need to overload this method, it is advise to still call this one from the base :class:`Action` with ":code:`super()._check_for_ambiguity()`" or ":code:`Action._check_for_ambiguity(self)`". - Returns - ------- - ``None`` - Raises ------- - AmbiguousAction + :class:`grid2op.Exceptions.AmbiguousAction` Or any of its more precise subclasses, depending on which assumption is not met. + """ if np.any(self._set_line_status[self._switch_line_status] != 0): raise InvalidLineStatus("You asked to change the status (connected / disconnected) of a powerline by" - " using the keyword \"change_status\" and set this same line state in \"set_status\"" - " (or \"hazard\" or \"maintenance\"). This ambiguous behaviour is not supported") + " using the keyword \"change_status\" and set this same line state in " + "\"set_status\" " + "(or \"hazard\" or \"maintenance\"). This ambiguous behaviour is not supported") # check size if "load_p" in self._dict_inj: if len(self._dict_inj["load_p"]) != self.n_load: @@ -1047,8 +1126,8 @@ def _check_for_ambiguity(self): # topological action if np.any(self._set_topo_vect[self._change_bus_vect] != 0): raise InvalidBusStatus("You asked to change the bus of an object with" - " using the keyword \"change_bus\" and set this same object state in \"set_bus\"" - ". This ambiguous behaviour is not supported") + " using the keyword \"change_bus\" and set this same object state in \"set_bus\"" + ". This ambiguous behaviour is not supported") for q_id, status in enumerate(self._set_line_status): if status == 1: @@ -1080,8 +1159,10 @@ def sample(self): """ This method is used to sample action. - Generic sampling of action can be really tedious. Uniform sampling is almost impossible. - The actual implementation gives absolutely no warranty toward any of these concerns. It will: + A generic sampling of action can be really tedious. Uniform sampling is almost impossible. + The actual implementation gives absolutely no warranty toward any of these concerns. + + It is not implemented yet. TODO By calling :func:`Action.sample`, the action is :func:`Action.reset` to a "do nothing" state. @@ -1090,6 +1171,7 @@ def sample(self): ------- self: :class:`Action` The action sampled among the action space. + """ self.reset() # TODO code the sampling now @@ -1134,12 +1216,13 @@ def _obj_caract_from_topo_id(self, id_): def __str__(self): """ - This utility allows to print in a human readable format which objects will be impacted by the action. + This utility allows printing in a human-readable format what objects will be impacted by the action. Returns ------- str: :class:`str` - The string representation of an :class:`Action` + The string representation of an :class:`Action` in a human-readable format. + """ res = ["This action will:"] # handles actions on injections @@ -1202,9 +1285,9 @@ def __str__(self): def as_dict(self): """ - Represent an action "as a" dictionnary. This dictionnary is usefull to further inspect on which elements - the actions had an impact. It is not recommended to use it as a way to serialize actions. The do nothing action - should allways be represented by an empty dictionnary. + Represent an action "as a" dictionary. This dictionary is useful to further inspect on which elements + the actions had an impact. It is not recommended to use it as a way to serialize actions. The "do nothing" action + should always be represented by an empty dictionary. The following keys (all optional) are present in the results: @@ -1213,51 +1296,52 @@ def as_dict(self): * `prod_p`: if the action modifies the active productions of generators. * `prod_v`: if the action modifies the voltage setpoint of generators. * `set_line_status` if the action tries to **set** the status of some powerlines. If present, this is a - a dictionnary with keys: + a dictionary with keys: * `nb_connected`: number of powerlines that are reconnected * `nb_disconnected`: number of powerlines that are disconnected * `connected_id`: the id of the powerlines reconnected * `disconnected_id`: the ids of the powerlines disconnected - * `change_line_status`: if the action tries to **change** the status of some powelrines. If present, this - is a dictionnary with keys: + * `change_line_status`: if the action tries to **change** the status of some powerlines. If present, this + is a dictionary with keys: * `nb_changed`: number of powerlines having their status changed * `changed_id`: the ids of the powerlines that are changed * `change_bus_vect`: if the action tries to **change** the topology of some substations. If present, this - is a dictionnary with keys: + is a dictionary with keys: * `nb_modif_subs`: number of substations impacted by the action * `modif_subs_id`: ids of the substations impacted by the action - * `change_bus_vect`: details the objects that are modified. It is itself a dictionnary that represents for + * `change_bus_vect`: details the objects that are modified. It is itself a dictionary that represents for each impacted substations (keys) the modification of the objects connected to it. * `set_bus_vect`: if the action tries to **set** the topology of some substations. If present, this is a - dictionnary with keys: + dictionary with keys: * `nb_modif_subs`: number of substations impacted by the action * `modif_subs_id`: the ids of the substations impacted by the action - * `set_bus_vect`: details the objects that are modified. It is also a dictionnary that representes for + * `set_bus_vect`: details the objects that are modified. It is also a dictionary that represents for each impacted substations (keys) how the elements connected to it are impacted (their "new" bus) - * `hazards` if the action is composed of some hazards. In this case it's simply the index of the powerlines + * `hazards` if the action is composed of some hazards. In this case, it's simply the index of the powerlines that are disconnected because of them. * `nb_hazards` the number of hazards the "action" implemented (eg number of powerlines disconnected because of hazards. - * `maintenance` if the action is composed of some maintenance. In this case it's simply the index of the + * `maintenance` if the action is composed of some maintenance. In this case, it's simply the index of the powerlines that are affected by maintenance operation at this time step. that are disconnected because of them. * `nb_maintenance` the number of maintenance the "action" implemented eg the number of powerlines - disconnected becaues of maintenance operation. + disconnected because of maintenance operations. * `redispatch` the redispatching action (if any). It gives, for each generator (all generator, not just the dispatchable one) the amount of power redispatched in this action. Returns ------- res: ``dict`` - The action represented as a dictionnary. See above for a description of it. + The action represented as a dictionary. See above for a description of it. + """ res = {} @@ -1330,33 +1414,33 @@ def as_dict(self): def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, substation_id=None): """ - Return the effect of this action on a give unique load, generator unit, powerline of substation. + Return the effect of this action on a unique given load, generator unit, powerline or substation. Only one of load, gen, line or substation should be filled. - The querry of these objects can only be done by id here (ie by giving the integer of the object in the backed). + The query of these objects can only be done by id here (ie by giving the integer of the object in the backed). The :class:`HelperAction` has some utilities to access them by name too. Parameters ---------- _sentinel: ``None`` - Used to prevent positional parameters. Internal, do not use. + Used to prevent positional parameters. Internal, **do not use**. load_id: ``int`` - ID of the load we want to inspect + The ID of the load we want to inspect gen_id: ``int`` - ID of the generator we want to inspect + The ID of the generator we want to inspect line_id: ``int`` - ID of the powerline we want to inspect + The ID of the powerline we want to inspect substation_id: ``int`` - ID of the substation we want to inspect + The ID of the substation we want to inspect Returns ------- res: :class:`dict` - A dictionnary with keys and value depending on which object needs to be inspected: + A dictionary with keys and value depending on which object needs to be inspected: - if a load is inspected, then the keys are: @@ -1378,12 +1462,12 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub - if a powerline is inspected then the keys are: - - "change_bus_or" : whether or not the origin end will be moved from one bus to another - - "change_bus_ex" : whether or not the extremity end will be moved from one bus to another - - "set_bus_or" : the new bus where the origin will be moved - - "set_bus_ex" : the new bus where the extremity will be moved - - "set_line_status" : the new status of the power line - - "change_line_status" : whether or not to switch the status of the powerline + - "change_bus_or": whether or not the origin end will be moved from one bus to another + - "change_bus_ex": whether or not the extremity end will be moved from one bus to another + - "set_bus_or": the new bus where the origin will be moved + - "set_bus_ex": the new bus where the extremity will be moved + - "set_line_status": the new status of the power line + - "change_line_status": whether or not to switch the status of the powerline - if a substation is inspected, it returns the topology to this substation in a dictionary with keys: @@ -1394,15 +1478,15 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub - If "set_bus" is 1, then the object (load, generator or powerline) will be moved to bus 1 of the substation to which it is connected. If it is already to bus 1 nothing will be done. If it's on another bus it will - connect it to bus 1. It's it's disconnected, it will reconnect it and connect it to bus 1. - - If "change_bus" is True, then object will be moved from one bus to another. If the object where on bus 1 + connect it to bus 1. It's disconnected, it will reconnect it and connect it to bus 1. + - If "change_bus" is True, then the object will be moved from one bus to another. If the object were on bus 1 then it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object were - disconnected, then it will be connected on the affected bus. + disconnected, then it will be connected to the affected bus. Raises ------ - Grid2OpException - If _sentinel is modified, or if None of the arguments are set or alternatively if 2 or more of the + :class:`grid2op.Exception.Grid2OpException` + If _sentinel is modified, or if none of the arguments are set or alternatively if 2 or more of the parameters are being set. """ @@ -1464,17 +1548,20 @@ def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, sub class TopologyAction(Action): """ - This class is here to model only Topological actions. - It will throw an "AmbiguousAction" error it someone attempt to change injections in any ways. + This class is model only topological actions. + It will throw an ":class:`grid2op.Exception.AmbiguousAction`" error it someone attempt to change injections + in any ways. It has the same attributes as its base class :class:`Action`. It is also here to show an example on how to implement a valid class deriving from :class:`Action`. + """ def __init__(self, gridobj): """ See the definition of :func:`Action.__init__` and of :class:`Action` for more information. Nothing more is done in this constructor. + """ Action.__init__(self, gridobj) @@ -1488,33 +1575,38 @@ def __init__(self, gridobj): def __call__(self): """ Compare to the ancestor :func:`Action.__call__` this type of Action doesn't allow to change the injections. - The only difference is in the returned value *dict_injection* that is always an empty dictionnary. + The only difference is in the returned value *dict_injection* that is always an empty dictionary. Returns ------- - dict_injection: :class:`dict` - This dictionnary is always empty + dict_injection: ``dict`` + This dictionary is always empty - set_line_status: :class:`numpy.array`, dtype:int + set_line_status: :class:`numpy.ndarray`, dtype:int This array is :attr:`Action._set_line_status` - switch_line_status: :class:`numpy.array`, dtype:bool + switch_line_status: :class:`numpy.ndarray`, dtype:bool This array is :attr:`Action._switch_line_status` - set_topo_vect: :class:`numpy.array`, dtype:int + set_topo_vect: :class:`numpy.ndarray`, dtype:int This array is :attr:`Action._set_topo_vect` - change_bus_vect: :class:`numpy.array`, dtype:bool + change_bus_vect: :class:`numpy.ndarray`, dtype:bool This array is :attr:`Action._change_bus_vect` + + redispatch: :class:`numpy.ndarray`, dtype:float + Thie array is :attr:`Action._redispatch` + """ if self._dict_inj: raise AmbiguousAction("You asked to modify the injection with an action of class \"TopologyAction\".") self._check_for_ambiguity() - return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect, self._redispatch + return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect,\ + self._redispatch def update(self, dict_): """ - As its original implementation, this method allows to modify the way a dictionnary can be mapped to a valid + As its original implementation, this method allows modifying the way a dictionary can be mapped to a valid :class:`Action`. It has only minor modifications compared to the original :func:`Action.update` implementation, most notably, it @@ -1524,12 +1616,13 @@ def update(self, dict_): ---------- dict_: :class:`dict` See the help of :func:`Action.update` for a detailed explanation. **NB** all the explanations concerning the - "injection" part are irrelevant for this subclass. + "injection" part is irrelevant for this subclass. Returns ------- self: :class:`TopologyAction` - Return object itself thus allowing mutiple call to "update" to be chained. + Return object itself thus allowing multiple calls to "update" to be chained. + """ self.as_vect = None if dict_ is not None: @@ -1550,35 +1643,38 @@ def update(self, dict_): def sample(self): """ + Sample a Topology action. + This method is not implemented at the moment. TODO Returns ------- - res: :class:`Action` + res: :class:`TopologyAction` The current action (useful to chain some calls to methods) """ self.reset() - # TODO code the sampling now - # TODO test it !!! return self class PowerLineSet(Action): """ - This class is here to model only a subpart of Topological actions, the one consisting in topological switching. - It will throw an "AmbiguousAction" error it someone attempt to change injections in any ways. + This class is here to model only a subpart of Topological actions, the one consisting of topological switching. + It will throw an "AmbiguousAction" error if someone attempts to change injections in any way. It has the same attributes as its base class :class:`Action`. - It is also here to show an example on how to implement a valid class deriving from :class:`Action`. + It is also here to show an example of how to implement a valid class deriving from :class:`Action`. + + **NB** This class doesn't allow to connect an object to other buses than their original bus. In this case, + reconnecting a powerline cannot be considered "ambiguous": all powerlines are reconnected on bus 1 on both + of their substations. - **NB** This class doesn't allow to connect object to other buses than their original bus. In this case, - reconnecting a powerline cannot be considered "ambiguous". We have to """ def __init__(self, gridobj): """ See the definition of :func:`Action.__init__` and of :class:`Action` for more information. Nothing more is done in this constructor. + """ Action.__init__(self, gridobj) @@ -1591,12 +1687,12 @@ def __init__(self, gridobj): def __call__(self): """ Compare to the ancestor :func:`Action.__call__` this type of Action doesn't allow to change the injections. - The only difference is in the returned value *dict_injection* that is always an empty dictionnary. + The only difference is in the returned value *dict_injection* that is always an empty dictionary. Returns ------- dict_injection: :class:`dict` - This dictionnary is always empty + This dictionary is always empty set_line_status: :class:`numpy.array`, dtype:int This array is :attr:`Action._set_line_status` @@ -1609,6 +1705,7 @@ def __call__(self): change_bus_vect: :class:`numpy.array`, dtype:bool This array is :attr:`Action._change_bus_vect`, it is never modified + """ if self._dict_inj: raise AmbiguousAction("You asked to modify the injection with an action of class \"TopologyAction\".") @@ -1617,7 +1714,7 @@ def __call__(self): def update(self, dict_): """ - As its original implementation, this method allows to modify the way a dictionnary can be mapped to a valid + As its original implementation, this method allows modifying the way a dictionary can be mapped to a valid :class:`Action`. It has only minor modifications compared to the original :func:`Action.update` implementation, most notably, it @@ -1632,10 +1729,10 @@ def update(self, dict_): Returns ------- self: :class:`PowerLineSet` - Return object itself thus allowing mutiple call to "update" to be chained. - """ + Return object itself thus allowing multiple calls to "update" to be chained. - self.as_vect = None + """ + self._vectorized = None if dict_ is not None: for kk in dict_.keys(): if kk not in self.authorized_keys: @@ -1692,10 +1789,10 @@ def sample(self): class SerializableActionSpace(SerializableSpace): """ - This class allows to serialize / de serialize the action space. + This class allows serializing/ deserializing the action space. - It should not be used inside an Environment, as some functions of the action might not be compatible with - the serialization, especially the checking of whether or not an Action is legal or not. + It should not be used inside an :attr:`grid2op.Environment.Environment` , as some functions of the action might not + be compatible with the serialization, especially the checking of whether or not an action is legal or not. Attributes ---------- @@ -1717,7 +1814,8 @@ def __init__(self, gridobj, actionClass=Action): Representation of the underlying powergrid. actionClass: ``type`` - Type of action used to build :attr:`Space.SerializableSpace.template_obj` + Type of action used to build :attr:`Space.SerializableSpace.template_obj`. It should derived from + :class:`Action`. """ SerializableSpace.__init__(self, gridobj=gridobj, subtype=actionClass) @@ -1728,27 +1826,27 @@ def __init__(self, gridobj, actionClass=Action): @staticmethod def from_dict(dict_): """ - Allows the de-serialization of an object stored as a dictionnary (for example in the case of json saving). + Allows the de-serialization of an object stored as a dictionary (for example in the case of JSON saving). Parameters ---------- dict_: ``dict`` - Representation of an Action Space (aka SerializableActionSpace) as a dictionnary. + Representation of an Action Space (aka SerializableActionSpace) as a dictionary. Returns ------- res: :class:``SerializableActionSpace`` - An instance of an action space matching the dictionnary. + An instance of an action space matching the dictionary. + """ tmp = SerializableSpace.from_dict(dict_) - res = SerializableActionSpace(gridobj=tmp, - actionClass=tmp.subtype) + res = SerializableActionSpace(gridobj=tmp, actionClass=tmp.subtype) return res def sample(self): """ - A utility used to sample :class:`grid2op.Action.Action`. + A utility used to sample :class:`Action`. This method is under development, use with care (actions are not sampled on the full action space, and are not uniform in general). @@ -1757,18 +1855,84 @@ def sample(self): ------- res: :class:`Action` A random action sampled from the :attr:`HelperAction.actionClass` + """ res = self.actionClass(gridobj=self) # only the GridObjects part of "self" is actually used res.sample() return res + def disconnect_powerlin(self, line_id, previous_action=None): + """ + Utilities to disconnect a powerline more easily. + + Parameters + ---------- + line_id: ``int`` + The powerline to be disconnected. + + previous_action + + Returns + ------- + + """ + if previous_action is None: + res = self.actionClass(gridobj=self) + else: + if not isinstance(previous_action, self.actionClass): + raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" " + "which is not the type of action handled by this helper " + "(\"{}\")".format(type(previous_action), self.actionClass)) + res = previous_action + if line_id > self.n_line: + raise AmbiguousAction("You asked to disconnect powerline of id {} but this id does not exist. The " + "grid counts only {} powerline".format(line_id, self.n_line)) + res.update({"set_line_status": [(line_id, -1)]}) + return res + + def reconnect_powerline(self, line_id, bus_or, bus_ex, previous_action=None): + """ + Utilities to disconnect a powerline more easily. + + Parameters + ---------- + line_id: ``int`` + The powerline to be disconnected. + + bus_or: ``int`` + On which bus to reconnect the powerline at its origin end + + bus_ex: ``int`` + On which bus to reconnect the powerline at its extremity end + previous_action + + Returns + ------- + + """ + if previous_action is None: + res = self.actionClass(gridobj=self) + else: + if not isinstance(previous_action, self.actionClass): + raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" " + "which is not the type of action handled by this helper " + "(\"{}\")".format(type(previous_action), self.actionClass)) + res = previous_action + if line_id > self.n_line: + raise AmbiguousAction("You asked to disconnect powerline of id {} but this id does not exist. The " + "grid counts only {} powerline".format(line_id, self.n_line)) + res.update({"set_line_status": [(line_id, -1)], + "set_bus": {"lines_or_id": [(line_id, bus_or)], + "lines_ex_id": [(line_id, bus_ex)]}}) + return res + def change_bus(self, name_element, extremity=None, substation=None, type_element=None, previous_action=None): """ - Utilities to change the bus of a single element if you give its name. **NB** Chaning a bus has the effect to + Utilities to change the bus of a single element if you give its name. **NB** Changing a bus has the effect to assign the object to bus 1 if it was before that connected to bus 2, and to assign it to bus 2 if it was connected to bus 1. It should not be mixed up with :func:`HelperAction.set_bus`. - If the parameter "*previous_action*" is not ``None``, then the action given to it is updated (inplace) and + If the parameter "*previous_action*" is not ``None``, then the action given to it is updated (in place) and returned. Parameters @@ -1776,9 +1940,9 @@ def change_bus(self, name_element, extremity=None, substation=None, type_element name_element: ``str`` The name of the element you want to change the bus extremity: ``str`` - "or" or "ex" for origin or extremity, ignored if element is not a powerline. + "or" or "ex" for origin or extremity, ignored if an element is not a powerline. substation: ``int``, optional - Its substation ID, if you know it will increase the performance. Otherwise the method will search it. + Its substation ID, if you know it will increase the performance. Otherwise, the method will search for it. type_element: ``int``, optional Type of the element to look for. It is here to speed up the computation. One of "line", "gen" or "load" previous_action: :class:`Action`, optional @@ -1791,14 +1955,17 @@ def change_bus(self, name_element, extremity=None, substation=None, type_element Raises ------ - AmbiguousAction + :class:`grid2op.Exception.AmbiguousAction` If *previous_action* has not the same type as :attr:`HelperAction.actionClass`. + """ if previous_action is None: res = self.actionClass(gridobj=self) else: if not isinstance(previous_action, self.actionClass): - raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" which is not the type of action handled by this helper (\"{}\")".format(type(previous_action), self.actionClass)) + raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" " + "which is not the type of action handled by this helper " + "(\"{}\")".format(type(previous_action), self.actionClass)) res = previous_action dict_, to_sub_pos, my_id, my_sub_id = self._extract_dict_action(name_element, extremity, substation, type_element, res) @@ -1854,7 +2021,8 @@ def _extract_dict_action(self, name_element, extremity=None, substation=None, ty "Element \"{}\" not found in the powergrid".format( name_element)) else: - raise AmbiguousAction("unknown type_element specifier \"{}\". type_element should be \"line\" or \"load\" or \"gen\"".format(extremity)) + raise AmbiguousAction("unknown type_element specifier \"{}\". type_element should be \"line\" or \"load\" " + "or \"gen\"".format(extremity)) my_id = None for i, nm in enumerate(to_name): @@ -1872,10 +2040,10 @@ def set_bus(self, name_element, new_bus, extremity=None, substation=None, type_e """ Utilities to set the bus of a single element if you give its name. **NB** Setting a bus has the effect to assign the object to this bus. If it was before that connected to bus 1, and you assign it to bus 1 (*new_bus* - = 1) it will stay on bus 1. If it was on bus 2 (and you still assign it to bus 1) it will be moved to bus + = 1) it will stay on bus 1. If it was on bus 2 (and you still assign it to bus 1) it will be moved to bus 2. 1. It should not be mixed up with :func:`HelperAction.change_bus`. - If the parameter "*previous_action*" is not ``None``, then the action given to it is updated (inplace) and + If the parameter "*previous_action*" is not ``None``, then the action given to it is updated (in place) and returned. Parameters @@ -1887,10 +2055,10 @@ def set_bus(self, name_element, new_bus, extremity=None, substation=None, type_e Id of the new bus to connect the object to. extremity: ``str`` - "or" or "ext" for origin or extremity, ignored if element is not a powerline. + "or" or "ext" for origin or extremity, ignored if the element is not a powerline. substation: ``int``, optional - Its substation ID, if you know it will increase the performance. Otherwise the method will search it. + Its substation ID, if you know it will increase the performance. Otherwise, the method will search for it. type_element: ``str``, optional Type of the element to look for. It is here to speed up the computation. One of "line", "gen" or "load" @@ -1907,13 +2075,15 @@ def set_bus(self, name_element, new_bus, extremity=None, substation=None, type_e ------ AmbiguousAction If *previous_action* has not the same type as :attr:`HelperAction.actionClass`. + """ if previous_action is None: res = self.actionClass(gridobj=self) else: res = previous_action - dict_, to_sub_pos, my_id, my_sub_id = self._extract_dict_action(name_element, extremity, substation, type_element, res) + dict_, to_sub_pos, my_id, my_sub_id = self._extract_dict_action(name_element, extremity, substation, + type_element, res) dict_["set_bus"][to_sub_pos[my_id]] = new_bus res.update({"set_bus": {"substations_id": [(my_sub_id, dict_["set_bus"])]}}) return res @@ -1931,13 +2101,27 @@ def reconnect_powerline(self, l_id, bus_or, bus_ex, previous_action=None): bus_ex: `int` the bus to which connect the extremity end the powerline + previous_action: :class:`Action`, optional + The (optional) action to update. It should be of the same type as :attr:`HelperAction.actionClass` + Returns ------- + res: :class:`Action` + The action with the modification implemented + + Raises + ------ + AmbiguousAction + If *previous_action* has not the same type as :attr:`HelperAction.actionClass`. """ if previous_action is None: res = self.actionClass(gridobj=self) else: + if not isinstance(previous_action, self.actionClass): + raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" " + "which is not the type of action handled by this helper " + "(\"{}\")".format(type(previous_action), self.actionClass)) res = previous_action res.update({"set_line_status": [(l_id, 1)], "set_bus": {"lines_or_id": [(l_id, bus_or)], @@ -1947,7 +2131,7 @@ def reconnect_powerline(self, l_id, bus_or, bus_ex, previous_action=None): def get_set_line_status_vect(self): """ - Computes and return a vector that can be used in the "set_status" keyword if building an :class:`Action` + Computes and returns a vector that can be used in the "set_status" keyword if building an :class:`Action` Returns ------- @@ -1979,7 +2163,7 @@ class HelperAction(SerializableActionSpace): more information). It will allow, thanks to its :func:`HelperAction.__call__` method to create valid :class:`Action`. It is the - preferred way to create object of class :class:`Action` in this package. + the preferred way to create an object of class :class:`Action` in this package. On the contrary to the :class:`Action`, it is NOT recommended to overload this helper. If more flexibility is needed on the type of :class:`Action` created, it is recommended to pass a different "*actionClass*" argument @@ -1989,7 +2173,7 @@ class HelperAction(SerializableActionSpace): Attributes ---------- game_rules: :class:`grid2op.GameRules.GameRules` - Class specifying the rules of the game, used to check the legality of the actions. + Class specifying the rules of the game used to check the legality of the actions. """ @@ -2006,11 +2190,11 @@ def __init__(self, gridobj, The representation of the powergrid. actionClass: ``type`` - Note that this parameter expected a class, and not an object of the class. It is used to return the + Note that this parameter expected a class and not an object of the class. It is used to return the appropriate action type. game_rules: :class:`grid2op.GameRules.GameRules` - Class specifying the rules of the game, used to check the legality of the actions. + Class specifying the rules of the game used to check the legality of the actions. """ SerializableActionSpace.__init__(self, gridobj, @@ -2023,9 +2207,23 @@ def __call__(self, dict_=None, check_legal=False, env=None): This utility allows you to build a valid action, with the proper sizes if you provide it with a valid dictionnary. - More information about this dictionnary can be found in the :func:`Action.__call__` help. This dictionnary + More information about this dictionnary can be found in the :func:`Action.update` help. This dictionnary is not changed in this method. + **NB** This is the only recommended way to make a valid, with proper dimension :class:`Action` object: + + Examples + -------- + Here is a short example on how to make a action. For more detailed examples see :func:`Action.update` + + .. code-block:: python + + import grid2op + # create a simple environment + env = grid2op.make() + act = env.action_space({}) + # act is now the "do nothing" action, that doesn't modify the grid. + Parameters ---------- dict_ : :class:`dict` @@ -2043,7 +2241,8 @@ def __call__(self, dict_=None, check_legal=False, env=None): Returns ------- res: :class:`Action` - Properly instanciated. + An action that is valid and corresponds to what the agent want to do with the formalism defined in + see :func:`Action.udpate`. """ From 7f10ed92a6c6e8e63b077b60f8cc4aee89fa4314 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Fri, 17 Jan 2020 18:58:56 +0100 Subject: [PATCH 08/14] starting to implement the renderer --- CHANGELOG.rst | 15 +- getting_started/1_Observation_Agents.ipynb | 5 +- getting_started/3_TrainingAnAgent.ipynb | 14 +- getting_started/4_StudyYourAgent.ipynb | 2091 ++++++++++++++- getting_started/Example_5bus.ipynb | 2749 ++++++++++++++++++++ grid2op/Action.py | 95 +- grid2op/BackendPandaPower.py | 23 +- grid2op/Environment.py | 99 +- grid2op/PlotPlotly.py | 4 + grid2op/Renderer.py | 589 +++++ grid2op/Settings_L2RPN2019.py | 6 +- grid2op/Space.py | 238 +- 12 files changed, 5703 insertions(+), 225 deletions(-) create mode 100644 getting_started/Example_5bus.ipynb create mode 100644 grid2op/Renderer.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bdd921586..702decc21 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,11 +2,14 @@ Change Log ============= [0.5.0] - 2020-01-xx -------------------- -- [UPDATED] more complete documentation of the Action class (with some examples) -- [ADDED] more complete documentation of the representation of the powergrid +- [ADDED] Adding another example notebook `getting_started/Example_5bus.ipynb` +- [ADDED] Adding another renderer for the live environment. +- [ADDED] Redispatching possibility for the environment +- [ADDED] More complete documentation of the representation of the powergrid (see documentation of `Space`) -- [UPDATED] more unit test for observations -- [UPDATED]remove the TODO's already coded (no more todo then) +- [UPDATED] More complete documentation of the Action class (with some examples) +- [UPDATED] More unit test for observations +- [UPDATED] Remove the TODO's already coded (no more todo then) - [UPDATED] GridStateFromFile can now read the starting date and the time interval of the chronics. - [BREAKING] Action/Backend has been modified with the implementation of redispatching. If you used a custom backend, you'll have to implement the "redispatching" part. @@ -24,9 +27,9 @@ Change Log - [FIXED] checking key-word arguments in "make" function: if an invalid argument is provided, it now raises an error. - [UPDATED] multiple random generator streams for observations -- [UPDATED] Refactoring of the Action and Observation Space. They now both ineherit from "Space" +- [UPDATED] Refactoring of the Action and Observation Space. They now both inherit from "Space" - [BREAKING] previous saved Action Spaces and Observation Spaces (as dictionnary) are no more compatible -- [BREAKING] renaming of attributes describing the powergrid accross classes for better consistency: +- [BREAKING] renaming of attributes describing the powergrid across classes for better consistency: ==================== ======================= ======================= Class Name Old Attribute Name New Attribute Name diff --git a/getting_started/1_Observation_Agents.ipynb b/getting_started/1_Observation_Agents.ipynb index 95a72dcdc..f884f2f89 100644 --- a/getting_started/1_Observation_Agents.ipynb +++ b/getting_started/1_Observation_Agents.ipynb @@ -676,7 +676,10 @@ " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", - " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00])" + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", + " 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,\n", + " 0.00000000e+00, 0.00000000e+00])" ] }, "execution_count": 12, diff --git a/getting_started/3_TrainingAnAgent.ipynb b/getting_started/3_TrainingAnAgent.ipynb index b55487c62..ba12d5ae2 100644 --- a/getting_started/3_TrainingAnAgent.ipynb +++ b/getting_started/3_TrainingAnAgent.ipynb @@ -1061,12 +1061,14 @@ "WARNING:tensorflow:From /home/donnotben/.local/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n", "Instructions for updating:\n", "Use tf.cast instead.\n", - "We had a loss equal to 40485736.0\n", - "We had a loss equal to 32051852.0\n", - "Lived with maximum time 285\n", - "Earned a total of reward equal to 5699.951357713955\n", - "We had a loss equal to 66216080.0\n", - "We had a loss equal to 69923336.0\n", + "We had a loss equal to 9.756083\n", + "Lived with maximum time 195\n", + "Earned a total of reward equal to 3899.911978452659\n", + "We had a loss equal to 9.603789\n", + "We had a loss equal to 9.757523\n", + "We had a loss equal to 9.757078\n", + "Lived with maximum time 287\n", + "Earned a total of reward equal to 5719.9211887159245\n", "Saving Network\n", "Successfully saved network.\n" ] diff --git a/getting_started/4_StudyYourAgent.ipynb b/getting_started/4_StudyYourAgent.ipynb index af98f6588..1d9dd52a7 100644 --- a/getting_started/4_StudyYourAgent.ipynb +++ b/getting_started/4_StudyYourAgent.ipynb @@ -1258,20 +1258,20 @@ "
\n", " \n", " \n", - "
\n", + "
\n", " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ diff --git a/getting_started/Example_5bus.ipynb b/getting_started/Example_5bus.ipynb new file mode 100644 index 000000000..51cfba050 --- /dev/null +++ b/getting_started/Example_5bus.ipynb @@ -0,0 +1,2749 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Some experiments on a 5 substations test case" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To demonstrate the use of the grid2op framework, we propose here to show how to make some quick studies on a purely fictious test case, a 5 bus system.\n", + "\n", + "This system should not be used for deep research purpose. It is provided here as an example and a tool to get used to how some analyses can be performed using grid2op." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we can create a environment:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import grid2op\n", + "env = grid2op.make(\"case5_example\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can see how well the \"do nothing\" agent (the most basic imaginable) can performe, using some \"gym like\" methods" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from grid2op.Agent import DoNothingAgent\n", + "my_agent = DoNothingAgent(env.action_space)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now do the standard gym loop, and we save all the observations:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "all_obs = []\n", + "obs = env.reset()\n", + "all_obs.append(obs)\n", + "reward = env.reward_range[0]\n", + "done = False\n", + "nb_step = 0\n", + "while True:\n", + " action = my_agent.act(obs, reward, done)\n", + " obs, reward, done, _ = env.step(action)\n", + " if done:\n", + " break\n", + " all_obs.append(obs)\n", + " nb_step += 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can check if the episode has been completed, or if there has been a game over:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of timesteps computed: 97\n", + "Total maximum number of timesteps possible: 2016\n" + ] + } + ], + "source": [ + "print(\"Number of timesteps computed: {}\".format(nb_step))\n", + "print(\"Total maximum number of timesteps possible: {}\".format(env.chronics_handler.max_timestep()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we see here, there is a game over: the agent successfully managed 97 timesteps, while the episode could have lasted 2016.\n", + "\n", + "Let's try to investigate this, for example by plotting the last observation. First we need to create a utilities that will make the plot:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from grid2op.PlotPlotly import PlotObs\n", + "# init the plot\n", + "graph_layout = [(0,0), (0,400), (200,400), (400, 400), (400, 0)]\n", + "plot_helper = PlotObs(substation_layout=graph_layout,\n", + " observation_space=env.observation_space)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "mode": "text", + "showlegend": false, + "text": [ + "8.3%" + ], + "type": "scatter", + "x": [ + 1.5308084989341915e-15 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 100 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 200 + ], + "y": [ + 199.99999999999997 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 200 + ], + "y": [ + -1.5308084989341915e-15 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "8.2%" + ], + "type": "scatter", + "x": [ + 100 + ], + "y": [ + 400 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "8.4%" + ], + "type": "scatter", + "x": [ + 300 + ], + "y": [ + 410.5654565435175 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "8.4%" + ], + "type": "scatter", + "x": [ + 300 + ], + "y": [ + 389.4345434564825 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 400 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 10 MW" + ], + "type": "scatter", + "x": [ + -70 + ], + "y": [ + 8.572527594031473e-15 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 9 MW" + ], + "type": "scatter", + "x": [ + 470 + ], + "y": [ + 400 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 8 MW" + ], + "type": "scatter", + "x": [ + 470 + ], + "y": [ + 0 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "+ 1 MW" + ], + "type": "scatter", + "x": [ + -35.000000000000036 + ], + "y": [ + -60.62177826491068 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "+ 18 MW" + ], + "type": "scatter", + "x": [ + -35.000000000000014 + ], + "y": [ + 460.6217782649107 + ] + } + ], + "layout": { + "height": 600, + "margin": { + "b": 100, + "l": 20, + "r": 20 + }, + "plot_bgcolor": "white", + "shapes": [ + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": -25, + "x1": 25, + "xref": "x", + "y0": -25, + "y1": 25, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": -25, + "x1": 25, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 175, + "x1": 225, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 375, + "x1": 425, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 375, + "x1": 425, + "xref": "x", + "y0": -25, + "y1": 25, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 1.5308084989341915e-15, + "x1": 1.5308084989341915e-15, + "xref": "x", + "y0": 25, + "y1": 375, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 11.18033988749895, + "x1": 188.81966011250105, + "xref": "x", + "y0": 22.360679774997898, + "y1": 377.6393202250021, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 17.67766952966369, + "x1": 382.32233047033634, + "xref": "x", + "y0": 17.677669529663685, + "y1": 382.3223304703363, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 25, + "x1": 375, + "xref": "x", + "y0": 0, + "y1": -3.061616997868383e-15, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 25, + "x1": 175, + "xref": "x", + "y0": 400, + "y1": 400, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 222.65769467591625, + "x1": 377.3423053240837, + "xref": "x", + "y0": 410.5654565435175, + "y1": 410.5654565435175, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 222.65769467591625, + "x1": 377.3423053240838, + "xref": "x", + "y0": 389.4345434564825, + "y1": 389.4345434564825, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 400, + "x1": 400, + "xref": "x", + "y0": 375, + "y1": 25, + "yref": "y" + }, + { + "type": "line", + "x0": -50, + "x1": -25, + "xref": "x", + "y0": 6.123233995736767e-15, + "y1": 3.061616997868383e-15, + "yref": "y" + }, + { + "type": "line", + "x0": 450, + "x1": 425, + "xref": "x", + "y0": 400, + "y1": 400, + "yref": "y" + }, + { + "type": "line", + "x0": 450, + "x1": 425, + "xref": "x", + "y0": 0, + "y1": 0, + "yref": "y" + }, + { + "type": "line", + "x0": -25.000000000000025, + "x1": -12.50000000000001, + "xref": "x", + "y0": -43.30127018922192, + "y1": -21.65063509461096, + "yref": "y" + }, + { + "type": "line", + "x0": -25.00000000000001, + "x1": -12.499999999999995, + "xref": "x", + "y0": 443.3012701892219, + "y1": 421.65063509461095, + "yref": "y" + } + ], + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "width": 800, + "xaxis": { + "range": [ + -142.5, + 542.5 + ], + "zeroline": false + }, + "yaxis": { + "range": [ + -142.5, + 542.5 + ] + } + } + }, + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "last_obs = all_obs[-1]\n", + "fig = plot_helper.plot_observation(last_obs)\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the last obsevation is pretty clear: 4 powerlines have been disconnected, thus isolating the load on the bottom right. This lead to a game over. \n", + "\n", + "It's also possible, of course, to inspect the previous state, just before this one:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "mode": "text", + "showlegend": false, + "text": [ + "100.0%" + ], + "type": "scatter", + "x": [ + 1.5308084989341915e-15 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 100 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "0.0%" + ], + "type": "scatter", + "x": [ + 200 + ], + "y": [ + 199.99999999999997 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "141.5%" + ], + "type": "scatter", + "x": [ + 200 + ], + "y": [ + -1.5308084989341915e-15 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "38.0%" + ], + "type": "scatter", + "x": [ + 100 + ], + "y": [ + 400 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "36.8%" + ], + "type": "scatter", + "x": [ + 300 + ], + "y": [ + 410.5654565435175 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "36.8%" + ], + "type": "scatter", + "x": [ + 300 + ], + "y": [ + 389.4345434564825 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "141.0%" + ], + "type": "scatter", + "x": [ + 400 + ], + "y": [ + 200 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 10 MW" + ], + "type": "scatter", + "x": [ + -70 + ], + "y": [ + 8.572527594031473e-15 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 9 MW" + ], + "type": "scatter", + "x": [ + 470 + ], + "y": [ + 400 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "- 8 MW" + ], + "type": "scatter", + "x": [ + 470 + ], + "y": [ + 0 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "+ 1 MW" + ], + "type": "scatter", + "x": [ + -35.000000000000036 + ], + "y": [ + -60.62177826491068 + ] + }, + { + "mode": "text", + "showlegend": false, + "text": [ + "+ 30 MW" + ], + "type": "scatter", + "x": [ + -35.000000000000014 + ], + "y": [ + 460.6217782649107 + ] + } + ], + "layout": { + "height": 600, + "margin": { + "b": 100, + "l": 20, + "r": 20 + }, + "plot_bgcolor": "white", + "shapes": [ + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": -25, + "x1": 25, + "xref": "x", + "y0": -25, + "y1": 25, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": -25, + "x1": 25, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 175, + "x1": 225, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 375, + "x1": 425, + "xref": "x", + "y0": 375, + "y1": 425, + "yref": "y" + }, + { + "fillcolor": "lightgray", + "line": { + "color": "LightSeaGreen" + }, + "type": "circle", + "x0": 375, + "x1": 425, + "xref": "x", + "y0": -25, + "y1": 25, + "yref": "y" + }, + { + "line": { + "color": "#9b2020" + }, + "type": "line", + "x0": 1.5308084989341915e-15, + "x1": 1.5308084989341915e-15, + "xref": "x", + "y0": 25, + "y1": 375, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 11.18033988749895, + "x1": 188.81966011250105, + "xref": "x", + "y0": 22.360679774997898, + "y1": 377.6393202250021, + "yref": "y" + }, + { + "line": { + "color": "gray", + "dash": "dash" + }, + "type": "line", + "x0": 17.67766952966369, + "x1": 382.32233047033634, + "xref": "x", + "y0": 17.677669529663685, + "y1": 382.3223304703363, + "yref": "y" + }, + { + "line": { + "color": "#8b0000" + }, + "type": "line", + "x0": 25, + "x1": 375, + "xref": "x", + "y0": 0, + "y1": -3.061616997868383e-15, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 25, + "x1": 175, + "xref": "x", + "y0": 400, + "y1": 400, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 222.65769467591625, + "x1": 377.3423053240837, + "xref": "x", + "y0": 410.5654565435175, + "y1": 410.5654565435175, + "yref": "y" + }, + { + "line": { + "color": "#efc5c5" + }, + "type": "line", + "x0": 222.65769467591625, + "x1": 377.3423053240838, + "xref": "x", + "y0": 389.4345434564825, + "y1": 389.4345434564825, + "yref": "y" + }, + { + "line": { + "color": "#8b0000" + }, + "type": "line", + "x0": 400, + "x1": 400, + "xref": "x", + "y0": 375, + "y1": 25, + "yref": "y" + }, + { + "type": "line", + "x0": -50, + "x1": -25, + "xref": "x", + "y0": 6.123233995736767e-15, + "y1": 3.061616997868383e-15, + "yref": "y" + }, + { + "type": "line", + "x0": 450, + "x1": 425, + "xref": "x", + "y0": 400, + "y1": 400, + "yref": "y" + }, + { + "type": "line", + "x0": 450, + "x1": 425, + "xref": "x", + "y0": 0, + "y1": 0, + "yref": "y" + }, + { + "type": "line", + "x0": -25.000000000000025, + "x1": -12.50000000000001, + "xref": "x", + "y0": -43.30127018922192, + "y1": -21.65063509461096, + "yref": "y" + }, + { + "type": "line", + "x0": -25.00000000000001, + "x1": -12.499999999999995, + "xref": "x", + "y0": 443.3012701892219, + "y1": 421.65063509461095, + "yref": "y" + } + ], + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "width": 800, + "xaxis": { + "range": [ + -142.5, + 542.5 + ], + "zeroline": false + }, + "yaxis": { + "range": [ + -142.5, + 542.5 + ] + } + } + }, + "text/html": [ + "
\n", + " \n", + " \n", + "
\n", + " \n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "previous_act = all_obs[-2]\n", + "fig = plot_helper.plot_observation(previous_act)\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now we can know the cause for this problem: all the powerlines that could provide power on the bottom right load are overloaded in this situation, so the protection worked and disconnect them to prevent hurting the surroundings." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/grid2op/Action.py b/grid2op/Action.py index c662e197e..9a3532e6b 100644 --- a/grid2op/Action.py +++ b/grid2op/Action.py @@ -875,15 +875,16 @@ def update(self, dict_): - ``True``: change the status of the powerline: disconnect it if it was connected, connect it if it was disconnected - - "set_bus": (numpy int vector or dictionary) will set the buses to which the objects are connected. It follows a - similar interpretation than the line status vector: + - "set_bus": (numpy int vector or dictionary) will set the buses to which the objects are connected. It + follows a similar interpretation than the line status vector: - 0 -> don't change anything - +1 -> set to bus 1, - +2 -> set to bus 2, etc. - -1: You can use this method to disconnect an object by setting the value to -1. - - "change_bus": (numpy bool vector or dictionary) will change the bus to which the object is connected. True will + - "change_bus": (numpy bool vector or dictionary) will change the bus to which the object is connected. + True will change it (eg switch it from bus 1 to bus 2 or from bus 2 to bus 1). NB this is only active if the system has only 2 buses per substation. @@ -906,14 +907,13 @@ def update(self, dict_): be usable. This will lead to an :class:`grid2op.Exception.AmbiguousAction` exception. - **NB**: The length of vectors provided here is NOT check in this function. This method can be "chained" and only on the - final - action, when used, eg. in the Backend, is checked. + **NB**: The length of vectors provided here is NOT check in this function. This method can be "chained" and + only on the final action, when used, eg. in the Backend, is checked. **NB**: If a powerline is disconnected, on maintenance, or suffer an outage, the associated "set_bus" will be ignored. - Disconnection has the priority on anything. This priority is given because, in case of hazard, the hazard has - the priority over the possible actions. + Disconnection has the priority on anything. This priority is given because, in case of hazard, the hazard + has the priority over the possible actions. Examples -------- @@ -1155,7 +1155,7 @@ def _check_for_ambiguity(self): raise InvalidLineStatus("You ask to connect an extremity powerline but also to *change* the bus to which " "it is connected. This is ambiguous. You must *set* this bus instead.") - def sample(self): + def sample(self, space_prng): """ This method is used to sample action. @@ -1167,6 +1167,10 @@ def sample(self): By calling :func:`Action.sample`, the action is :func:`Action.reset` to a "do nothing" state. + Parameters + ---------- + space_prng + Returns ------- self: :class:`Action` @@ -1641,12 +1645,16 @@ def update(self, dict_): self._digest_change_status(dict_) return self - def sample(self): + def sample(self, space_prng): """ Sample a Topology action. This method is not implemented at the moment. TODO + Parameters + ---------- + space_prng + Returns ------- res: :class:`TopologyAction` @@ -1681,8 +1689,11 @@ def __init__(self, gridobj): # the injection keys is not authorized, meaning it will send a warning is someone try to implement some # modification injection. self.authorized_keys = set([k for k in self.authorized_keys - if k != "injection" and k != "set_bus" and \ - k != "change_bus" and k != "change_line_status"]) + if k != "injection" and k != "set_bus" and + k != "change_bus" and k != "change_line_status" and + k != "redispatch"]) + + self.attr_list_vect = ["_set_line_status"] def __call__(self): """ @@ -1710,7 +1721,8 @@ def __call__(self): if self._dict_inj: raise AmbiguousAction("You asked to modify the injection with an action of class \"TopologyAction\".") self._check_for_ambiguity() - return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect, self._redispatch + return {}, self._set_line_status, self._switch_line_status, self._set_topo_vect, self._change_bus_vect, \ + self._redispatch def update(self, dict_): """ @@ -1765,20 +1777,26 @@ def disambiguate_reconnection(self): self._set_topo_vect[self.line_ex_pos_topo_vect[sel_]] = 1 self._set_topo_vect[self.line_or_pos_topo_vect[sel_]] = 1 - def sample(self): + def sample(self, space_prng): """ Sample a PowerlineSwitch Action. By default, this sampling will act on one random powerline, and it will either disconnect it or reconnect it each with equal probability. + Parameters + ---------- + space_prng: ``numpy.random.RandomState`` + The pseudo random number generator of the Action space used to sample actions. + Returns ------- res: :class:`PowerLineSwitch` The sampled action """ self.reset() - i = np.random.randint(0, self.size()) # the powerline on which we can act + # TODO here use the prng state from the ActionSpace !!!! + i = space_prng.randint(0, self.size()) # the powerline on which we can act val = 2*np.random.randint(0, 2) - 1 # the action: +1 reconnect it, -1 disconnect it self._set_line_status[i] = val if val == 1: @@ -1968,7 +1986,8 @@ def change_bus(self, name_element, extremity=None, substation=None, type_element "(\"{}\")".format(type(previous_action), self.actionClass)) res = previous_action - dict_, to_sub_pos, my_id, my_sub_id = self._extract_dict_action(name_element, extremity, substation, type_element, res) + dict_, to_sub_pos, my_id, my_sub_id = self._extract_dict_action(name_element, extremity, substation, + type_element, res) dict_["change_bus"][to_sub_pos[my_id]] = True res.update({"change_bus": {"substations_id": [(my_sub_id, dict_["change_bus"])]}}) # res.update(dict_) @@ -1986,7 +2005,8 @@ def _extract_database_powerline(self, extremity): elif extremity is None: raise Grid2OpException("It is mandatory to know on which ends you want to change the bus of the powerline") else: - raise Grid2OpException("unknown extremity specifier \"{}\". Extremity should be \"or\" or \"ex\"".format(extremity)) + raise Grid2OpException("unknown extremity specifier \"{}\". Extremity should be \"or\" or \"ex\"" + "".format(extremity)) return to_subid, to_sub_pos, to_name def _extract_dict_action(self, name_element, extremity=None, substation=None, type_element=None, action=None): @@ -2088,47 +2108,6 @@ def set_bus(self, name_element, new_bus, extremity=None, substation=None, type_e res.update({"set_bus": {"substations_id": [(my_sub_id, dict_["set_bus"])]}}) return res - def reconnect_powerline(self, l_id, bus_or, bus_ex, previous_action=None): - """ - Build the valid not ambiguous action consisting in reconnecting a powerline. - - Parameters - ---------- - l_id: `int` - the powerline id to be reconnected - bus_or: `int` - the bus to which connect the origin end of the powerline - bus_ex: `int` - the bus to which connect the extremity end the powerline - - previous_action: :class:`Action`, optional - The (optional) action to update. It should be of the same type as :attr:`HelperAction.actionClass` - - Returns - ------- - res: :class:`Action` - The action with the modification implemented - - Raises - ------ - AmbiguousAction - If *previous_action* has not the same type as :attr:`HelperAction.actionClass`. - - """ - if previous_action is None: - res = self.actionClass(gridobj=self) - else: - if not isinstance(previous_action, self.actionClass): - raise AmbiguousAction("The action to update using `HelperAction` is of type \"{}\" " - "which is not the type of action handled by this helper " - "(\"{}\")".format(type(previous_action), self.actionClass)) - res = previous_action - res.update({"set_line_status": [(l_id, 1)], - "set_bus": {"lines_or_id": [(l_id, bus_or)], - "lines_ex_id": [(l_id, bus_ex)]} - }) - return res - def get_set_line_status_vect(self): """ Computes and returns a vector that can be used in the "set_status" keyword if building an :class:`Action` diff --git a/grid2op/BackendPandaPower.py b/grid2op/BackendPandaPower.py index 7d87a8c2d..7e6e68563 100644 --- a/grid2op/BackendPandaPower.py +++ b/grid2op/BackendPandaPower.py @@ -96,6 +96,10 @@ def __init__(self): self.v_ex = None self.a_ex = None + self.load_p = None + self.load_q = None + self.load_v = None + self._pf_init = "flat" self._pf_init = "results" self._nb_bus_before = 0 @@ -489,8 +493,20 @@ def runpf(self, is_dc=False): self._nb_bus_before = nb_bus if self._grid.res_gen.isnull().values.any(): + # TODO see if there is a better way here # sometimes pandapower does not detect divergence and put Nan. - raise p.powerflow.LoadflowNotConverged + raise pp.powerflow.LoadflowNotConverged + + # if self._grid.res_load.isnull().values.any(): + # # TODO see if there is a better way here + # # some loads are disconnected: it's a game over case! + # raise pp.powerflow.LoadflowNotConverged + + self.load_p, self.load_q, self.load_v = self._loads_info() + if not np.all(np.isfinite(self.load_v)): + # TODO see if there is a better way here + # some loads are disconnected: it's a game over case! + raise pp.powerflow.LoadflowNotConverged # I retrieve the data once for the flows, so has to not re read multiple dataFrame self.p_or = self._aux_get_line_info("p_from_mw", "p_hv_mw") @@ -611,12 +627,15 @@ def generators_info(self): prod_q[self._id_bus_added] += self._grid._ppc["gen"][self._iref_slack, 2] return prod_p, prod_q, prod_v - def loads_info(self): + def _loads_info(self): load_p = 1. * self._grid.res_load["p_mw"].values load_q = 1. * self._grid.res_load["q_mvar"].values load_v = self._grid.res_bus["vm_pu"].values[self._grid.load["bus"]] * self.load_pu_to_kv return load_p, load_q, load_v + def loads_info(self): + return self.load_p, self.load_q, self.load_v + def lines_or_info(self): return self.p_or, self.q_or, self.v_or, self.a_or diff --git a/grid2op/Environment.py b/grid2op/Environment.py index f4122b09b..abff57d76 100644 --- a/grid2op/Environment.py +++ b/grid2op/Environment.py @@ -15,28 +15,30 @@ Example (adapted from gym documentation available at `gym random_agent.py `_ ): ->>> import grid2op ->>> from grid2op.Agent import DoNothingAgent ->>> env = grid2op.make() ->>> agent = DoNothingAgent(env.action_space) ->>> env.seed(0) ->>> episode_count = 100 ->>> reward = 0 ->>> done = False ->>> total_reward = 0 ->>> for i in range(episode_count): ->>> ob = env.reset() ->>> while True: ->>> action = agent.act(ob, reward, done) ->>> ob, reward, done, _ = env.step(action) ->>> total_reward += reward ->>> if done: ->>> # in this case the episode is over ->>> break ->>> ->>> # Close the env and write monitor result info to disk ->>> env.close() ->>> print("The total reward was {:.2f}".format(total_reward)) +.. code-block:: python + + import grid2op + from grid2op.Agent import DoNothingAgent + env = grid2op.make() + agent = DoNothingAgent(env.action_space) + env.seed(0) + episode_count = 100 + reward = 0 + done = False + total_reward = 0 + for i in range(episode_count): + ob = env.reset() + while True: + action = agent.act(ob, reward, done) + ob, reward, done, _ = env.step(action) + total_reward += reward + if done: + # in this case the episode is over + break + + # Close the env and write monitor result info to disk + env.close() + print("The total reward was {:.2f}".format(total_reward)) """ @@ -54,6 +56,7 @@ from .Parameters import Parameters from .Backend import Backend from .ChronicsHandler import ChronicsHandler + from .Renderer import Renderer except (ModuleNotFoundError, ImportError): from Space import GridObjects from Action import HelperAction, Action, TopologyAction @@ -64,6 +67,7 @@ from Parameters import Parameters from Backend import Backend from ChronicsHandler import ChronicsHandler + from Renderer import Renderer import pdb @@ -169,6 +173,9 @@ class Environment(GridObjects): env_modification: :class:`grid2op.Action.Action` Representation of the actions of the environment for the modification of the powergrid. + current_reward: ``float`` + The reward of the current time step + TODO update with maintenance, hazards etc. see below # store actions "cooldown" times_before_line_status_actionable @@ -392,8 +399,13 @@ def __init__(self, self.metadata = {'render.modes': []} self.spec = None + self.current_reward = self.reward_range[0] + self.done = False self._reset_vectors_and_timings() + def attach_renderer(self, graph_layout): + self.viewer = Renderer(graph_layout, observation_space=self.helper_observation) + def __str__(self): return '<{} instance>'.format(type(self).__name__) # TODO be closer to original gym implementation @@ -408,14 +420,18 @@ def __enter__(self): Examples -------- - >>> import grid2op - >>> import grid2op.Agent - >>> with grid2op.make() as env: - >>> agent = grid2op.Agent.DoNothingAgent(env.action_space) - >>> act = env.action_space() - >>> obs, r, done, info = env.step(act) - >>> act = agent.act(obs, r, info) - >>> obs, r, done, info = env.step(act) + + .. code-block:: python + + import grid2op + import grid2op.Agent + with grid2op.make() as env: + agent = grid2op.Agent.DoNothingAgent(env.action_space) + act = env.action_space() + obs, r, done, info = env.step(act) + act = agent.act(obs, r, info) + obs, r, done, info = env.step(act) + """ return self @@ -940,15 +956,15 @@ def step(self, action): "is_ambiguous": is_ambiguous, "is_dipatching_illegal": is_illegal_redisp, "is_illegal_reco": is_illegal_reco} + self.done = self._is_done(has_error, is_done) + self.current_reward = self._get_reward(action, + has_error, + is_done, + is_illegal or is_illegal_redisp or is_illegal_reco, + is_ambiguous) + # TODO documentation on all the possible way to be illegal now - return self.current_obs,\ - self._get_reward(action, - has_error, - is_done, - is_illegal or is_illegal_redisp or is_illegal_reco, - is_ambiguous),\ - self._is_done(has_error, is_done),\ - infos + return self.current_obs, self.current_reward, self.done, infos def _reset_vectors_and_timings(self): """ @@ -985,6 +1001,10 @@ def _reset_vectors_and_timings(self): self._time_powerflow = 0 self._time_extract_obs = 0 + # reward and others + self.current_reward = self.reward_range[0] + self.done = False + def _reset_redispatching(self): # redispatching self.target_dispatch = np.full(shape=self.n_gen, dtype=np.float, fill_value=0.) @@ -1018,7 +1038,8 @@ def reset(self): def render(self, mode='human'): # TODO here, and reuse pypownet - pass + if self.viewer is not None: + self.viewer.render(self.current_obs, reward=self.current_reward, timestamp=self.time_stamp, done=self.done) def close(self): # todo there might be some side effect diff --git a/grid2op/PlotPlotly.py b/grid2op/PlotPlotly.py index e8860ea0c..0e74a3cc8 100644 --- a/grid2op/PlotPlotly.py +++ b/grid2op/PlotPlotly.py @@ -291,6 +291,10 @@ def _get_gen_name(self, sub_id, g_id): p_nm = 'gen_{}_{}'.format(sub_id, g_id) return p_nm + def plot_observation(self, observation, fig=None): + res = self.get_plot_observation(observation, fig=fig) + return res + def get_plot_observation(self, observation, fig=None): """ Plot the given observation in the given figure. diff --git a/grid2op/Renderer.py b/grid2op/Renderer.py new file mode 100644 index 000000000..3b27860c7 --- /dev/null +++ b/grid2op/Renderer.py @@ -0,0 +1,589 @@ +import numpy as np +import cmath +import math # for regular real sqrt +import time +import pdb + +try: + import pygame + import seaborn as sns + can_plot = True +except: + can_plot = False + pass + +__all__ = ["Renderer"] + + +class Point: + # https://codereview.stackexchange.com/questions/70143/drawing-a-dashed-line-with-pygame + # constructed using a normal tupple + def __init__(self, point_t = (0,0)): + self.x = float(point_t[0]) + self.y = float(point_t[1]) + + # define all useful operators + def __add__(self, other): + return Point((self.x + other.x, self.y + other.y)) + + def __sub__(self, other): + return Point((self.x - other.x, self.y - other.y)) + + def __mul__(self, scalar): + return Point((self.x*scalar, self.y*scalar)) + + def __div__(self, scalar): + return Point((self.x/scalar, self.y/scalar)) + + def __floordiv__(self, scalar): + return Point((self.x/scalar, self.y/scalar)) + + def __truediv__(self, scalar): + return Point((self.x/scalar, self.y/scalar)) + + def __len__(self): + return int(math.sqrt(self.x**2 + self.y**2)) + + # get back values in original tuple format + def get(self): + return (self.x, self.y) + + def to_cplx(self): + return self.x + 1j * self.y + + @staticmethod + def from_cplx(cplx): + return Point((cplx.real, cplx.imag)) + + +def draw_dashed_line(surf, color, start_pos, end_pos, width=1, dash_length=10): + # https://codereview.stackexchange.com/questions/70143/drawing-a-dashed-line-with-pygame + origin = Point(start_pos) + target = Point(end_pos) + displacement = target - origin + length = len(displacement) + slope = displacement/length + + for index in range(0, int(length/dash_length), 2): + start = origin + (slope * index * dash_length) + end = origin + (slope * (index + 1) * dash_length) + pygame.draw.line(surf, color, start.get(), end.get(), width) + + +def draw_arrow(surf, color, start_pos, end_pos, positive_flow, width=1, num_arrows=10, length_arrow=15, angle_arrow=30): + if positive_flow: + origin = Point(start_pos) + target = Point(end_pos) + else: + target = Point(start_pos) + origin = Point(end_pos) + + displacement = target - origin + length = len(displacement) + slope = displacement/length + + # phi = cmath.phase(slope.to_cplx()) * 360 / 2*cmath.pi + phi = cmath.phase(displacement.to_cplx()) * 360 / (2*cmath.pi) + cste_ = 2*cmath.pi / 360 * 1j + rotatedown = cmath.exp(cste_ * (180 + phi + angle_arrow) ) + rotateup = cmath.exp(cste_ * (180 + phi - angle_arrow) ) + + first_arrow_part = length_arrow*rotateup + second_arrow_part = length_arrow*rotatedown + + per_displ = displacement / (num_arrows+1) + for index in range(0, int(num_arrows)): + mid = origin + (per_displ * (index + 1) ) + start_arrow = Point.from_cplx(mid.to_cplx() + first_arrow_part) + end_arrow = Point.from_cplx(mid.to_cplx() + second_arrow_part) + # , end_arrow.get() + pygame.draw.lines(surf, color, False, + [start_arrow.get(), mid.get(), end_arrow.get()], + width) + + +class Renderer(object): + """ + TODO + """ + def __init__(self, substation_layout, + observation_space, + radius_sub=25., + load_prod_dist=70., + bus_radius=4., + timestep_duration_seconds=1., + fontsize=20): + """ + + Parameters + ---------- + substation_layout: ``list`` + List of tupe given the position of each of the substation of the powergrid. + + observation_space: :class:`grid2op.Observation.ObservationHelper` + Observation space + + """ + if not can_plot: + raise RuntimeError("Impossible to plot as pygame cannot be imported.") + + self.observation_space = observation_space + + # pygame + pygame.init() + self.video_width, self.video_height = 1300, 700 + self.timestep_duration_seconds = timestep_duration_seconds + self.screen = pygame.display.set_mode((self.video_width, self.video_height), pygame.RESIZABLE) + pygame.display.set_caption('Grid2Op Renderer') # Window title + self.window_grid = (1000, 700) + self.background_color = [70, 70, 73] + self.screen.fill(self.background_color) + self.lag_x = 150 + self.lag_y = 100 + self.font = pygame.font.Font(None, fontsize) + self.font_pause = pygame.font.Font(None, 30) + + # graph layout + # convert the layout that is given in standard mathematical orientation, to pygame representation (y axis + # inverted) + self._layout = {} + tmp = [(el1, -el2) for el1, el2 in substation_layout] + + # then scale the grid to be on the window, with the proper margin (careful, margin are applied both left and r + # and right, so count twice + tmp_arr = np.array(tmp) + min_ = tmp_arr.min(axis=0) + max_ = tmp_arr.max(axis=0) + b = min_ + a = max_ - min_ + tmp = [(int((el1- b[0]) / a[0] * (self.window_grid[0] - 2*self.lag_x)) + self.lag_x, + int((el2 - b[1]) / a[1] * (self.window_grid[1] - 2*self.lag_y)) + self.lag_y) + for el1, el2 in tmp] + + self._layout["substations"] = tmp + self.subs_elements = [None for _ in observation_space.sub_info] + + self.radius_sub = radius_sub + self.load_prod_dist = load_prod_dist # distance between load and generator to the center of the substation + self.bus_radius = bus_radius + # get the element in each substation + for sub_id in range(observation_space.sub_info.shape[0]): + this_sub = {} + objs = observation_space.get_obj_connect_to(substation_id=sub_id) + + for c_id in objs["loads_id"]: + c_nm = self._get_load_name(sub_id, c_id) + this_load = {} + this_load["type"] = "load" + this_load["sub_pos"] = observation_space.load_to_sub_pos[c_id] + this_sub[c_nm] = this_load + + for g_id in objs["generators_id"]: + g_nm = self._get_gen_name(sub_id, g_id) + this_gen = {} + this_gen["type"] = "gen" + this_gen["sub_pos"] = observation_space.gen_to_sub_pos[g_id] + this_sub[g_nm] = this_gen + + for lor_id in objs["lines_or_id"]: + ext_id = observation_space.line_ex_to_subid[lor_id] + l_nm = self._get_line_name(sub_id, ext_id, lor_id) + this_line = {} + this_line["type"] = "line" + this_line["sub_pos"] = observation_space.line_or_to_sub_pos[lor_id] + this_sub[l_nm] = this_line + + for lex_id in objs["lines_ex_id"]: + or_id = observation_space.line_or_to_subid[lex_id] + l_nm = self._get_line_name(or_id, sub_id, lex_id) + this_line = {} + this_line["type"] = "line" + this_line["sub_pos"] = observation_space.line_ex_to_sub_pos[lex_id] + this_sub[l_nm] = this_line + + self.subs_elements[sub_id] = this_sub + + def _get_line_name(self, subor_id, sub_ex_id, line_id): + l_nm = 'l_{}_{}_{}'.format(subor_id, sub_ex_id, line_id) + return l_nm + + def _get_load_name(self, sub_id, c_id): + c_nm = "load_{}_{}".format(sub_id, c_id) + return c_nm + + def _get_gen_name(self, sub_id, g_id): + p_nm = 'gen_{}_{}'.format(sub_id, g_id) + return p_nm + + def event_looper(self, force=False): + # TODO from https://github.com/MarvinLer/pypownet/blob/master/pypownet/environment.py + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + exit() + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + pygame.quit() + exit() + if event.key == pygame.K_SPACE: + # pause_surface = self.draw_plot_pause() + # self.screen.blit(pause_surface, (320 + self.left_menu_shape[0], 320)) + # pygame.display.flip() + return not force + return force + + def render(self, obs, reward=None, done=None, timestamp=None): + # TODO from https://github.com/MarvinLer/pypownet/blob/master/pypownet/environment.py + force = self.event_looper(force=False) + while self.event_looper(force=force): + pass + + if not "line" in self._layout: + # update the layout of the objects only once to ensure the same positionning is used + # if more than 1 observation are displayed one after the other. + self._compute_layout(obs) + + # The game is not paused anymore (or never has been), I can render the next surface + self.screen.fill(self.background_color) + + # draw the state now + self._draw_generic_info(reward, done, timestamp) + self._draw_subs(observation=obs) + self._draw_powerlines(observation=obs) + self._draw_loads(observation=obs) + self._draw_gens(observation=obs) + + pygame.display.flip() + + def _draw_generic_info(self, reward=None, done=None, timestamp=None): + color = pygame.Color(255, 255, 255) + if reward is not None: + text_label = "Instantaneous reward: {:.1f}".format(reward) + text_graphic = self.font.render(text_label, True, color) + self.screen.blit(text_graphic, (self.window_grid[0]+100, 100)) + if done is not None: + pass + + if timestamp is not None: + text_label = "Date : {:%Y-%m-%d %H:%M}".format(timestamp) + text_graphic = self.font.render(text_label, True, color) + self.screen.blit(text_graphic, (self.window_grid[0]+100, 200)) + + def _draw_subs(self, observation): + for i, el in enumerate(self._layout["substations"]): + self._draw_sub(el, radius=self.radius_sub) + + def _draw_sub(self, center, radius): + pygame.draw.circle(self.screen, + pygame.Color(255, 255, 255), + [int(el) for el in center], + int(radius), + 2) + + def _draw_powerlines(self, observation): + + for line_id, (rho, status, p_or) in enumerate(zip(observation.rho, observation.line_status, observation.p_or)): + # the next 5 lines are always the same, for each observation, it makes sense to compute it once + # and then reuse it + + sub_or_id, sub_ex_id = self._layout["line"][line_id] + + l_nm = self._get_line_name(sub_or_id, sub_ex_id, line_id) + pos_or = self.subs_elements[sub_or_id][l_nm]["pos"] + pos_ex = self.subs_elements[sub_ex_id][l_nm]["pos"] + + if not status: + # line is disconnected + draw_dashed_line(self.screen, pygame.Color(0, 0, 0), pos_or, pos_ex) + else: + # line is connected + + # step 0: compute thickness and color + rho_max = 1.5 # TODO here get it back from parameters, or environment or whatever + + if rho < (rho_max / 1.5): + amount_green = 255 - int(255. * 1.5 * rho / rho_max) + else: + amount_green = 0 + + amount_red = int(255 - (50 + int(205. * rho / rho_max))) + + color = pygame.Color(amount_red, amount_green, 20) + + width = 1 + if rho > rho_max: + width = 4 + elif rho > 1.: + width = 3 + elif rho > 0.9: + width = 2 + width += 3 + + # step 1: draw the powerline with right color and thickness + pygame.draw.line(self.screen, color, pos_or, pos_ex, width) + + # step 2: draw arrows indicating current flows + draw_arrow(self.screen, color, pos_or, pos_ex, + p_or >= 0., + num_arrows=width, + width=width) + + def _get_position(self, theta): + quarter_pi = cmath.pi / 4 + half_pi = cmath.pi / 2. + + if theta >= -quarter_pi and theta < quarter_pi: + res = "center|left" + elif theta >= quarter_pi and theta < quarter_pi + half_pi: + res = "up|center" + elif theta >= quarter_pi + half_pi and theta < quarter_pi + 2. * half_pi: + res = "center|right" + else: + res = "down|center" + + return res + + def _aligned_text(self, pos, text_graphic, pos_text): + pos_x = pos_text.real + pos_y = pos_text.imag + width = text_graphic.get_width() + height = text_graphic.get_height() + + if pos == "center|left": + pos_y -= height // 2 + elif pos == "up|center": + pos_x -= width // 2 + pos_y -= height + elif pos == "center|right": + pos_x -= width + pos_y -= height // 2 + elif pos == "down|center": + pos_x -= width // 2 + self.screen.blit(text_graphic, (pos_x, pos_y)) + + def _draw_loads(self, observation): + for c_id, por in enumerate(observation.load_p): + state = observation.state_of(load_id=c_id) + sub_id = state["sub_id"] + c_nm = self._get_load_name(sub_id, c_id) + + if not "elements_display" in self.subs_elements[sub_id][c_nm]: + pos_load_sub = self.subs_elements[sub_id][c_nm]["pos"] + pos_center_sub = self._layout["substations"][sub_id] + + z_sub = (pos_center_sub[0] + 1j * pos_center_sub[1]) + theta = cmath.phase((self.subs_elements[sub_id][c_nm]["z"] - z_sub)) + pos_load = z_sub + cmath.exp(1j * theta) * self.load_prod_dist + + # position of the end of the line connecting the object to the substation + pos_end_line = pos_load - cmath.exp(1j * theta) * 20 + pos = self._get_position(theta) + tmp_dict = {"pos_end_line": pos_end_line, + "pos_load_sub": pos_load_sub, + "pos_load": pos_load, "pos": pos} + self.subs_elements[sub_id][c_nm]["elements_display"] = tmp_dict + else: + dict_element = self.subs_elements[sub_id][c_nm]["elements_display"] + pos_end_line = dict_element["pos_end_line"] + pos_load_sub = dict_element["pos_load_sub"] + pos_load = dict_element["pos_load"] + pos = dict_element["pos"] + + color = pygame.Color(0, 0, 0) + width = 2 + pygame.draw.line(self.screen, color, pos_load_sub, (pos_end_line.real, pos_end_line.imag), width) + text_label = "- {:.1f} MW".format(por) + text_graphic = self.font.render(text_label, True, color) + self._aligned_text(pos, text_graphic, pos_load) + + def _draw_gens(self, observation): + for g_id, por in enumerate(observation.prod_p): + state = observation.state_of(gen_id=g_id) + sub_id = state["sub_id"] + g_nm = self._get_gen_name(sub_id, g_id) + + if not "elements_display" in self.subs_elements[sub_id][g_nm]: + pos_load_sub = self.subs_elements[sub_id][g_nm]["pos"] + pos_center_sub = self._layout["substations"][sub_id] + + z_sub = (pos_center_sub[0] + 1j * pos_center_sub[1]) + theta = cmath.phase((self.subs_elements[sub_id][g_nm]["z"] - z_sub)) + pos_load = z_sub + cmath.exp(1j * theta) * self.load_prod_dist + + pos = self._get_position(theta) + # position of the end of the line connecting the object to the substation + pos_end_line = pos_load - cmath.exp(1j * theta) * 20 + tmp_dict = {"pos_end_line": pos_end_line, "pos_load_sub": pos_load_sub, "pos_load": pos_load, + "pos": pos} + self.subs_elements[sub_id][g_nm]["elements_display"] = tmp_dict + else: + dict_element = self.subs_elements[sub_id][g_nm]["elements_display"] + pos_end_line = dict_element["pos_end_line"] + pos_load_sub = dict_element["pos_load_sub"] + pos_load = dict_element["pos_load"] + pos = dict_element["pos"] + + color = pygame.Color(0, 0, 0) + width = 2 + pygame.draw.line(self.screen, color, pos_load_sub, (pos_end_line.real, pos_end_line.imag), width) + text_label = "+ {:.1f} MW".format(por) + text_graphic = self.font.render(text_label, True, color) + self._aligned_text(pos, text_graphic, pos_load) + + def _draw_topos(self, observation, fig): + #TODO copy paste from plotplotly + res_topo = [] + for sub_id, elements in enumerate(self.subs_elements): + pos_center_sub = self._layout["substations"][sub_id] + z_sub = (pos_center_sub[0] + 1j * pos_center_sub[1]) + + tmp = observation.state_of(substation_id=sub_id) + if tmp["nb_bus"] == 1: + # not to overload the plot, if everything is at the same bus, i don't plot it + continue + # I have at least 2 buses + + # I compute the position of each elements + bus_vect = tmp["topo_vect"] + + # i am not supposed to have more than 2 buses + buses_z = [None, None] # center of the different buses + nb_co = [0, 0] # center of the different buses + + # the position of a bus is for now the average of all the elements in there + for el_nm, dict_el in elements.items(): + this_el_bus = bus_vect[dict_el["sub_pos"]] - 1 + if this_el_bus >= 0: + nb_co[this_el_bus] += 1 + if buses_z[this_el_bus] is None: + buses_z[this_el_bus] = dict_el["z"] + else: + buses_z[this_el_bus] += dict_el["z"] + buses_z = [el / nb for el, nb in zip(buses_z, nb_co)] + theta_z = [cmath.phase((el - z_sub)) for el in buses_z] + m_ = np.mean(theta_z) - cmath.pi / 2 + theta_z = [el-m_ for el in theta_z] + buses_z = [z_sub + (self.radius_sub-self.bus_radius)*0.75*cmath.exp(1j * theta) for theta in theta_z] + + # TODO don't just do the average, but afterwards split it more evenly, and at a fixed distance from the + # center of the substation + + # I plot the buses + for bus_id, z_bus in enumerate(buses_z): + bus_color = '#ff7f0e' if bus_id == 0 else '#1f77b4' + res = go.layout.Shape( + type="circle", + xref="x", + yref="y", + x0=z_bus.real - self.bus_radius, + y0=z_bus.imag - self.bus_radius, + x1=z_bus.real + self.bus_radius, + y1=z_bus.imag + self.bus_radius, + fillcolor=bus_color, + line_color=bus_color, + ) + res_topo.append(res) + # i connect every element to the proper bus with the proper color + for el_nm, dict_el in elements.items(): + this_el_bus = bus_vect[dict_el["sub_pos"]] -1 + if this_el_bus >= 0: + res = go.layout.Shape( + type="line", + xref="x", + yref="y", + x0=dict_el["z"].real, + y0=dict_el["z"].imag, + x1=buses_z[this_el_bus].real, + y1=buses_z[this_el_bus].imag, + line=dict(color='#ff7f0e' if this_el_bus == 0 else '#1f77b4')) + res_topo.append(res) + return res_topo + + def _compute_layout(self, observation): + #TODO copy paste from plotplotly + """ + Compute the position of each of the objects. + + Parameters + ---------- + observation: :class:`grid2op.Observation.Observation` + The observation used to know which object belong where. + + Returns + ------- + + """ + self._layout["line"] = {} + + # assign powerline coordinates + for line_id in range(len(observation.rho)): + if line_id not in self._layout["line"]: + state = observation.state_of(line_id=line_id) + sub_or_id = state["origin"]["sub_id"] + sub_ex_id = state["extremity"]["sub_id"] + pos_or = self._layout["substations"][sub_or_id] + pos_ex = self._layout["substations"][sub_ex_id] + + # make sure the powerline are connected to the circle of the substation and not to the center of it + z_or_tmp = pos_or[0] + 1j * pos_or[1] + z_ex_tmp = pos_ex[0] + 1j * pos_ex[1] + + module_or = cmath.phase(z_ex_tmp - z_or_tmp) + module_ex = cmath.phase(- (z_ex_tmp - z_or_tmp)) + + # check parrallel lines: + # for now it works only if there are 2 parrallel lines. The idea is to add / withdraw + # 10° for each module in this case. + # TODO draw line but not straight line in this case, this looks ugly for now :-/ + deg_parrallel = 25 + tmp_parrallel = self.observation_space.get_lines_id(from_=sub_or_id, to_=sub_ex_id) + if len(tmp_parrallel) > 1: + if line_id == tmp_parrallel[0]: + module_or += deg_parrallel / 360 * 2 * cmath.pi + module_ex -= deg_parrallel / 360 * 2 * cmath.pi + else: + module_or -= deg_parrallel / 360 * 2 * cmath.pi + module_ex += deg_parrallel / 360 * 2 * cmath.pi + + z_or = z_or_tmp + self.radius_sub * cmath.exp(module_or * 1j) + z_ex = z_ex_tmp + self.radius_sub * cmath.exp(module_ex * 1j) + pos_or = z_or.real, z_or.imag + pos_ex = z_ex.real, z_ex.imag + self._layout["line"][line_id] = sub_or_id, sub_ex_id + # TODO here get proper name + l_nm = self._get_line_name(sub_or_id, sub_ex_id, line_id) + + self.subs_elements[sub_or_id][l_nm]["pos"] = pos_or + self.subs_elements[sub_or_id][l_nm]["z"] = z_or + self.subs_elements[sub_ex_id][l_nm]["pos"] = pos_ex + self.subs_elements[sub_ex_id][l_nm]["z"] = z_ex + + # assign loads and generators coordinates + # this is done by first computing the "optimal" placement if there were only substation (so splitting equally + # the objects around the circle) and then remove the closest position that are taken by the powerlines. + for sub_id, elements in enumerate(self.subs_elements): + nb_el = len(elements) + + # equally split + pos_sub = self._layout["substations"][sub_id] + z_sub = pos_sub[0] + 1j * pos_sub[1] + pos_possible = [self.radius_sub * cmath.exp(1j * 2 * cmath.pi * i / nb_el) + z_sub + for i in range(nb_el)] + + # remove powerlines (already assigned) + for el_nm, dict_el in elements.items(): + if dict_el["type"] == "line": + z = dict_el["z"] + closest = np.argmin([abs(pos - z)**2 for pos in pos_possible]) + pos_possible = [el for i, el in enumerate(pos_possible) if i != closest] + + i = 0 + # now assign load and generator + for el_nm, dict_el in elements.items(): + if dict_el["type"] != "line": + dict_el["pos"] = (pos_possible[i].real, pos_possible[i].imag) + dict_el["z"] = pos_possible[i] + i += 1 + + + diff --git a/grid2op/Settings_L2RPN2019.py b/grid2op/Settings_L2RPN2019.py index 0d6a775aa..7fbd85aa2 100644 --- a/grid2op/Settings_L2RPN2019.py +++ b/grid2op/Settings_L2RPN2019.py @@ -364,13 +364,17 @@ def from_vect(self, vect): # self._set_topo_vect[self.line_ex_pos_topo_vect[sel_]] = 1 # self._set_topo_vect[self.line_or_pos_topo_vect[sel_]] = 1 - def sample(self): + def sample(self, space_prng): """ Sample a PowerlineSwitch Action. By default, this sampling will act on one random powerline, and it will either disconnect it or reconnect it each with equal probability. + Parameters + ---------- + space_prng + Returns ------- res: :class:`PowerLineSwitch` diff --git a/grid2op/Space.py b/grid2op/Space.py index e19662112..f7dd46107 100644 --- a/grid2op/Space.py +++ b/grid2op/Space.py @@ -1,8 +1,8 @@ """ -This class abstract the main components of Action, Observation, ActionSpace and ObservationSpace. +This class abstracts the main components of Action, Observation, ActionSpace, and ObservationSpace. -It basically represents a powergrid (the object in it) in a format completely agnostic to the solver use to compute -the powerflows (:class:`grid2op.Backend.Backend`). +It represents a powergrid (the object in it) in a format completely agnostic to the solver used to compute +the power flows (:class:`grid2op.Backend.Backend`). """ import re @@ -30,7 +30,7 @@ class GridObjects: """ This class stores in a Backend agnostic way some information about the powergrid. - It stores information about number of objects, and which objects are where, their names etc. + It stores information about numbers of objects, and which objects are where, their names, etc. The classes :class:`grid2op.Action.Action`, :class:`grid2op.Action.HelperAction`, :class:`grid2op.Observation.Observation`, :class:`grid2op.Observation.ObservationHelper` and @@ -44,7 +44,7 @@ class GridObjects: (production), an end of a powerline (each powerline have exactly two extremities: "origin" (or) and "extremity" (ext)). - every "object" (see above) is connected to a unique substation. Each substation then counts a given (fixed) - number of objects connected to it. [in ths platform we don't consider the possibility to build new "objects" as + number of objects connected to it. [in this platform we don't consider the possibility to build new "objects" as of today] For each object, the bus to which it is connected is given in the `*_to_subid` (for @@ -52,13 +52,13 @@ class GridObjects: connected) We suppose that, at every substation, each object (if connected) can be connected to either "busbar" 1 or - "busbar" 2. This means that, at maximum, there are 2 inedpendant buses for each substation. + "busbar" 2. This means that, at maximum, there are 2 independent buses for each substation. With this hypothesis, we can represent (thought experiment) each substation by a vector. This vector has as many components than the number of objects in the substation (following the previous example, the vector representing the first substation would have 5 components). And each component of this vector would represent a fixed element in it. For example, if say, the load with id 1 is connected to the first element, there would be - a unique component saying if load with id 1 is connected to busbar 1 or busbar 2. For the generators, this + a unique component saying if the load with id 1 is connected to busbar 1 or busbar 2. For the generators, this id in this (fictive) vector is indicated in the :attr:`GridObjects.gen_to_sub_pos` vector. For example the first position of :attr:`GridObjects.gen_to_sub_pos` indicates on which component of the (fictive) vector representing the substation 1 to look to know on which bus the first generator is connected. @@ -66,10 +66,10 @@ class GridObjects: We define the "topology" as the busbar to which each object is connected: each object being connected to either busbar 1 or busbar 2, this topology can be represented by a vector of fixed size (and it actually is in :attr:`grid2op.Observation.Observation.topo_vect` or in :func:`grid2op.Backend.Backend.get_topo_vect`). There are - multiple ways to make such vector. We decided to concatenate all the (fictive) vectors described above. This - concatenation represent the actual topology of this powergrid at a given timestep. This class doesn't store this + multiple ways to make such a vector. We decided to concatenate all the (fictive) vectors described above. This + concatenation represents the actual topology of this powergrid at a given timestep. This class doesn't store this information (see :class:`grid2op.Observation.Observation` for such purpose). - This entails taht: + This entails that: - the bus to which each object on a substation will be stored in consecutive components of such a vector. For example, if the first substation of the grid has 5 elements connected to it, then the first 5 elements of @@ -85,7 +85,7 @@ class GridObjects: i) retrieve the substation to which this object is connected (for example looking at :attr:`GridObjects.line_or_to_subid` [l_id] to know on which substation is connected the origin of powerline with id $l_id$.) ii) once this substation id is known, compute which are the components of the topological vector that encodes information about this substation. For example, if the substation id `sub_id` is 4, we a) count the number of elements in substations with id 0, 1, 2 and 3 (say it's 42) we know, by definition that the substation 4 is encoded in ,:attr:`grid2op.Observation.Observation.topo_vect` starting at component 42 and b) this substations has :attr:`GridObjects.sub_info` [sub_id] elements (for the sake of the example say it's 5) then the end of the vector for substation 4 will be 42+5 = 47. Finally, we got the representation of the "local topology" of the substation 4 by looking at :attr:`grid2op.Observation.Observation.topo_vect` [42:47]. - iii) retrieve which component of this vector of dimension 5 (remember we assumed substation 4 had 5 elements) encodes information about the origin end of line with id `l_id`. This information is given in :attr:`GridObjects.line_or_to_sub_pos` [l_id]. This is a number between 0 and 4, say it's 3. and 3 being the index of the object in the substation) + iii) retrieve which component of this vector of dimension 5 (remember we assumed substation 4 had 5 elements) encodes information about the origin end of the line with id `l_id`. This information is given in :attr:`GridObjects.line_or_to_sub_pos` [l_id]. This is a number between 0 and 4, say it's 3. 3 being the index of the object in the substation) - method 2 (not recommended): all of the above is stored (for the same powerline) in the :attr:`GridObjects.line_or_pos_topo_vect` [l_id]. In the example above, we will have: @@ -116,12 +116,23 @@ class GridObjects: - :attr:`GridObjects.gen_to_sub_pos` - :attr:`GridObjects.line_or_to_sub_pos` - :attr:`GridObjects.line_ex_to_sub_pos` - - :attr:`GridObjects.gen_type` - - :attr:`GridObjects.gen_pmin` - - :attr:`GridObjects.gen_pmax` - - :attr:`GridObjects.gen_redispatchable` - - :attr:`GridObjects.gen_max_ramp_up` - - :attr:`GridObjects.gen_max_ramp_down` + + Note that if you want to model an environment with unit commitment or redispatching capabilities, you also need + to provide the following attributes: + + - :attr:`GridObjects.gen_type` + - :attr:`GridObjects.gen_pmin` + - :attr:`GridObjects.gen_pmax` + - :attr:`GridObjects.gen_redispatchable` + - :attr:`GridObjects.gen_max_ramp_up` + - :attr:`GridObjects.gen_max_ramp_down` + - :attr:`GridObjects.gen_min_uptime` + - :attr:`GridObjects.gen_min_downtime` + - :attr:`GridObjects.gen_cost_per_MW` + - :attr:`GridObjects.gen_startup_cost` + - :attr:`GridObjects.gen_shutdown_cost` + + These information are loaded using the :func:`grid2op.Backend.Backend.load_redispacthing_data` method. A call to the function :func:`GridObjects._compute_pos_big_topo` allow to compute the \*_pos_topo_vect attributes (for example :attr:`GridObjects.line_ex_pos_topo_vect`) can be computed from this data. @@ -133,7 +144,7 @@ class GridObjects: ---------- n_line: :class:`int` - number of powerline in the powergrid + number of powerlines in the powergrid n_gen: :class:`int` number of generators in the powergrid @@ -145,32 +156,33 @@ class GridObjects: number of loads in the powergrid dim_topo: :class:`int` - Total number of objects in the powergrid. This is also the dimension of the "topology vector" defined above. + The total number of objects in the powergrid. This is also the dimension of the "topology vector" defined above. sub_info: :class:`numpy.ndarray`, dtype:int for each substation, gives the number of elements connected to it load_to_subid: :class:`numpy.ndarray`, dtype:int for each load, gives the id the substation to which it is connected. For example, - :attr:`GridObjects.load_to_subid`[load_id] gives the id of the substation to which the load of id + :attr:`GridObjects.load_to_subid` [load_id] gives the id of the substation to which the load of id `load_id` is connected. gen_to_subid: :class:`numpy.ndarray`, dtype:int for each generator, gives the id the substation to which it is connected line_or_to_subid: :class:`numpy.ndarray`, dtype:int - for each lines, gives the id the substation to which its "origin" end is connected + for each line, gives the id the substation to which its "origin" end is connected line_ex_to_subid: :class:`numpy.ndarray`, dtype:int - for each lines, gives the id the substation to which its "extremity" end is connected + for each line, gives the id the substation to which its "extremity" end is connected load_to_sub_pos: :class:`numpy.ndarray`, dtype:int - The topology if of the subsation *i* is given by a vector, say *sub_topo_vect* of size - :attr:`GridObjects.sub_info`\[i\]. For a given load of id *l*, - :attr:`Action.GridObjects.load_to_sub_pos`\[l\] is the index - of the load *l* in the vector *sub_topo_vect*. This means that, if - *sub_topo_vect\[ action.load_to_sub_pos\[l\] \]=2* - then load of id *l* is connected to the second bus of the substation. + Suppose you represent the topoology of the substation *s* with a vector (each component of this vector will + represent an object connected to this substation). This vector has, by definition the size + :attr:`GridObject.sub_info` [s]. `load_to_sub_pos` tells which component of this vector encodes the + current load. Suppose that load of id `l` is connected to the substation of id `s` (this information is + stored in :attr:`GridObjects.load_to_subid` [l]), then if you represent the topology of the substation + `s` with a vector `sub_topo_vect`, then "`sub_topo_vect` [ :attr:`GridObjects.load_to_subid` [l] ]" will encode + on which bus the load of id `l` is stored. gen_to_sub_pos: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_to_sub_pos` but for generators. @@ -182,13 +194,12 @@ class GridObjects: same as :attr:`GridObjects.load_to_sub_pos` but for "extremity" end of powerlines. load_pos_topo_vect: :class:`numpy.ndarray`, dtype:int - It has a similar role as :attr:`GridObjects.load_to_sub_pos` but it gives the position in the vector representing - the whole topology. More concretely, if the complete topology of the powergrid is represented here by a vector - *full_topo_vect* resulting of the concatenation of the topology vector for each substation - (see :attr:`GridObjects.load_to_sub_pos`for more information). For a load of id *l* in the powergrid, - :attr:`GridObjects.load_pos_topo_vect`\[l\] gives the index, in this *full_topo_vect* that concerns load *l*. - More formally, if *_topo_vect\[ backend.load_pos_topo_vect\[l\] \]=2* then load of id l is connected to the - second bus of the substation. + The topology if the entire grid is given by a vector, say *topo_vect* of size + :attr:`GridObjects.dim_topo`. For a given load of id *l*, + :attr:`GridObjects.load_to_sub_pos` [l] is the index + of the load *l* in the vector :attr:`grid2op.Observation.Observation.topo_vect` . This means that, if + "`topo_vect` [ :attr:`GridObjects.load_pos_topo_vect` \[l\] ]=2" + then load of id *l* is connected to the second bus of the substation. gen_pos_topo_vect: :class:`numpy.ndarray`, dtype:int same as :attr:`GridObjects.load_pos_topo_vect` but for generators. @@ -200,10 +211,10 @@ class GridObjects: same as :attr:`GridObjects.load_pos_topo_vect` but for "extremity" end of powerlines. name_load: :class:`numpy.ndarray`, dtype:str - ordered name of the loads in the grid. + ordered names of the loads in the grid. name_gen: :class:`numpy.ndarray`, dtype:str - ordered name of the productions in the grid. + ordered names of the productions in the grid. name_line: :class:`numpy.ndarray`, dtype:str ordered names of the powerline in the grid. @@ -213,14 +224,14 @@ class GridObjects: attr_list_vect: ``list`` List of string. It represents the attributes that will be stored to/from vector when the Observation is converted - to / from it. This parameter is also used to compute automatically :func:`GridObjects.dtype` and + to/from it. This parameter is also used to compute automatically :func:`GridObjects.dtype` and :func:`GridObjects.shape` as well as :func:`GridObjects.size`. If this class is derived, then it's really important that this vector is properly set. All the attributes with the name on this vector should have - consistently the same size and shape, otherwise some methods will not behave as expected. + consistently the same size and shape, otherwise, some methods will not behave as expected. _vectorized: :class:`numpy.ndarray`, dtype:float The representation of the GridObject as a vector. See the help of :func:`GridObjects.to_vect` and - :func:`GridObjects.from_vect` for more information. **NB** for performance reason, the convertion of the internal + :func:`GridObjects.from_vect` for more information. **NB** for performance reason, the conversion of the internal representation to a vector is not performed at any time. It is only performed when :func:`GridObjects.to_vect` is called the first time. Otherwise, this attribute is set to ``None``. @@ -253,19 +264,19 @@ class GridObjects: for unit commitment problems or redispacthing action. gen_min_uptime: :class:`numpy.ndarray`, dtype:float - Minimum time (expressed in number of timesteps) a generator need to be turned on: it's not possible to + The minimum time (expressed in the number of timesteps) a generator needs to be turned on: it's not possible to turn off generator `gen_id` that has been turned on less than `gen_min_time_on` [`gen_id`] timesteps ago. Optional. Used for unit commitment problems or redispacthing action. gen_min_downtime: :class:`numpy.ndarray`, dtype:float - Minimum time (expressed in number of timesteps) a generator need to be turned off: it's not possible to + The minimum time (expressed in the number of timesteps) a generator needs to be turned off: it's not possible to turn on generator `gen_id` that has been turned off less than `gen_min_time_on` [`gen_id`] timesteps ago. Optional. Used for unit commitment problems or redispacthing action. gen_cost_per_MW: :class:`numpy.ndarray`, dtype:float - For each generator, it gives the "operating cost", eg the cost, in term of "used currency" for the production of + For each generator, it gives the "operating cost", eg the cost, in terms of "used currency" for the production of one MW with this generator, if it is already turned on. It's a positive real number. It's the marginal cost for each MW. Optional. Used for unit commitment problems or redispacthing action. @@ -359,7 +370,7 @@ def _raise_error_attr_list_none(self): Raises ------- - NotImplementedError + ``NotImplementedError`` """ if self.attr_list_vect is None: @@ -369,8 +380,9 @@ def _raise_error_attr_list_none(self): def _get_array_from_attr_name(self, attr_name): """ - This function allows to return the proper attribute vector that can be inspected in the - shape, size, dtype, from_vect and to_vect method. + This function returns the proper attribute vector that can be inspected in the + :func:`GridObject.shape`, :func:`GridObject.size`, :func:`GridObject.dtype`, + :func:`GridObject.from_vect` and :func:`GridObject.to_vect` method. If this function is overloaded, then the _assign_attr_from_name must be too. @@ -382,15 +394,15 @@ def _get_array_from_attr_name(self, attr_name): Returns ------- res: ``numpy.ndarray`` - The attribute corresponding the the name + The attribute corresponding the name, flatten as a 1d vector. """ return np.array(self.__dict__[attr_name]).flatten() def to_vect(self): """ - Convert this instance of GridObjects to a numpy array. - The size of the array is always the same and is determined by the `size` method. + Convert this instance of GridObjects to a numpy ndarray. + The size of the array is always the same and is determined by the :func:`GridObject.size` method. **NB**: in case the class GridObjects is derived, either :attr:`GridObjects.attr_list_vect` is properly defined for the derived class, or this function must be @@ -399,7 +411,7 @@ def to_vect(self): Returns ------- res: ``numpy.ndarray`` - The respresentation of this action as a numpy array + The representation of this action as a flat numpy ndarray """ @@ -462,7 +474,7 @@ def _assign_attr_from_name(self, attr_nm, vect): """ Assign the proper attributes with name 'attr_nm' with the value of the vector vect - If this function is overloaded, then the _get_array_from_attr_name must be too. + If this function is overloaded, then the _get_array_from_attr_name must be too. Parameters ---------- @@ -483,8 +495,8 @@ def from_vect(self, vect): Convert a GridObjects, represented as a vector, into an GridObjects object. **NB**: in case the class GridObjects is derived, - either :attr:`GridObjects.attr_list_vect` is properly defined for the derived class, or this function must be - redefined. + either :attr:`GridObjects.attr_list_vect` is properly defined for the derived class, or this function must be + redefined. Only the size is checked. If it does not match, an :class:`grid2op.Exceptions.AmbiguousAction` is thrown. Otherwise the component of the vector are coerced into the proper type silently. @@ -518,8 +530,8 @@ def size(self): NB that it is a requirement that converting an GridObjects gives a vector of a fixed size throughout a training. **NB**: in case the class GridObjects is derived, - either :attr:`GridObjects.attr_list_vect` is properly defined for the derived class, or this function must be - redefined. + either :attr:`GridObjects.attr_list_vect` is properly defined for the derived class, or this function must be + redefined. Returns ------- @@ -670,23 +682,29 @@ def assert_grid_correct(self): It is called after the _grid has been loaded. - These function is by default called by the :class:`grid2op.Environment` class after the initialization of the environment. + These function is by default called by the :class:`grid2op.Environment` class after the initialization of the + environment. If these tests are not successfull, no guarantee are given that the backend will return consistent computations. - In order for the backend to fully understand the structure of actions, it is strongly advised NOT to override this method. + In order for the backend to fully understand the structure of actions, it is strongly advised NOT to override + this method. :return: ``None`` :raise: :class:`grid2op.EnvError` and possibly all of its derived class. """ if self.name_line is None: - raise EnvError("name_line is None. Powergrid is invalid. Line names are used to make the correspondance between the chronics and the backend") + raise EnvError("name_line is None. Powergrid is invalid. Line names are used to make the correspondance " + "between the chronics and the backend") if self.name_load is None: - raise EnvError("name_load is None. Powergrid is invalid. Line names are used to make the correspondance between the chronics and the backend") + raise EnvError("name_load is None. Powergrid is invalid. Line names are used to make the correspondance " + "between the chronics and the backend") if self.name_gen is None: - raise EnvError("name_gen is None. Powergrid is invalid. Line names are used to make the correspondance between the chronics and the backend") + raise EnvError("name_gen is None. Powergrid is invalid. Line names are used to make the correspondance " + "between the chronics and the backend") if self.name_sub is None: - raise EnvError("name_sub is None. Powergrid is invalid. Substation names are used to make the correspondance between the chronics and the backend") + raise EnvError("name_sub is None. Powergrid is invalid. Substation names are used to make the " + "correspondance between the chronics and the backend") if self.n_gen <= 0: raise EnvError("n_gen is negative. Powergrid is invalid: there are no generator") @@ -800,14 +818,18 @@ def assert_grid_correct(self): if np.any(~np.isfinite(tmp)): raise EnvError("One of the vector is made of non finite elements") except Exception as e: - raise EnvError("Impossible to check wheter or not vectors contains online finite elements (pobably one or more topology related vector is not valid (None)") + raise EnvError("Impossible to check wheter or not vectors contains online finite elements (pobably one " + "or more topology related vector is not valid (None)") # check sizes if len(self.sub_info) != self.n_sub: - raise IncorrectNumberOfSubstation("The number of substation is not consistent in self.sub_info (size \"{}\") and self.n_sub ({})".format(len(self.sub_info), self.n_sub)) + raise IncorrectNumberOfSubstation("The number of substation is not consistent in " + "self.sub_info (size \"{}\")" + " and self.n_sub ({})".format(len(self.sub_info), self.n_sub)) if np.sum(self.sub_info) != self.n_load + self.n_gen + 2*self.n_line: err_msg = "The number of elements of elements is not consistent between self.sub_info where there are " - err_msg += "{} elements connected to all substations and the number of load, generators and lines in the _grid." + err_msg += "{} elements connected to all substations and the number of load, generators and lines in " \ + "the _grid." err_msg = err_msg.format(np.sum(self.sub_info)) raise IncorrectNumberOfElements(err_msg) @@ -1133,16 +1155,19 @@ def get_lines_id(self, _sentinel=None, from_=None, to_=None): """ res = [] if from_ is None: - raise BackendError("ObservationSpace.get_lines_id: impossible to look for a powerline with no origin substation. Please modify \"from_\" parameter") + raise BackendError("ObservationSpace.get_lines_id: impossible to look for a powerline with no origin " + "substation. Please modify \"from_\" parameter") if to_ is None: - raise BackendError("ObservationSpace.get_lines_id: impossible to look for a powerline with no extremity substation. Please modify \"to_\" parameter") + raise BackendError("ObservationSpace.get_lines_id: impossible to look for a powerline with no extremity " + "substation. Please modify \"to_\" parameter") for i, (ori, ext) in enumerate(zip(self.line_or_to_subid, self.line_ex_to_subid)): if ori == from_ and ext == to_: res.append(i) if res is []: - raise BackendError("ObservationSpace.get_line_id: impossible to find a powerline with connected at origin at {} and extremity at {}".format(from_, to_)) + raise BackendError("ObservationSpace.get_line_id: impossible to find a powerline with connected at " + "origin at {} and extremity at {}".format(from_, to_)) return res @@ -1169,7 +1194,8 @@ def get_generators_id(self, sub_id): res = [] if sub_id is None: raise BackendError( - "GridObjects.get_generators_id: impossible to look for a generator not connected to any substation. Please modify \"sub_id\" parameter") + "GridObjects.get_generators_id: impossible to look for a generator not connected to any substation. " + "Please modify \"sub_id\" parameter") for i, s_id_gen in enumerate(self.gen_to_subid): if s_id_gen == sub_id: @@ -1177,7 +1203,8 @@ def get_generators_id(self, sub_id): if res is []: raise BackendError( - "GridObjects.get_generators_id: impossible to find a generator connected at substation {}".format(sub_id)) + "GridObjects.get_generators_id: impossible to find a generator connected at " + "substation {}".format(sub_id)) return res @@ -1203,7 +1230,8 @@ def get_loads_id(self, sub_id): res = [] if sub_id is None: raise BackendError( - "GridObjects.get_loads_id: impossible to look for a load not connected to any substation. Please modify \"sub_id\" parameter") + "GridObjects.get_loads_id: impossible to look for a load not connected to any substation. " + "Please modify \"sub_id\" parameter") for i, s_id_gen in enumerate(self.load_to_subid): if s_id_gen == sub_id: @@ -1219,9 +1247,11 @@ def to_dict(self): """ Convert the object as a dictionnary. Note that unless this method is overidden, a call to it will only output the + Returns ------- - + res: ``dict`` + The representation of the object as a dictionary that can be json serializable. """ res = {} save_to_dict(res, self, "name_gen", lambda li: [str(el) for el in li]) @@ -1257,6 +1287,21 @@ def to_dict(self): @staticmethod def from_dict(dict_): + """ + Create a valid GridObject (or one of its derived class if this method is overide) from a dictionnary (usually + read from a json file) + + Parameters + ---------- + dict_: ``dict`` + The representation of the GridObject as a dictionary. + + Returns + ------- + res: :class:`GridObject` + The object of the proper class that were initially represented as a dictionary. + + """ res = GridObjects() res.name_gen = extract_from_dict(dict_, "name_gen", lambda x: np.array(x).astype(str)) res.name_load = extract_from_dict(dict_, "name_load", lambda x: np.array(x).astype(str)) @@ -1311,25 +1356,33 @@ class SerializableSpace(GridObjects): Type use to build the template object :attr:`SerializableSpace.template_obj`. This type should derive from :class:`grid2op.Action.Action` or :class:`grid2op.Observation.Observation`. - template_obj: [:class:`grid2op.Action.Action`, :class:`grid2op.Observation.Observation`] - An instance of the "*actionClass*" provided used to provide higher level utilities, such as the size of the - action (see :func:`Action.size`) or to sample a new Action (see :func:`Action.sample`) + template_obj: :class:`grid2op.GridObjects` + An instance of the "*subtype*" provided used to provide higher level utilities, such as the size of the + action (see :func:`grid2op.Action.Action.size`) or to sample a new Action + (see :func:`grid2op.Action.Action.sample`) for example. n: ``int`` Size of the space - space_prng: ``np.random.RandomState`` - The random state of the observation (in case of non deterministic observations. This should not be used at the + space_prng: ``numpy.random.RandomState`` + The random state of the observation (in case of non deterministic observations or Action. + This should not be used at the moment) seed: ``int`` - The seed used throughout the episode in case of non deterministic observations. + The seed used throughout the episode in case of non deterministic observations or action. - shape: ``None`` - For gym compatibility, do not use yet + shape: ``numpy.ndarray``, dtype:int + Shape of each of the component of the Object if represented in a flat vector. An instance that derives from a + GridObject (for example :class:`grid2op.Action.Action` or :grid2op:`Observation.Observation`) can be + thought of as being concatenation of independant spaces. This vector gives the dimension of all the basic + spaces they are made of. - dtype: ``None`` - For gym compatibility, do not use yet + dtype: ``numpy.ndarray``, dtype:int + Data type of each of the component of the Object if represented in a flat vector. An instance that derives from + a GridObject (for example :class:`grid2op.Action.Action` or :grid2op:`Observation.Observation`) can be + thought of as being concatenation of independant spaces. This vector gives the type of all the basic + spaces they are made of. """ def __init__(self, gridobj, @@ -1338,7 +1391,7 @@ def __init__(self, gridobj, subtype: ``type`` Type of action used to build :attr:`SerializableActionSpace.template_act`. This type should derive - from :class:`grid2op.Action.Action` or :class:`grid2op.Observation.Observation`. + from :class:`grid2op.Action.Action` or :class:`grid2op.Observation.Observation` . """ @@ -1360,7 +1413,6 @@ def __init__(self, gridobj, self.global_vars = None - # TODO self.shape = self.template_obj.shape() self.dtype = self.template_obj.dtype() @@ -1419,24 +1471,29 @@ def from_dict(dict_): try: subtype = eval(".".join(actionClass_li[1:])) except: - msg_err_ = "Impossible to find the module \"{}\" to load back the space (ERROR 1). Try \"from {} import {}\"" - raise Grid2OpException(msg_err_.format(actionClass_str, ".".join(actionClass_li[:-1]), actionClass_li[-1])) + msg_err_ = "Impossible to find the module \"{}\" to load back the space (ERROR 1). " \ + "Try \"from {} import {}\"" + raise Grid2OpException(msg_err_.format(actionClass_str, ".".join(actionClass_li[:-1]), + actionClass_li[-1])) else: - msg_err_ = "Impossible to find the module \"{}\" to load back the space (ERROR 2). Try \"from {} import {}\"" - raise Grid2OpException(msg_err_.format(actionClass_str, ".".join(actionClass_li[:-1]), actionClass_li[-1])) + msg_err_ = "Impossible to find the module \"{}\" to load back the space (ERROR 2). " \ + "Try \"from {} import {}\"" + raise Grid2OpException(msg_err_.format(actionClass_str, ".".join(actionClass_li[:-1]), + actionClass_li[-1])) except AttributeError: try: subtype = eval(actionClass_li[-1]) except: if len(actionClass_li) > 1: msg_err_ = "Impossible to find the class named \"{}\" to load back the space (ERROR 3)" \ - "(module is found but not the class in it) Please import it via \"from {} import {}\"." + "(module is found but not the class in it) Please import it via " \ + "\"from {} import {}\"." msg_err_ = msg_err_.format(actionClass_str, ".".join(actionClass_li[:-1]), actionClass_li[-1]) else: - msg_err_ = "Impossible to import the class named \"{}\" to load back the space (ERROR 4) (the " \ - "module is found but not the class in it)" + msg_err_ = "Impossible to import the class named \"{}\" to load back the space (ERROR 4) " \ + "(the module is found but not the class in it)" msg_err_ = msg_err_.format(actionClass_str) raise Grid2OpException(msg_err_) @@ -1463,6 +1520,7 @@ def to_dict(self): def size(self): """ The size of any action converted to vector. + Returns ------- n: ``int`` From 60735e28df6c372c9ff74f7dff2528602fd4cf31 Mon Sep 17 00:00:00 2001 From: DONNOT Benjamin Date: Mon, 20 Jan 2020 15:26:56 +0100 Subject: [PATCH 09/14] improving the renderer, refactoring multiple ways to display grids --- getting_started/4_StudyYourAgent.ipynb | 85 ++-- getting_started/test_renderer.py | 23 + getting_started/test_renderer_14.py | 121 +++++ grid2op/Environment.py | 2 +- grid2op/PlotGraph.py | 299 +++++++++++++ grid2op/PlotPlotly.py | 251 ++--------- grid2op/PlotPyGame.py | 370 ++++++++++++++++ grid2op/Renderer.py | 589 ------------------------- grid2op/Runner.py | 25 +- 9 files changed, 909 insertions(+), 856 deletions(-) create mode 100644 getting_started/test_renderer.py create mode 100644 getting_started/test_renderer_14.py create mode 100644 grid2op/PlotGraph.py create mode 100644 grid2op/PlotPyGame.py delete mode 100644 grid2op/Renderer.py diff --git a/getting_started/4_StudyYourAgent.ipynb b/getting_started/4_StudyYourAgent.ipynb index 1d9dd52a7..79015833f 100644 --- a/getting_started/4_StudyYourAgent.ipynb +++ b/getting_started/4_StudyYourAgent.ipynb @@ -61,7 +61,16 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pygame 1.9.6\n", + "Hello from the pygame community. https://www.pygame.org/contribute.html\n" + ] + } + ], "source": [ "import os\n", "import sys\n", @@ -1258,20 +1267,20 @@ "
\n", " \n", " \n", - "
\n", + "
\n", "