Skip to content

Commit

Permalink
Merge pull request #513 from BDonnot/bd_dev
Browse files Browse the repository at this point in the history
Bd dev
  • Loading branch information
BDonnot authored Aug 25, 2023
2 parents 2c7cef4 + 4345467 commit 3e16cf5
Show file tree
Hide file tree
Showing 60 changed files with 1,590 additions and 92 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Change Log
- [???] "asynch" multienv
- [???] properly model interconnecting powerlines

[1.9.4] - 2023-xx-yy
---------------------
- [FIXED] read-the-docs template is not compatible with latest sphinx version (7.0.0)
see https://github.com/readthedocs/sphinx_rtd_theme/issues/1463
- [FIXED] issue https://github.com/rte-france/Grid2Op/issues/511
- [FIXED] issue https://github.com/rte-france/Grid2Op/issues/508
- [ADDED] some classes that can be used to reproduce exactly what happened in a previously run environment
see `grid2op.Chronics.FromOneEpisodeData` and `grid2op.Opponent.FromEpisodeDataOpponent`
and `grid2op.Chronics.FromMultiEpisodeData`
- [ADDED] An helper function to get the kwargs to disable the opponent (see `grid2op.Opponent.get_kwargs_no_opponent()`)
- [IMPROVED] doc of `obs.to_dict` and `obs.to_json` (see https://github.com/rte-france/Grid2Op/issues/509)

[1.9.3] - 2023-07-28
---------------------
- [BREAKING] the "chronix2grid" dependency now points to chronix2grid and not to the right branch
Expand Down
7 changes: 7 additions & 0 deletions docs/opponent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ deactivate it, you can do this by customization the call to "grid2op.make" like
from grid2op.Opponent import BaseOpponent, NeverAttackBudget
env_name = ...
# if you want to disable the opponent you can do (grid2op >= 1.9.4)
kwargs_no_opp = grid2op.Opponent.get_kwargs_no_opponent()
env_no_opp = grid2op.make(env_name, **kwargs_no_opp)
# and there the opponent is disabled
# or, in a more complex fashion (or for older grid2op version <= 1.9.3)
env_without_opponent = grid2op.make(env_name,
opponent_attack_cooldown=999999,
opponent_attack_duration=0,
Expand Down
8 changes: 5 additions & 3 deletions grid2op/Action/baseAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -3231,8 +3231,9 @@ def as_dict(self) -> dict:
sub_id = "{}".format(substation_id)
if not sub_id in res["change_bus_vect"]:
res["change_bus_vect"][sub_id] = {}
res["change_bus_vect"][sub_id]["{}".format(obj_id)] = {
"type": objt_type
res["change_bus_vect"][sub_id]["{}_{}".format(objt_type, obj_id)] = {
"type": objt_type,
"id": obj_id,
}
all_subs.add(sub_id)

Expand All @@ -3252,8 +3253,9 @@ def as_dict(self) -> dict:
sub_id = "{}".format(substation_id)
if not sub_id in res["set_bus_vect"]:
res["set_bus_vect"][sub_id] = {}
res["set_bus_vect"][sub_id]["{}".format(obj_id)] = {
res["set_bus_vect"][sub_id]["{}_{}".format(objt_type, obj_id)] = {
"type": objt_type,
"id": obj_id,
"new_bus": k,
}
all_subs.add(sub_id)
Expand Down
2 changes: 1 addition & 1 deletion grid2op/Action/serializableActionSpace.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def supports_type(self, action_type):
import grid2op
from grid2op.Converter import ConnectivityConverter
env = grid2op.make("rte_case14_realistic", test=True)
env = grid2op.make("l2rpn_case14_sandbox", test=True)
can_i_use_set_bus = env.action_space.supports_type("set_bus") # this is True
env2 = grid2op.make("educ_case14_storage", test=True)
Expand Down
4 changes: 3 additions & 1 deletion grid2op/Agent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"MLAgent",
"RecoPowerlineAgent",
"FromActionsListAgent",
"RecoPowerlinePerArea"
"RecoPowerlinePerArea",
"AlertAgent"
]

from grid2op.Agent.baseAgent import BaseAgent
Expand All @@ -35,3 +36,4 @@
from grid2op.Agent.recoPowerlineAgent import RecoPowerlineAgent
from grid2op.Agent.fromActionsListAgent import FromActionsListAgent
from grid2op.Agent.recoPowerLinePerArea import RecoPowerlinePerArea
from grid2op.Agent.alertAgent import AlertAgent
2 changes: 1 addition & 1 deletion grid2op/Agent/recoPowerLinePerArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class RecoPowerlinePerArea(BaseAgent):
You can use it like:
.. code-block::
.. code-block:: python
import grid2op
from grid2op.Agent import RecoPowerlinePerArea
Expand Down
3 changes: 2 additions & 1 deletion grid2op/Backend/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1753,9 +1753,10 @@ def assert_grid_correct(self):
self._init_class_attr()

# hack due to changing class of imported module in the module itself
self.__class__ = type(self).init_grid(
future_cls = orig_type.init_grid(
type(self), force_module=type(self).__module__
)
self.__class__ = future_cls
setattr(
sys.modules[type(self).__module__],
self.__class__.__name__,
Expand Down
7 changes: 6 additions & 1 deletion grid2op/Chronics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"GridStateFromFileWithForecastsWithoutMaintenance",
"FromNPY",
"FromChronix2grid",
"FromHandlers"
"FromHandlers",
"FromOneEpisodeData",
"FromMultiEpisodeData"
]

from grid2op.Chronics.chronicsHandler import ChronicsHandler
Expand All @@ -30,3 +32,6 @@
from grid2op.Chronics.fromNPY import FromNPY
from grid2op.Chronics.fromChronix2grid import FromChronix2grid
from grid2op.Chronics.time_series_from_handlers import FromHandlers

from grid2op.Chronics.fromOneEpisodeData import FromOneEpisodeData
from grid2op.Chronics.fromMultiEpisodeData import FromMultiEpisodeData
188 changes: 188 additions & 0 deletions grid2op/Chronics/fromMultiEpisodeData.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Copyright (c) 2023, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.

from datetime import datetime, timedelta
import os
import numpy as np
import copy
import warnings
from typing import Optional, Union, List
from pathlib import Path

from grid2op.Exceptions import (
ChronicsError, ChronicsNotFoundError
)

from grid2op.Chronics.gridValue import GridValue

from grid2op.dtypes import dt_int, dt_float
from grid2op.Chronics.fromOneEpisodeData import TYPE_EP_DATA_INGESTED, FromOneEpisodeData


class FromMultiEpisodeData(GridValue):
"""This class allows to redo some episode that have been previously run using a runner.
It is an extension of the class :class:`FromOneEpisodeData` but with multiple episodes.
.. seealso::
:class:`grid2op.Chronics.FromOneEpisodeData`if you want to use only one episode
.. warning::
It has the same limitation as :class:`grid2op.Chronics.FromOneEpisodeData`, including:
- forecasts are not saved so cannot be retrieved with this class. You can however
use `obs.simulate` and in this case it will lead perfect forecasts.
- to make sure you are running the exact same episode, you need to create the environment
with the :class:`grid2op.Opponent.FromEpisodeDataOpponent` opponent
Examples
---------
You can use this class this way:
First, you generate some data by running an episode with do nothing or reco powerline agent,
preferably episode that go until the end of your time series
.. code-block:: python
import grid2op
from grid2op.Runner import Runner
from grid2op.Agent import RecoPowerlineAgent
path_agent = ....
nb_episode = ...
env_name = "l2rpn_case14_sandbox" # or any other name
env = grid2op.make(env_name, etc.)
# optional (change the parameters to allow the )
param = env.parameters
param.NO_OVERFLOW_DISCONNECTION = True
env.change_parameters(param)
env.reset()
# end optional
runner = Runner(**env.get_params_for_runner(),
agentClass=RecoPowerlineAgent)
runner.run(nb_episode=nb_episode,
path_save=path_agent)
And then you can load it back and run the exact same environment with the same
time series, the same attacks etc. with:
.. code-block:: python
import grid2op
from grid2op.Chronics import FromMultiEpisodeData
from grid2op.Opponent import FromEpisodeDataOpponent
from grid2op.Episode import EpisodeData
path_agent = .... # same as above
env_name = .... # same as above
# path_agent is the path where data coming from a grid2op runner are stored
# NB it should come from a do nothing agent, or at least
# an agent that does not modify the injections (no redispatching, curtailment, storage)
li_episode = EpisodeData.list_episode(path_agent)
env = grid2op.make(env_name,
chronics_class=FromMultiEpisodeData,
data_feeding_kwargs={"li_ep_data": li_episode},
opponent_class=FromEpisodeDataOpponent,
opponent_attack_cooldown=1,
)
# li_ep_data in this case is a list of anything that is accepted by `FromOneEpisodeData`
obs = env.reset()
# and now you can use "env" as any grid2op environment.
"""
MULTI_CHRONICS = True
def __init__(self,
path, # can be None !
li_ep_data: List[TYPE_EP_DATA_INGESTED],
time_interval=timedelta(minutes=5),
sep=";", # here for compatibility with grid2op, but not used
max_iter=-1,
start_datetime=datetime(year=2019, month=1, day=1),
chunk_size=None,
list_perfect_forecasts=None, # TODO
**kwargs, # unused
):
super().__init__(time_interval, max_iter, start_datetime, chunk_size)
self.li_ep_data = [FromOneEpisodeData(path,
ep_data=el,
time_interval=time_interval,
max_iter=max_iter,
chunk_size=chunk_size,
list_perfect_forecasts=list_perfect_forecasts,
start_datetime=start_datetime)
for el in li_ep_data
]
self._prev_cache_id = len(self.li_ep_data) - 1
self.data = self.li_ep_data[self._prev_cache_id]
self._episode_data = self.data._episode_data # used by the fromEpisodeDataOpponent

def next_chronics(self):
self._prev_cache_id += 1
# TODO implement the shuffling indeed.
# if self._prev_cache_id >= len(self._order):
# self.space_prng.shuffle(self._order)
self._prev_cache_id %= len(self.li_ep_data)

def initialize(
self,
order_backend_loads,
order_backend_prods,
order_backend_lines,
order_backend_subs,
names_chronics_to_backend=None,
):

self.data = self.li_ep_data[self._prev_cache_id]
self.data.initialize(
order_backend_loads,
order_backend_prods,
order_backend_lines,
order_backend_subs,
names_chronics_to_backend=names_chronics_to_backend,
)
self._episode_data = self.data._episode_data

def done(self):
return self.data.done()

def load_next(self):
return self.data.load_next()

def check_validity(self, backend):
return self.data.check_validity(backend)

def forecasts(self):
return self.data.forecasts()

def tell_id(self, id_num, previous=False):
id_num = int(id_num)
if not isinstance(id_num, (int, dt_int)):
raise ChronicsError("FromMultiEpisodeData can only be used with `tell_id` being an integer "
"at the moment. Feel free to write a feature request if you want more.")

self._prev_cache_id = id_num
self._prev_cache_id %= len(self.li_ep_data)

if previous:
self._prev_cache_id -= 1
self._prev_cache_id %= len(self.li_ep_data)

def get_id(self) -> str:
return f'{self._prev_cache_id }'

def max_timestep(self):
return self.data.max_timestep()

def fast_forward(self, nb_timestep):
self.data.fast_forward(nb_timestep)
Loading

0 comments on commit 3e16cf5

Please sign in to comment.