Skip to content

Commit

Permalink
fixing the automatic class with the new name change + improve automat…
Browse files Browse the repository at this point in the history
…ic class for multi mix

Signed-off-by: DONNOT Benjamin <[email protected]>
  • Loading branch information
BDonnot committed Nov 21, 2024
1 parent ad607b9 commit 47ae6b2
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 137 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ Native multi agents support:
- [IMPROVED] some type hints for some agent class
- [IMPROVED] the `backend.update_from_obs` function to work even when observation
does not have shunt information but there are not shunts on the grid.
- [IMPROVED] consistency of `MultiMixEnv` in case of automatic_classes (only one
class is generated for all mixes)

[1.10.4] - 2024-10-15
-------------------------
Expand Down
10 changes: 5 additions & 5 deletions grid2op/Environment/baseEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
HighResSimCounter)
from grid2op.Backend import Backend
from grid2op.dtypes import dt_int, dt_float, dt_bool
from grid2op.Space import GridObjects, RandomObject
from grid2op.Space import GridObjects, RandomObject, GRID2OP_CLASSES_ENV_FOLDER
from grid2op.Exceptions import (Grid2OpException,
EnvError,
InvalidRedispatching,
Expand Down Expand Up @@ -353,7 +353,7 @@ def __init__(
self._local_dir_cls = _local_dir_cls # suppose it's the second path to the environment, so the classes are already in the files
self._read_from_local_dir = _read_from_local_dir
if self._read_from_local_dir is not None:
if os.path.split(self._read_from_local_dir)[1] == "_grid2op_classes":
if os.path.split(self._read_from_local_dir)[1] == GRID2OP_CLASSES_ENV_FOLDER:
# legacy behaviour (using experimental_read_from_local_dir kwargs in env.make)
self._do_not_erase_local_dir_cls = True
else:
Expand Down Expand Up @@ -4081,7 +4081,7 @@ def _aux_gen_classes(cls_other, sys_path, _add_class_output=False):
sys.path.append(sub_repo)

sub_repo_mod = None
if tmp_nm == "_grid2op_classes":
if tmp_nm == GRID2OP_CLASSES_ENV_FOLDER:
# legacy "experimental_read_from_local_dir"
# issue was the module "_grid2op_classes" had the same name
# regardless of the environment, so grid2op was "confused"
Expand Down Expand Up @@ -4203,9 +4203,9 @@ def generate_classes(self, *, local_dir_id=None, _guard=None, _is_base_env__=Tru
"(eg no the top level env) if I don't know the path of "
"the top level environment.")
if local_dir_id is not None:
sys_path = os.path.join(self.get_path_env(), "_grid2op_classes", local_dir_id)
sys_path = os.path.join(self.get_path_env(), GRID2OP_CLASSES_ENV_FOLDER, local_dir_id)
else:
sys_path = os.path.join(self.get_path_env(), "_grid2op_classes")
sys_path = os.path.join(self.get_path_env(), GRID2OP_CLASSES_ENV_FOLDER)

if _is_base_env__:
if os.path.exists(sys_path):
Expand Down
2 changes: 1 addition & 1 deletion grid2op/Environment/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def __init__(
# this means that the "make" call is issued from the
# creation of a MultiMix.
# So I use the base name instead.
self.name = "".join(_overload_name_multimix[2:])
self.name = _overload_name_multimix.name_env + _overload_name_multimix.add_to_name
self.multimix_mix_name = name
self._overload_name_multimix = _overload_name_multimix
else:
Expand Down
166 changes: 113 additions & 53 deletions grid2op/Environment/multiMixEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,53 @@
import warnings
import numpy as np
import copy
from typing import Any, Dict, Tuple, Union, List, Literal
from typing import Any, Dict, Tuple, Union, List, Literal, Optional

from grid2op.dtypes import dt_int, dt_float
from grid2op.Space import GridObjects, RandomObject, DEFAULT_N_BUSBAR_PER_SUB
from grid2op.Space import GridObjects, RandomObject, DEFAULT_N_BUSBAR_PER_SUB, GRID2OP_CLASSES_ENV_FOLDER
from grid2op.Exceptions import EnvError, Grid2OpException
from grid2op.Backend import Backend
from grid2op.Observation import BaseObservation
from grid2op.MakeEnv.PathUtils import USE_CLASS_IN_FILE
from grid2op.Environment.baseEnv import BaseEnv
from grid2op.typing_variables import STEP_INFO_TYPING, RESET_OPTIONS_TYPING


class _OverloadNameMultiMixInfo:
def __init__(self,
path_cls=None,
path_env=None,
name_env=None,
add_to_name="",
):
self.path_cls = path_cls
self.path_env = path_env
self.name_env = name_env
self.add_to_name = add_to_name

def __getitem__(self, arg):
try:
arg_ = int(arg)
except ValueError as exc_:
raise exc_

if arg_ != arg:
raise RuntimeError("you can only access this class with integer")

if arg_ < 0:
arg_ += 4

if arg_ == 0:
return self.path_cls
if arg_ == 1:
return self.path_env
if arg_ == 2:
return self.name_env
if arg_ == 3:
return self.add_to_name
raise IndexError("_OverloadNameMultiMixInfo can only be used with index being 0, 1, 2 or 3")


class MultiMixEnvironment(GridObjects, RandomObject):
"""
This class represent a single powergrid configuration,
Expand Down Expand Up @@ -186,29 +222,36 @@ def __init__(
# TODO: with backend.copy() instead !
backendClass = None
backend_kwargs = {}
self._ptr_backend_obj_first_env : Optional[Backend]= None
_added_bk_name = ""

if "backend" in kwargs:
backendClass = type(kwargs["backend"])
if hasattr(kwargs["backend"], "_my_kwargs"):
# was introduced in grid2op 1.7.1
backend_kwargs = kwargs["backend"]._my_kwargs
_added_bk_name = kwargs["backend"].get_class_added_name()
self._ptr_backend_obj_first_env = kwargs["backend"]
del kwargs["backend"]

li_mix_nms = [mix_name for mix_name in sorted(os.listdir(envs_dir)) if os.path.isdir(os.path.join(envs_dir, mix_name))]

li_mix_nms = [mix_name for mix_name in sorted(os.listdir(envs_dir))
if (mix_name != GRID2OP_CLASSES_ENV_FOLDER
and os.path.isdir(os.path.join(envs_dir, mix_name))
)]
if not li_mix_nms:
raise EnvError("We did not find any mix in this multi-mix environment.")

# Make sure GridObject class attributes are set from first env
# Should be fine since the grid is the same for all envs
if not _add_cls_nm_bk:
multi_env_name = (None, envs_dir, os.path.basename(os.path.abspath(envs_dir)), _add_to_name)
multi_env_name = _OverloadNameMultiMixInfo(None, envs_dir, os.path.basename(os.path.abspath(envs_dir)), _add_to_name)
else:
_add_to_name = _added_bk_name + _add_to_name
multi_env_name = (None, envs_dir, os.path.basename(os.path.abspath(envs_dir)), _add_to_name)

multi_env_name = _OverloadNameMultiMixInfo(None, envs_dir, os.path.basename(os.path.abspath(envs_dir)), _add_to_name)
env_for_init = self._aux_create_a_mix(envs_dir,
li_mix_nms[0],
True, # first mix
logger,
backendClass,
backend_kwargs,
Expand All @@ -220,24 +263,24 @@ def __init__(
experimental_read_from_local_dir,
multi_env_name,
kwargs)

cls_res_me = self._aux_add_class_file(env_for_init)
if cls_res_me is not None:
self.__class__ = cls_res_me
else:
self.__class__ = type(self).init_grid(type(env_for_init.backend), _local_dir_cls=env_for_init._local_dir_cls)
self.mix_envs.append(env_for_init)
self._local_dir_cls = env_for_init._local_dir_cls

# TODO reuse same observation_space and action_space in all the envs maybe ?
multi_env_name = (type(env_for_init)._PATH_GRID_CLASSES, *multi_env_name[1:])
multi_env_name.path_cls = type(env_for_init)._PATH_GRID_CLASSES
multi_env_name.name_env = env_for_init.env_name

try:
for mix_name in li_mix_nms[1:]:
mix_path = os.path.join(envs_dir, mix_name)
if not os.path.isdir(mix_path):
continue
mix = self._aux_create_a_mix(envs_dir,
mix_name,
False,
logger,
backendClass,
backend_kwargs,
Expand All @@ -264,10 +307,9 @@ def __init__(
el._do_not_erase_local_dir_cls = True
self.env_index = 0
self.current_env = self.mix_envs[self.env_index]

# legacy behaviour (using experimental_read_from_local_dir kwargs in env.make)
if self._read_from_local_dir is not None:
if os.path.split(self._read_from_local_dir)[1] == "_grid2op_classes":
if os.path.split(self._read_from_local_dir)[1] == GRID2OP_CLASSES_ENV_FOLDER:
self._do_not_erase_local_dir_cls = True
else:
self._do_not_erase_local_dir_cls = True
Expand Down Expand Up @@ -301,10 +343,27 @@ def _aux_add_class_file(self, env_for_init):
cls_res_me = self._aux_aux_add_class_file(sys_path, env_for_init)
return cls_res_me
return None


def _aux_make_backend_from_cls(self, backendClass, backend_kwargs):
# Special case for backend
try:
# should pass with grid2op >= 1.7.1
bk = backendClass(**backend_kwargs)
except TypeError as exc_:
# with grid2Op version prior to 1.7.1
# you might have trouble with
# "TypeError: __init__() got an unexpected keyword argument 'can_be_copied'"
msg_ = ("Impossible to create a backend for each mix using the "
"backend key-word arguments. Falling back to creating "
"with no argument at all (default behaviour with grid2op <= 1.7.0).")
warnings.warn(msg_)
bk = backendClass()
return bk

def _aux_create_a_mix(self,
envs_dir,
mix_name,
is_first_mix,
logger,
backendClass,
backend_kwargs,
Expand All @@ -326,45 +385,46 @@ def _aux_create_a_mix(self,
else None
)
mix_path = os.path.join(envs_dir, mix_name)
# Special case for backend
if backendClass is not None:
try:
# should pass with grid2op >= 1.7.1
bk = backendClass(**backend_kwargs)
except TypeError as exc_:
# with grid2Op version prior to 1.7.1
# you might have trouble with
# "TypeError: __init__() got an unexpected keyword argument 'can_be_copied'"
msg_ = ("Impossible to create a backend for each mix using the "
"backend key-word arguments. Falling back to creating "
"with no argument at all (default behaviour with grid2op <= 1.7.0).")
warnings.warn(msg_)
bk = backendClass()
mix = make(
mix_path,
backend=bk,
_add_cls_nm_bk=_add_cls_nm_bk,
_add_to_name=_add_to_name,
_compat_glop_version=_compat_glop_version,
n_busbar=n_busbar,
test=_test,
logger=this_logger,
experimental_read_from_local_dir=experimental_read_from_local_dir,
_overload_name_multimix=multi_env_name,
**kwargs,
)
kwargs_make = dict(
_add_cls_nm_bk=_add_cls_nm_bk,
_add_to_name=_add_to_name,
_compat_glop_version=_compat_glop_version,
n_busbar=n_busbar,
test=_test,
logger=this_logger,
experimental_read_from_local_dir=experimental_read_from_local_dir,
_overload_name_multimix=multi_env_name,
**kwargs)

if is_first_mix:
# in the first mix either I need to create the backend, or
# pass the backend given in argument
if self._ptr_backend_obj_first_env is not None:
# I reuse the backend passed as object on the first mix
bk = self._ptr_backend_obj_first_env
kwargs_make["backend"] = bk
elif backendClass is not None:
# Special case for backend
bk = self._aux_make_backend_from_cls(backendClass, backend_kwargs)
kwargs_make["backend"] = bk
else:
mix = make(
mix_path,
n_busbar=n_busbar,
_add_to_name=_add_to_name,
_compat_glop_version=_compat_glop_version,
test=_test,
logger=this_logger,
experimental_read_from_local_dir=experimental_read_from_local_dir,
_overload_name_multimix=multi_env_name,
**kwargs,
)
# in the other mixes, things are created with either a copy of the backend
# or a new backend from the kwargs
if self._ptr_backend_obj_first_env._can_be_copied:
bk = self._ptr_backend_obj_first_env.copy()
elif backendClass is not None:
# Special case for backend
bk = self._aux_make_backend_from_cls(self.mix_envs[0]._raw_backend_class,
self._ptr_backend_obj_first_env._my_kwargs)
kwargs_make["backend"] = bk
mix = make(
mix_path,
**kwargs_make
)
if is_first_mix and self._ptr_backend_obj_first_env is None:
# if the "backend" kwargs has not been provided in the user call to "make"
# then I save a "pointer" to the backend of the first mix
self._ptr_backend_obj_first_env = mix.backend
return mix

def get_path_env(self):
Expand Down Expand Up @@ -635,7 +695,7 @@ def __del__(self):

def generate_classes(self):
mix_for_classes = self.mix_envs[0]
path_cls = os.path.join(mix_for_classes.get_path_env(), "_grid2op_classes")
path_cls = os.path.join(mix_for_classes.get_path_env(), GRID2OP_CLASSES_ENV_FOLDER)
if not os.path.exists(path_cls):
try:
os.mkdir(path_cls)
Expand Down
8 changes: 8 additions & 0 deletions grid2op/MakeEnv/Make.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,14 @@ def make(
Other keyword argument to give more control on the environment you are creating. See
the Parameters information of the :func:`make_from_dataset_path`.
_add_cls_nm_bk: ``bool``
Internal (and new in version 1.11.0). This flag (True by default, which is a breaking
change from 1.11.0 compared to previous versions) will add the backend
name in the generated class name.
It is deactivated if classes are automatically generated by default `use_class_in_files`
is ``True``
_add_to_name:
Internal, do not use (and can only be used when setting "test=True"). If
`experimental_read_from_local_dir` is set to True, this has no effect.
Expand Down
Loading

0 comments on commit 47ae6b2

Please sign in to comment.