Skip to content

Commit

Permalink
Merge pull request Grid2op#242 from rte-france/dev_1.6.2
Browse files Browse the repository at this point in the history
Upgrade to version 1.6.2
  • Loading branch information
BDonnot authored Jul 27, 2021
2 parents db48d12 + b667529 commit 99a4ce3
Show file tree
Hide file tree
Showing 22 changed files with 233 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ grid2op/data_test/l2rpn_neurips_2020_track1_with_alert/_statistics_do_nothing/
save/
shorten_env.py
test_hash_env.py
test_pickle.py
test_rllib_vincevaki.py

# profiling files
**.prof
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ Change Log
- [???] "asynch" multienv
- [???] properly model interconnecting powerlines

[1.6.2] - 2021-07-27
---------------------
- [ADDED] the complete support for pickling grid2op classes. This is a major feature that allows to use grid2op
way more easily with multiprocessing and to ensure compatibility with more recent version of some RL package
(*eg* ray / rllib). Note that full compatibility with "multiprocessing" and "pickle" is not completely done yet.

[1.6.1] - 2021-07-27
---------------------
- [FIXED] a bug in the "env.get_path_env()" in case `env` was a multimix (it returned the path of the current mix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
"version": "3.8.10"
}
},
"nbformat": 4,
Expand Down
8 changes: 7 additions & 1 deletion grid2op/Action/ActionSpace.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ class ActionSpace(SerializableActionSpace):
"""

def __init__(self, gridobj, legal_action, actionClass=BaseAction):
def __init__(self,
gridobj,
legal_action,
actionClass=BaseAction # need to be a base grid2op type (and not a type generated on the fly)
):
"""
INTERNAL USE ONLY
Expand All @@ -61,6 +65,8 @@ def __init__(self, gridobj, legal_action, actionClass=BaseAction):
Class specifying the rules of the game used to check the legality of the actions.
"""
actionClass._add_shunt_data()
actionClass._update_value_set()
SerializableActionSpace.__init__(self, gridobj, actionClass=actionClass)
self.legal_action = legal_action

Expand Down
27 changes: 14 additions & 13 deletions grid2op/Action/BaseAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,19 +409,6 @@ def __init__(self):
self.shunt_q = None
self.shunt_bus = None

mycls = type(self)
if mycls.shunt_added is False and mycls.shunts_data_available:
mycls.shunt_added = True

mycls.attr_list_vect = copy.deepcopy(mycls.attr_list_vect)
mycls.attr_list_vect += ["shunt_p", "shunt_q", "shunt_bus"]

mycls.authorized_keys = copy.deepcopy(mycls.authorized_keys)
mycls.authorized_keys.add("shunt")
mycls.attr_nan_list_set.add("shunt_p")
mycls.attr_nan_list_set.add("shunt_q")
mycls._update_value_set()

self._single_act = True

self._raise_alarm = np.full(shape=self.dim_alarms, dtype=dt_bool, fill_value=False) # TODO
Expand All @@ -437,6 +424,20 @@ def __init__(self):
self._modif_curtailment = False
self._modif_alarm = False

@classmethod
def _add_shunt_data(cls):
if cls.shunt_added is False and cls.shunts_data_available:
cls.shunt_added = True

cls.attr_list_vect = copy.deepcopy(cls.attr_list_vect)
cls.attr_list_vect += ["shunt_p", "shunt_q", "shunt_bus"]

cls.authorized_keys = copy.deepcopy(cls.authorized_keys)
cls.authorized_keys.add("shunt")
cls.attr_nan_list_set.add("shunt_p")
cls.attr_nan_list_set.add("shunt_q")
cls._update_value_set()

def alarm_raised(self):
"""
INTERNAL
Expand Down
2 changes: 0 additions & 2 deletions grid2op/Action/DontAct.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def __init__(self):
"""
PlayableAction.__init__(self)
if DontAct.attr_list_set:
self._update_value_set()

def update(self, dict_):
"""
Expand Down
9 changes: 6 additions & 3 deletions grid2op/Backend/Backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ def copy(self):
start_grid = self._grid
self._grid = None
res = copy.deepcopy(self)
res.__class__ = type(self) # somehow deepcopy forget the init class... weird
res._grid = copy.deepcopy(start_grid)
self._grid = start_grid
res._is_loaded = False # i can reload a copy of an environment
Expand Down Expand Up @@ -758,9 +759,10 @@ def _disconnect_line(self, id_):
:type id_: int
"""
action = self._complete_action_class()
my_cls = type(self)
action = my_cls._complete_action_class()
action.update({"set_line_status": [(id_, -1)]})
bk_act = self.my_bk_act_class()
bk_act = my_cls.my_bk_act_class()
bk_act += action
self.apply_action(bk_act)

Expand Down Expand Up @@ -1532,10 +1534,11 @@ def assert_grid_correct(self):
# class is already initialized
# and set up the proper class and everything
self._init_class_attr()

# type(self)._INIT_GRID_CLS = orig_type
# hack due to changing class of imported module in the module itself
self.__class__ = type(self).init_grid(type(self), force_module=type(self).__module__)
setattr(sys.modules[type(self).__module__], self.__class__.__name__, self.__class__)

# reset the attribute of the grid2op.Backend.Backend class
# that can be messed up with depending on the initialization of the backend
Backend._clear_class_attribute()
Expand Down
5 changes: 5 additions & 0 deletions grid2op/Environment/BaseEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,8 @@ def _create_opponent(self):
if not issubclass(self._opponent_class, BaseOpponent):
raise EnvError("Impossible to make an opponent with a type that does not inherit from BaseOpponent.")

self._opponent_action_class._add_shunt_data()
self._opponent_action_class._update_value_set()
self._opponent_action_space = self._helper_action_class(gridobj=type(self.backend),
legal_action=AlwaysLegal,
actionClass=self._opponent_action_class
Expand All @@ -611,6 +613,9 @@ def _init_myclass(self):
if self._backend_action_class is not None:
# the class has already been initialized
return
# remember the original grid2op class
type(self)._INIT_GRID_CLS = type(self)

bk_type = type(self.backend) # be careful here: you need to initialize from the class, and not from the object
# create the proper environment class for this specific environment
self.__class__ = type(self).init_grid(bk_type)
Expand Down
6 changes: 5 additions & 1 deletion grid2op/Environment/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ def _init_backend(self, chronics_handler, backend,
bk_type = type(self.backend) # be careful here: you need to initialize from the class, and not from the object
self._rewardClass = rewardClass
self._actionClass = actionClass.init_grid(gridobj=bk_type)
self._actionClass._add_shunt_data()
self._actionClass._update_value_set()
self._observationClass = observationClass.init_grid(gridobj=bk_type)

self._complete_action_cls = CompleteAction.init_grid(gridobj=bk_type)
Expand Down Expand Up @@ -285,7 +287,8 @@ def _init_backend(self, chronics_handler, backend,
raise Grid2OpException("Parameter \"voltagecontrolClass\" should derive from \"ControlVoltageFromFile\".")

self._voltage_controler = self._voltagecontrolerClass(gridobj=bk_type,
controler_backend=self.backend)
controler_backend=self.backend,
actionSpace_cls=self._helper_action_class)

# create the opponent
# At least the 3 following attributes should be set before calling _create_opponent
Expand Down Expand Up @@ -837,6 +840,7 @@ def copy(self):
self._voltage_controler = None

res = copy.deepcopy(self)

res.backend = tmp_backend.copy()
res._observation_space = tmp_obs_space.copy()
res.current_obs = obs_tmp.copy()
Expand Down
3 changes: 3 additions & 0 deletions grid2op/Episode/EpisodeData.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,18 @@ def __init__(self,
"actions",
check_legit=False,
init_me=_init_collections)

self.observations = CollectionWrapper(observations,
observation_space,
"observations",
init_me=_init_collections)

self.env_actions = CollectionWrapper(env_actions,
helper_action_env,
"env_actions",
check_legit=False,
init_me=_init_collections)

self.attacks = CollectionWrapper(attack,
attack_space,
"attacks",
Expand Down
4 changes: 2 additions & 2 deletions grid2op/Observation/ObservationSpace.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ def __init__(self,
actionClass = CompleteAction

SerializableObservationSpace.__init__(self, gridobj, observationClass=observationClass)

self.with_forecast = with_forecast
self._simulate_parameters = copy.deepcopy(env.parameters)

Expand All @@ -90,9 +89,10 @@ def __init__(self,

other_rewards = {k: v.rewardClass for k, v in env.other_rewards.items()}

# TODO here: have another backend maybe
# TODO here: have another backend class maybe
self._backend_obs = env.backend.copy()
_ObsEnv_class = _ObsEnv.init_grid(type(env.backend), force_module=_ObsEnv.__module__)
_ObsEnv_class._INIT_GRID_CLS = _ObsEnv # otherwise it's lost
setattr(sys.modules[_ObsEnv.__module__], _ObsEnv_class.__name__, _ObsEnv_class)
self.obs_env = _ObsEnv_class(init_grid_path=None, # don't leak the path of the real grid to the observation space
backend_instanciated=self._backend_obs,
Expand Down
12 changes: 7 additions & 5 deletions grid2op/Observation/_ObsEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ def __init__(self,
kwargs_attention_budget=kwargs_attention_budget)
self._helper_action_class = helper_action_class
self._reward_helper = reward_helper
self._obsClass = None
self._obsClass = obsClass.init_grid(type(backend_instanciated))

# initialize the observation space
self._obsClass = None

self.gen_activeprod_t_init = np.zeros(self.n_gen, dtype=dt_float)
self.gen_activeprod_t_redisp_init = np.zeros(self.n_gen, dtype=dt_float)
self.times_before_line_status_actionable_init = np.zeros(self.n_line, dtype=dt_int)
Expand All @@ -91,7 +92,7 @@ def __init__(self,
backend=backend_instanciated,
names_chronics_to_backend=None,
actionClass=action_helper.actionClass,
observationClass=self._obsClass,
observationClass=obsClass,
rewardClass=None,
legalActClass=legalActClass)
self.no_overflow_disconnection = parameters.NO_OVERFLOW_DISCONNECTION
Expand Down Expand Up @@ -155,7 +156,7 @@ def _init_backend(self,
backend,
names_chronics_to_backend,
actionClass,
observationClass,
observationClass, # base grid2op type
rewardClass,
legalActClass):
self._env_dc = self.parameters.ENV_DC
Expand All @@ -178,7 +179,8 @@ def _init_backend(self,

# create the attention budget
self._create_attention_budget()

self._obsClass = observationClass.init_grid(type(self.backend))
self._obsClass._INIT_GRID_CLS = observationClass
self.current_obs_init = self._obsClass(seed=None,
obs_env=None,
action_helper=None)
Expand Down
4 changes: 3 additions & 1 deletion grid2op/Opponent/OpponentSpace.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def __init__(self,
opponent,
attack_duration, # maximum duration of an attack
attack_cooldown, # minimum duration between two consecutive attack
budget_per_timestep=0., action_space=None):
budget_per_timestep=0.,
action_space=None):

if action_space is not None:
if not isinstance(action_space, compute_budget.action_space):
raise OpponentError("BaseAction space provided to build the agent is not a subclass from the"
Expand Down
9 changes: 2 additions & 7 deletions grid2op/Opponent/RandomLineOpponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ def __init__(self, action_space):
self._attacks = None
self._lines_ids = None

# this is the constructor:
# it should have the exact same signature as here

def init(self, partial_env, lines_attacked=[], **kwargs):
"""
INTERNAL
Expand Down Expand Up @@ -67,10 +64,8 @@ def init(self, partial_env, lines_attacked=[], **kwargs):
# Pre-build attacks actions
self._attacks = []
for l_id in self._lines_ids:
a = self.action_space({
'set_line_status': [(l_id, -1)]
})
self._attacks.append(a)
att = self.action_space({'set_line_status': [(l_id, -1)]})
self._attacks.append(att)
self._attacks = np.array(self._attacks)

