Skip to content

Commit

Permalink
Merge pull request #455 from BDonnot/master
Browse files Browse the repository at this point in the history
rules as instance
  • Loading branch information
BDonnot committed May 23, 2023
2 parents 91ae444 + 1bb8085 commit 59a48a8
Show file tree
Hide file tree
Showing 19 changed files with 238 additions and 75 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Change Log

[1.8.2] - 2023-xx-yy
--------------------
- [BREAKING] (because prone to bug): force the environment name in the `grid2op.make` function.
- [BREAKING] because bugged... The default behaviour for `env.render()` is now "rgb_array". The mode
"human" has been removed because it needs some fixes. This should not impact lots of code.
- [BREAKING] the "maintenance_forecast" file is deprecated and is no longer used (this should not
Expand Down Expand Up @@ -135,6 +136,7 @@ Change Log
passed to `chronix2grid.add_data`
- [IMPROVED] it is no more reasonably possible to misuse the `MultifolderWithCache` (for example by
forgetting to `reset()` the cache): an error will be raised in case the proper function has not been called.
- [IMPROVED] possibility to pass game rules by instance of object and not by class.

[1.8.1] - 2023-01-11
---------------------
Expand Down
31 changes: 31 additions & 0 deletions docs/opponent.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,37 @@ To summarize what is going on here:
type of Opponent, we don't provide any information in the documentation at this stage. Feel free to submit
a github issue if this is an issue for you.

How to deactivate an opponent in an environment
--------------------------------------------------

If you come accross an environment with an "opponent" already present but for some reasons you want to
deactivate it, you can do this by customization the call to "grid2op.make" like this:

.. code-block:: python
import grid2op
from grid2op.Action import DontAct
from grid2op.Opponent import BaseOpponent, NeverAttackBudget
env_name = ...
env_without_opponent = grid2op.make(env_name,
opponent_attack_cooldown=999999,
opponent_attack_duration=0,
opponent_budget_per_ts=0,
opponent_init_budget=0,
opponent_action_class=DontAct,
opponent_class=BaseOpponent,
opponent_budget_class=NeverAttackBudget,
... # other arguments pass to the "make" function
)
.. note::
Currently it's not possible to deactivate an opponent once the environment is created.

If you want this feature, you can comment the issue https://github.com/rte-france/Grid2Op/issues/426


Detailed Documentation by class
--------------------------------
.. automodule:: grid2op.Opponent
Expand Down
11 changes: 6 additions & 5 deletions grid2op/Agent/baseAgent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
from abc import ABC, abstractmethod
from grid2op.Space import RandomObject
from grid2op.Observation import BaseObservation


class BaseAgent(RandomObject, ABC):
Expand All @@ -32,7 +33,7 @@ def __init__(self, action_space):
RandomObject.__init__(self)
self.action_space = copy.deepcopy(action_space)

def reset(self, obs):
def reset(self, obs: BaseObservation):
"""
This method is called at the beginning of a new episode.
It is implemented by agents to reset their internal state if needed.
Expand All @@ -44,7 +45,7 @@ def reset(self, obs):
"""
pass

def seed(self, seed):
def seed(self, seed: int):
"""
This function is used to guarantee that the "pseudo random numbers" generated and used by the agent instance
will be deterministic.
Expand All @@ -70,7 +71,7 @@ def seed(self, seed):
return super().seed(seed), self.action_space.seed(seed)

@abstractmethod
def act(self, observation, reward, done=False):
def act(self, observation: BaseObservation, reward: float, done : bool=False):
"""
This is the main method of an BaseAgent. Given the current observation and the current reward (ie the reward
that the environment send to the agent after the previous action has been implemented).
Expand Down Expand Up @@ -110,7 +111,7 @@ def act(self, observation, reward, done=False):
The action chosen by the bot / controler / agent.
"""
pass
return self.action_space()

def save_state(self, savestate_path :os.PathLike):
"""
Expand Down Expand Up @@ -169,4 +170,4 @@ def load_state(self, loadstate_path :os.PathLike):
savestate_path: ``string``
The path from which your agent state variables should be loaded
"""
pass
pass
25 changes: 25 additions & 0 deletions grid2op/Environment/BaseEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
from grid2op.operator_attention import LinearAttentionBudget
from grid2op.Action._BackendAction import _BackendAction
from grid2op.Chronics import ChronicsHandler
from grid2op.Rules import AlwaysLegal, BaseRules


# TODO put in a separate class the redispatching function

Expand Down Expand Up @@ -3786,3 +3788,26 @@ def forecasts(self):
if self._forecasts is None:
self._forecasts = self.chronics_handler.forecasts()
return self._forecasts

def _check_rules_correct(self, legalActClass):
if isinstance(legalActClass, type):
# raise Grid2OpException(
# 'Parameter "legalActClass" used to build the Environment should be a type '
# "(a class) and not an object (an instance of a class). "
# 'It is currently "{}"'.format(type(legalActClass))
# )
if not issubclass(legalActClass, BaseRules):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should derived form the '
'grid2op.BaseRules class, type provided is "{}"'.format(
type(legalActClass)
)
)
else:
if not isinstance(legalActClass, BaseRules):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should be an instance of the '
'grid2op.BaseRules class, type provided is "{}"'.format(
type(legalActClass)
)
)
47 changes: 21 additions & 26 deletions grid2op/Environment/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ def _init_backend(
raise EnvError(
"Impossible to use the same backend twice. Please create your environment with a "
"new backend instance (new object)."
)
)

need_process_backend = False
if not self.backend.is_loaded:
# usual case: the backend is not loaded
# NB it is loaded when the backend comes from an observation for
Expand Down Expand Up @@ -255,8 +257,9 @@ def _init_backend(
self.load_alarm_data()
# to force the initialization of the backend to the proper type
self.backend.assert_grid_correct()
need_process_backend = True

self._handle_compat_glop_version()
self._handle_compat_glop_version(need_process_backend)

self._has_been_initialized() # really important to include this piece of code! and just here after the
# backend has loaded everything
Expand All @@ -271,19 +274,8 @@ def _init_backend(
*_, tmp = self.backend.generators_info()

# rules of the game
if not isinstance(legalActClass, type):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should be a type '
"(a class) and not an object (an instance of a class). "
'It is currently "{}"'.format(type(legalActClass))
)
if not issubclass(legalActClass, BaseRules):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should derived form the '
'grid2op.BaseRules class, type provided is "{}"'.format(
type(legalActClass)
)
)
self._check_rules_correct(legalActClass)

self._game_rules = RulesChecker(legalActClass=legalActClass)
self._legalActClass = legalActClass

Expand Down Expand Up @@ -420,7 +412,8 @@ def _init_backend(
)

# test the backend returns object of the proper size
self.backend.assert_grid_correct_after_powerflow()
if need_process_backend:
self.backend.assert_grid_correct_after_powerflow()

# for gym compatibility
self.reward_range = self._reward_helper.range()
Expand Down Expand Up @@ -477,7 +470,7 @@ def _helper_observation(self):
def _helper_action_player(self):
return self._action_space

def _handle_compat_glop_version(self):
def _handle_compat_glop_version(self, need_process_backend):
if (
self._compat_glop_version is not None
and self._compat_glop_version != grid2op.__version__
Expand All @@ -488,7 +481,8 @@ def _handle_compat_glop_version(self):
"read back data (for example with EpisodeData) that were stored with previous "
"grid2op version."
)
self.backend.set_env_name(f"{self.name}_{self._compat_glop_version}")
if need_process_backend:
self.backend.set_env_name(f"{self.name}_{self._compat_glop_version}")
cls_bk = type(self.backend)
cls_bk.glop_version = self._compat_glop_version
if cls_bk.glop_version == cls_bk.BEFORE_COMPAT_VERSION:
Expand Down Expand Up @@ -552,15 +546,16 @@ def _handle_compat_glop_version(self):
cls_bk.set_no_storage()
Environment.deactivate_storage(self.backend)

# the following line must be called BEFORE "self.backend.assert_grid_correct()" !
self.backend.storage_deact_for_backward_comaptibility()
if need_process_backend:
# the following line must be called BEFORE "self.backend.assert_grid_correct()" !
self.backend.storage_deact_for_backward_comaptibility()

# and recomputes everything while making sure everything is consistent
self.backend.assert_grid_correct()
type(self.backend)._topo_vect_to_sub = np.repeat(
np.arange(cls_bk.n_sub), repeats=cls_bk.sub_info
)
type(self.backend).grid_objects_types = new_grid_objects_types
# and recomputes everything while making sure everything is consistent
self.backend.assert_grid_correct()
type(self.backend)._topo_vect_to_sub = np.repeat(
np.arange(cls_bk.n_sub), repeats=cls_bk.sub_info
)
type(self.backend).grid_objects_types = new_grid_objects_types

def _voltage_control(self, agent_action, prod_v_chronics):
"""
Expand Down
8 changes: 1 addition & 7 deletions grid2op/Environment/_ObsEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,7 @@ def _init_backend(
self.backend = backend
self._has_been_initialized() # really important to include this piece of code! and just here after the

if not issubclass(legalActClass, BaseRules):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should derived form the '
'grid2op.BaseRules class, type provided is "{}"'.format(
type(legalActClass)
)
)
self._check_rules_correct(legalActClass)
self._game_rules = RulesChecker(legalActClass=legalActClass)
self._legalActClass = legalActClass
# self._action_space = self._do_nothing
Expand Down
9 changes: 7 additions & 2 deletions grid2op/MakeEnv/Make.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def _aux_make_multimix(


def make(
dataset="rte_case14_realistic",
dataset=None,
test=False,
logger=None,
experimental_read_from_local_dir=False,
Expand Down Expand Up @@ -326,7 +326,12 @@ def make(
warnings.warn(f"The environment variable \"{_VAR_FORCE_TEST}\" is defined so grid2op will be forced in \"test\" mode. "
f"This is equivalent to pass \"grid2op.make(..., test=True)\" and prevents any download of data.")
test = True


if dataset is None:
raise Grid2OpException("Impossible to create an environment without its name. Please call something like: \n"
"> env = grid2op.make('l2rpn_case14_sandbox') \nor\n"
"> env = grid2op.make('rte_case14_realistic')")

accepted_kwargs = ERR_MSG_KWARGS.keys() | {"dataset", "test"}
for el in kwargs:
if el not in accepted_kwargs:
Expand Down
7 changes: 6 additions & 1 deletion grid2op/MakeEnv/MakeFromPath.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,15 +464,20 @@ def make_from_dataset_path(
# Get default rules class
rules_class_cfg = DefaultRules
if "rules_class" in config_data and config_data["rules_class"] is not None:
warnings.warn("You used the deprecated rules_class in your config. Please change its "
"name to 'gamerules_class' to mimic the grid2op.make kwargs.")
rules_class_cfg = config_data["rules_class"]
if "gamerules_class" in config_data and config_data["gamerules_class"] is not None:
rules_class_cfg = config_data["gamerules_class"]

## Create the rules of the game (mimic the operationnal constraints)
gamerules_class = _get_default_aux(
"gamerules_class",
kwargs,
defaultClass=rules_class_cfg,
defaultClassApp=BaseRules,
msg_error=ERR_MSG_KWARGS["gamerules_class"],
isclass=True,
isclass=None,
)

# Get default reward class
Expand Down
2 changes: 1 addition & 1 deletion grid2op/Observation/baseObservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3832,7 +3832,7 @@ def get_simulator(self) -> "grid2op.simulator.Simulator":
Basic usage are:
..code-block:: python
.. code-block:: python
import grid2op
env_name = ...
Expand Down
2 changes: 1 addition & 1 deletion grid2op/Reward/BaseReward.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def __call__(self, action, env, has_error, is_done, is_illegal, is_ambiguous):
end of the episode.
"""
pass
return self.reward_min

def get_range(self):
"""
Expand Down
38 changes: 24 additions & 14 deletions grid2op/Rules/RulesChecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
# SPDX-License-Identifier: MPL-2.0
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.

import copy
import warnings

from grid2op.Exceptions import Grid2OpException
from grid2op.Rules.BaseRules import BaseRules
from grid2op.Rules.AlwaysLegal import AlwaysLegal
Expand All @@ -26,20 +29,27 @@ def __init__(self, legalActClass=AlwaysLegal):
The class that will be used to tell if the actions are legal or not. The class must be given, and not
an object of this class. It should derived from :class:`BaseRules`.
"""
if not isinstance(legalActClass, type):
raise Grid2OpException(
'Parameter "legalActClass" used to build the RulesChecker should be a '
"type (a class) "
"and not an object (an instance of a class). "
'It is currently "{}"'.format(type(legalActClass))
)

if not issubclass(legalActClass, BaseRules):
raise Grid2OpException(
"Gamerules: legalActClass should be initialize with a class deriving "
"from BaseRules and not {}".format(type(legalActClass))
)
self.legal_action = legalActClass()
if isinstance(legalActClass, type):
if not issubclass(legalActClass, BaseRules):
raise Grid2OpException(
"Gamerules: legalActClass should be initialize with a class deriving "
"from BaseRules and not {}".format(type(legalActClass))
)
self.legal_action = legalActClass()
else:
if not isinstance(legalActClass, BaseRules):
raise Grid2OpException(
'Parameter "legalActClass" used to build the Environment should be an instance of the '
'grid2op.BaseRules class, type provided is "{}"'.format(
type(legalActClass)
)
)
try:
self.legal_action = copy.deepcopy(legalActClass)
except Exception as exc_:
warnings.warn("You passed the legal action as an instance that cannot be deepcopied. It will be "
"used 'as is', we do not garantee anything if you modify the original object.")
self.legal_action = legalActClass

def __call__(self, action, env):
"""
Expand Down

0 comments on commit 59a48a8

Please sign in to comment.