Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bd dev #513

Merged
merged 13 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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