def attack(self, observation, agent_action, env_action,
Expand Down
68 changes: 68 additions & 0 deletions grid2op/Space/GridObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,8 @@ def from_vect(self, vect, check_legit=True):
"""
if vect.shape[0] != self.size():
import pdb
pdb.set_trace()
raise IncorrectNumberOfElements("Incorrect number of elements found while load a GridObjects "
"from a vector. Found {} elements instead of {}"
"".format(vect.shape[0], self.size()))
Expand Down Expand Up @@ -2130,6 +2132,7 @@ def init_grid(cls, gridobj, force=False, extra_name=None, force_module=None):
name_res = "{}_{}".format(cls.__name__, gridobj.env_name)
if gridobj.glop_version != grid2op.__version__:
name_res += f"_{gridobj.glop_version}"

if name_res in globals():
if not force:
# no need to recreate the class, it already exists
Expand All @@ -2141,6 +2144,13 @@ def init_grid(cls, gridobj, force=False, extra_name=None, force_module=None):
cls_attr_as_dict = {}
GridObjects._make_cls_dict_extended(gridobj, cls_attr_as_dict, as_list=False)
res_cls = type(name_res, (cls, ), cls_attr_as_dict)
if hasattr(cls, "_INIT_GRID_CLS"):
# original class is already from an initialized environment, i keep track of it
res_cls._INIT_GRID_CLS = cls._INIT_GRID_CLS
else:
# i am the original class from grid2op
res_cls._INIT_GRID_CLS = cls

res_cls._compute_pos_big_topo_cls()
if res_cls.glop_version != grid2op.__version__:
res_cls.process_grid2op_compat()
Expand Down Expand Up @@ -2900,3 +2910,61 @@ def same_grid_class(cls, other_cls) -> bool:
if not np.array_equal(getattr(cls, attr_nm), getattr(other_cls, attr_nm)):
return False
return True

@staticmethod
def init_grid_from_dict_for_pickle(name_res, orig_cls, cls_attr):
"""
This function is used internally for pickle to build the classes of the
objects instead of loading them from the module (which is impossible as
most classes are defined on the fly in grid2op)
It is expected to create an object of the correct type. This object will then be
"filled" with the proper content automatically by python, because i provided the "state" of the
object in the __reduce__ method.
"""
# check if the class already exists, if so returns it
if name_res in globals():
# no need to recreate the class, it already exists
res_cls = globals()[name_res]
else:
# define properly the class, as it is not found
res_cls = type(name_res, (orig_cls, ), cls_attr)
res_cls._INIT_GRID_CLS = orig_cls # don't forget to remember the base class
# if hasattr(res_cls, "n_sub") and res_cls.n_sub > 0:
# that's a grid2op class iniailized with an environment, I need to initialize it too
res_cls._compute_pos_big_topo_cls()
if res_cls.glop_version != grid2op.__version__:
res_cls.process_grid2op_compat()

# add the class in the "globals" for reuse later
globals()[name_res] = res_cls

# now create an "empty" object (using new)
res = res_cls.__new__(res_cls)
return res

# test for pickle
def __reduce__(self):
"""
It here to avoid issue with pickle.
But the problem is that it's also used by deepcopy... So its implementation is used a lot
"""
cls_attr_as_dict = {}
GridObjects._make_cls_dict_extended(type(self), cls_attr_as_dict, as_list=False)
if hasattr(self, "__getstate__"):
my_state = self.__getstate__()
else:
my_state = {}
for k, v in self.__dict__.items():
my_state[k] = copy.copy(v)

my_cls = type(self)
if hasattr(my_cls, "_INIT_GRID_CLS"):
# I am a type created when an environment is loaded
base_cls = my_cls._INIT_GRID_CLS
else:
# i am a "raw" type directly coming from grid2op
base_cls = my_cls
return GridObjects.init_grid_from_dict_for_pickle, \
(type(self).__name__, base_cls, cls_attr_as_dict), \
my_state
Loading

0 comments on commit 99a4ce3

Please sign in to comment.