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 #669

Merged
merged 6 commits into from
Dec 2, 2024
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
18 changes: 17 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ Native multi agents support:
- [FIXED] the `obs.get_forecast_env` : in some cases the resulting first
observation (obtained from `for_env.reset()`) did not have the correct
topology.
- [FIXED] issue https://github.com/Grid2op/grid2op/issues/665 (`obs.reset()`
was not correctly implemented: some attributes were forgotten)
- [FIXED] issue https://github.com/Grid2op/grid2op/issues/667 (`act.as_serializable_dict()`
was not correctly implemented AND the `_aux_affect_object_int` and `_aux_affect_object_float`
have been also fixed - weird behaviour when you give them a list with the exact length of the
object you tried to modified (for example a list with a size of `n_load` that affected the loads))
- [FIXED] a bug when using the `DoNothingHandler` for the maintenance and the
environment data
- [FIXED] an issue preventing to set the thermal limit in the options
if the last simulated action lead to a game over
- [ADDED] possibility to set the "thermal limits" when calling `env.reset(..., options={"thermal limit": xxx})`
- [ADDED] possibility to retrieve some structural information about elements with
with `gridobj.get_line_info(...)`, `gridobj.get_load_info(...)`, `gridobj.get_gen_info(...)`
Expand All @@ -138,7 +148,13 @@ Native multi agents support:
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)

- [IMPROVED] the `act.as_serializable_dict()` to be more 'backend agnostic'as
it nows tries to use the name of the elements in the json output
- [IMPROVED] the way shunt data are digested in the `BaseAction` class (it is now
possible to use the same things as for the other types of element)
- [IMPROVED] grid2op does not require the `chronics` folder when using the `FromHandlers`
class

[1.10.4] - 2024-10-15
-------------------------
- [FIXED] new pypi link (no change in code)
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Benjamin Donnot'

# The full version, including alpha/beta/rc tags
release = '1.11.0.dev1'
release = '1.11.0.dev2'
version = '1.11'


Expand Down
151 changes: 72 additions & 79 deletions grid2op/Action/baseAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,13 +592,13 @@ def __deepcopy__(self, memodict={}) -> "BaseAction":

return res

def _aux_serialize_add_key_change(self, attr_nm, dict_key, res):
tmp_ = [int(id_) for id_, val in enumerate(getattr(self, attr_nm)) if val]
def _aux_serialize_add_key_change(self, attr_nm, dict_key, res, vect_id_to_name):
tmp_ = [str(vect_id_to_name[id_]) for id_, val in enumerate(getattr(self, attr_nm)) if val]
if tmp_:
res[dict_key] = tmp_

def _aux_serialize_add_key_set(self, attr_nm, dict_key, res):
tmp_ = [(int(id_), int(val)) for id_, val in enumerate(getattr(self, attr_nm)) if np.abs(val) >= 1e-7]
def _aux_serialize_add_key_set(self, attr_nm, dict_key, res, vect_id_to_name):
tmp_ = [(str(vect_id_to_name[id_]), int(val)) for id_, val in enumerate(getattr(self, attr_nm)) if np.abs(val) >= 1e-7]
if tmp_:
res[dict_key] = tmp_

Expand Down Expand Up @@ -651,37 +651,37 @@ def as_serializable_dict(self) -> dict:

if self._modif_change_bus:
res["change_bus"] = {}
self._aux_serialize_add_key_change("load_change_bus", "loads_id", res["change_bus"])
self._aux_serialize_add_key_change("gen_change_bus", "generators_id", res["change_bus"])
self._aux_serialize_add_key_change("line_or_change_bus", "lines_or_id", res["change_bus"])
self._aux_serialize_add_key_change("line_ex_change_bus", "lines_ex_id", res["change_bus"])
self._aux_serialize_add_key_change("load_change_bus", "loads_id", res["change_bus"], cls.name_load)
self._aux_serialize_add_key_change("gen_change_bus", "generators_id", res["change_bus"], cls.name_gen)
self._aux_serialize_add_key_change("line_or_change_bus", "lines_or_id", res["change_bus"], cls.name_line)
self._aux_serialize_add_key_change("line_ex_change_bus", "lines_ex_id", res["change_bus"], cls.name_line)
if hasattr(cls, "n_storage") and cls.n_storage:
self._aux_serialize_add_key_change("storage_change_bus", "storages_id", res["change_bus"])
self._aux_serialize_add_key_change("storage_change_bus", "storages_id", res["change_bus"], cls.name_storage)
if not res["change_bus"]:
del res["change_bus"]

if self._modif_change_status:
res["change_line_status"] = [
int(id_) for id_, val in enumerate(self._switch_line_status) if val
str(cls.name_line[id_]) for id_, val in enumerate(self._switch_line_status) if val
]
if not res["change_line_status"]:
del res["change_line_status"]

# int elements
if self._modif_set_bus:
res["set_bus"] = {}
self._aux_serialize_add_key_set("load_set_bus", "loads_id", res["set_bus"])
self._aux_serialize_add_key_set("gen_set_bus", "generators_id", res["set_bus"])
self._aux_serialize_add_key_set("line_or_set_bus", "lines_or_id", res["set_bus"])
self._aux_serialize_add_key_set("line_ex_set_bus", "lines_ex_id", res["set_bus"])
self._aux_serialize_add_key_set("load_set_bus", "loads_id", res["set_bus"], cls.name_load)
self._aux_serialize_add_key_set("gen_set_bus", "generators_id", res["set_bus"], cls.name_gen)
self._aux_serialize_add_key_set("line_or_set_bus", "lines_or_id", res["set_bus"], cls.name_line)
self._aux_serialize_add_key_set("line_ex_set_bus", "lines_ex_id", res["set_bus"], cls.name_line)
if hasattr(cls, "n_storage") and cls.n_storage:
self._aux_serialize_add_key_set("storage_set_bus", "storages_id", res["set_bus"])
self._aux_serialize_add_key_set("storage_set_bus", "storages_id", res["set_bus"], cls.name_storage)
if not res["set_bus"]:
del res["set_bus"]

if self._modif_set_status:
res["set_line_status"] = [
(int(id_), int(val))
(str(cls.name_line[id_]), int(val))
for id_, val in enumerate(self._set_line_status)
if val != 0
]
Expand All @@ -691,7 +691,7 @@ def as_serializable_dict(self) -> dict:
# float elements
if self._modif_redispatch:
res["redispatch"] = [
(int(id_), float(val))
(str(cls.name_gen[id_]), float(val))
for id_, val in enumerate(self._redispatch)
if np.abs(val) >= 1e-7
]
Expand All @@ -700,7 +700,7 @@ def as_serializable_dict(self) -> dict:

if self._modif_storage:
res["set_storage"] = [
(int(id_), float(val))
(str(cls.name_storage[id_]), float(val))
for id_, val in enumerate(self._storage_power)
if np.abs(val) >= 1e-7
]
Expand All @@ -709,7 +709,7 @@ def as_serializable_dict(self) -> dict:

if self._modif_curtailment:
res["curtail"] = [
(int(id_), float(val))
(str(cls.name_gen[id_]), float(val))
for id_, val in enumerate(self._curtail)
if np.abs(val + 1.) >= 1e-7
]
Expand All @@ -719,25 +719,26 @@ def as_serializable_dict(self) -> dict:
# more advanced options
if self._modif_inj:
res["injection"] = {}
for ky in ["prod_p", "prod_v", "load_p", "load_q"]:
for ky, vect_nm in zip(["prod_p", "prod_v", "load_p", "load_q"],
[cls.name_gen, cls.name_gen, cls.name_load, cls.name_load]):
if ky in self._dict_inj:
res["injection"][ky] = [float(val) for val in self._dict_inj[ky]]
res["injection"][ky] = {str(vect_nm[i]): float(val) for i, val in enumerate(self._dict_inj[ky])}
if not res["injection"]:
del res["injection"]

if cls.shunts_data_available:
res["shunt"] = {}
if np.isfinite(self.shunt_p).any():
res["shunt"]["shunt_p"] = [
(int(sh_id), float(val)) for sh_id, val in enumerate(self.shunt_p) if np.isfinite(val)
(str(cls.name_shunt[sh_id]), float(val)) for sh_id, val in enumerate(self.shunt_p) if np.isfinite(val)
]
if np.isfinite(self.shunt_q).any():
res["shunt"]["shunt_q"] = [
(int(sh_id), float(val)) for sh_id, val in enumerate(self.shunt_q) if np.isfinite(val)
(str(cls.name_shunt[sh_id]), float(val)) for sh_id, val in enumerate(self.shunt_q) if np.isfinite(val)
]
if (self.shunt_bus != 0).any():
res["shunt"]["shunt_bus"] = [
(int(sh_id), int(val))
(str(cls.name_shunt[sh_id]), int(val))
for sh_id, val in enumerate(self.shunt_bus)
if val != 0
]
Expand Down Expand Up @@ -1860,60 +1861,51 @@ def __call__(self) -> Tuple[dict, np.ndarray, np.ndarray, np.ndarray, np.ndarray

def _digest_shunt(self, dict_):
cls = type(self)
if "shunt" in dict_:
ddict_ = dict_["shunt"]

key_shunt_reco = {"set_bus", "shunt_p", "shunt_q", "shunt_bus"}
for k in ddict_:
if k not in key_shunt_reco:
warn = "The key {} is not recognized by BaseAction when trying to modify the shunt.".format(
k
if "shunt" not in dict_:
return
ddict_ = dict_["shunt"]

key_shunt_reco = {"set_bus", "shunt_p", "shunt_q", "shunt_bus"}
for k in ddict_:
if k not in key_shunt_reco:
warn = "The key {} is not recognized by BaseAction when trying to modify the shunt.".format(
k
)
warn += " Recognized keys are {}".format(sorted(key_shunt_reco))
warnings.warn(warn)

for key_n, vect_self in zip(
["shunt_bus", "shunt_p", "shunt_q", "set_bus"],
[self.shunt_bus, self.shunt_p, self.shunt_q, self.shunt_bus],
):
if key_n in ddict_:
tmp = ddict_[key_n]
if tmp is None:
pass
elif key_n == "shunt_bus" or key_n == "set_bus":
self._aux_affect_object_int(
tmp,
key_n,
cls.n_shunt,
cls.name_shunt,
np.arange(cls.n_shunt),
vect_self,
max_val=cls.n_busbar_per_sub
)
elif key_n == "shunt_p" or key_n == "shunt_q":
self._aux_affect_object_float(
tmp,
key_n,
cls.n_shunt,
cls.name_shunt,
np.arange(cls.n_shunt),
vect_self
)
else:
raise AmbiguousAction(
"Invalid way to modify {} for shunts. It should be a numpy array or a "
"list, found {}.".format(key_n, type(tmp))
)
warn += " Recognized keys are {}".format(sorted(key_shunt_reco))
warnings.warn(warn)
for key_n, vect_self in zip(
["shunt_bus", "shunt_p", "shunt_q", "set_bus"],
[self.shunt_bus, self.shunt_p, self.shunt_q, self.shunt_bus],
):
if key_n in ddict_:
tmp = ddict_[key_n]
if isinstance(tmp, np.ndarray):
# complete shunt vector is provided
vect_self[:] = tmp
elif isinstance(tmp, list):
# expected a list: (id shunt, new bus)
for (sh_id, new_bus) in tmp:
if sh_id < 0:
raise AmbiguousAction(
"Invalid shunt id {}. Shunt id should be positive".format(
sh_id
)
)
if sh_id >= cls.n_shunt:
raise AmbiguousAction(
"Invalid shunt id {}. Shunt id should be less than the number "
"of shunt {}".format(sh_id, cls.n_shunt)
)
if key_n == "shunt_bus" or key_n == "set_bus":
if new_bus <= -2:
raise IllegalAction(
f"Cannot ask for a shunt bus <= -2, found {new_bus} for shunt id {sh_id}"
)
elif new_bus > cls.n_busbar_per_sub:
raise IllegalAction(
f"Cannot ask for a shunt bus > {cls.n_busbar_per_sub} "
f"the maximum number of busbar per substations"
f", found {new_bus} for shunt id {sh_id}"
)

vect_self[sh_id] = new_bus
elif tmp is None:
pass
else:
raise AmbiguousAction(
"Invalid way to modify {} for shunts. It should be a numpy array or a "
"dictionary.".format(key_n)
)

def _digest_injection(self, dict_):
# I update the action
Expand Down Expand Up @@ -2265,6 +2257,7 @@ def update(self,
- "curtail" : TODO
- "raise_alarm" : TODO
- "raise_alert": TODO
- "shunt": TODO

**NB**: CHANGES: you can reconnect a powerline without specifying on each bus you reconnect it at both its
ends. In that case the last known bus id for each its end is used.
Expand Down Expand Up @@ -4059,7 +4052,7 @@ def _aux_affect_object_int(
if len(values) == nb_els:
# 2 cases: either i set all loads in the form [(0,..), (1,..), (2,...)]
# or i should have converted the list to np array
if isinstance(values[0], tuple):
if isinstance(values[0], (tuple, list)):
# list of tuple, handled below
# TODO can be somewhat "hacked" if the type of the object on the list is not always the same
pass
Expand Down Expand Up @@ -5492,7 +5485,7 @@ def _aux_affect_object_float(
raise IllegalAction(
f"Impossible to set {name_el} values with a single float."
)
elif isinstance(values[0], tuple):
elif isinstance(values[0], (tuple, list)):
# list of tuple, handled below
# TODO can be somewhat "hacked" if the type of the object on the list is not always the same
pass
Expand Down
6 changes: 4 additions & 2 deletions grid2op/Chronics/time_series_from_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,17 @@ def load_next(self):
res["injection"] = dict_inj

# load maintenance
maintenance_time, maintenance_duration = None, None
if self.maintenance_handler is not None:
tmp_ = self.maintenance_handler.load_next(res)
if tmp_ is not None:
res["maintenance"] = tmp_
maintenance_time, maintenance_duration = self.maintenance_handler.load_next_maintenance()
else:
if maintenance_time is None:
maintenance_time = self._no_mh_time
if maintenance_duration is None:
maintenance_duration = self._no_mh_duration

# load hazards
if self.hazard_duration is not None:
res["hazards"] = self.hazards_handler.load_next(res)
Expand Down
2 changes: 2 additions & 0 deletions grid2op/Environment/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,8 @@ def reset(self,
if ambiguous:
raise Grid2OpException("You provided an invalid (ambiguous) action to set the 'init state'") from except_tmp
init_state.remove_change()
if self.observation_space.obs_env is not None:
self.observation_space.obs_env.reset()
super().reset(seed=seed, options=options)

if options is not None and "max step" in options:
Expand Down
8 changes: 5 additions & 3 deletions grid2op/MakeEnv/MakeFromPath.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
FromNPY,
FromChronix2grid,
GridStateFromFile,
FromHandlers,
GridValue)
from grid2op.Space import GRID2OP_CLASSES_ENV_FOLDER
from grid2op.Action import BaseAction, DontAct
Expand Down Expand Up @@ -342,7 +343,7 @@ def make_from_dataset_path(
print(exc_)
raise EnvError(
"Invalid dataset config file: {}".format(config_path_abs)
) from None
) from exc_

# Get graph layout
graph_layout = None
Expand Down Expand Up @@ -558,7 +559,6 @@ def make_from_dataset_path(
chronics_class_cfg = ChangeNothing
if "chronics_class" in config_data and config_data["chronics_class"] is not None:
chronics_class_cfg = config_data["chronics_class"]

# Get default Grid class
grid_value_class_cfg = GridStateFromFile
if (
Expand Down Expand Up @@ -605,7 +605,9 @@ def make_from_dataset_path(
if (
((chronics_class_used != ChangeNothing) and
(chronics_class_used != FromNPY) and
(chronics_class_used != FromChronix2grid))
(chronics_class_used != FromChronix2grid) and
(chronics_class_used != FromHandlers)
)
) and exc_chronics is not None:
raise EnvError(
f"Impossible to find the chronics for your environment. Please make sure to provide "
Expand Down
6 changes: 3 additions & 3 deletions grid2op/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.

"""
Grid2Op

Grid2Op a testbed platform to model sequential decision making in power systems.
"""
__version__ = '1.11.0.dev1'

__version__ = '1.11.0.dev2'

__all__ = [
"Action",
Expand Down
2 changes: 1 addition & 1 deletion grid2op/tests/BaseBackendTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2092,7 +2092,7 @@ def test_shunt_ambiguous_id_incorrect(self):
backend=backend,
_add_to_name=type(self).__name__ + "_1"
) as env_case2:
with self.assertRaises(AmbiguousAction):
with self.assertRaises(IllegalAction):
act = env_case2.action_space({"shunt": {"set_bus": [(0, 2)]}})

def test_shunt_effect(self):
Expand Down
Loading