Skip to content

Commit

Permalink
Merge pull request #9 from BDonnot/MaintenanceHazardsSerial
Browse files Browse the repository at this point in the history
Serialization of maintenance and hazard
  • Loading branch information
BDonnot committed Nov 28, 2019
2 parents 64d90a2 + fa04a0f commit f4ac30d
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 52 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Change Log
=============

[0.3.5] - 2019-11-28
--------------------
- [ADDED] serialization of the environment modifications
- [FIXED] error messages in `grid2op.GridValue.check_validity`
- [ADDED] the changelog file
- [UPDATED] notebook getting_started/4_StudyYourAgent.ipynb to reflect these changes
- [ADDED] serialization of hazards and maintenance in actions (if any)
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 = '0.3.4'
release = '0.3.5'
version = '0.3'


Expand Down
49 changes: 48 additions & 1 deletion getting_started/4_StudyYourAgent.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"from grid2op.Utils import ActionSpace, ObservationSpace\n",
"\n",
"action_space = ActionSpace.from_dict(\"study_agent_getting_started/dict_action_space.json\")\n",
"env_modif_space = ActionSpace.from_dict(\"study_agent_getting_started/dict_env_modification_space.json\")\n",
"observation_space = ObservationSpace.from_dict(\"study_agent_getting_started/dict_observation_space.json\")"
]
},
Expand Down Expand Up @@ -176,7 +177,13 @@
"li_observations = []\n",
"for i in range(observations_npy.shape[0]):\n",
" tmp = observation_space.from_vect(observations_npy[i,:])\n",
" li_observations.append(tmp)"
" li_observations.append(tmp)\n",
" \n",
"env_modifications = np.load(os.path.join(path_episode_1, \"env_modifications.npy\"))\n",
"li_env_modifs = []\n",
"for i in range(env_modifications.shape[0]):\n",
" tmp = env_modif_space.from_vect(env_modifications[i,:])\n",
" li_env_modifs.append(tmp)"
]
},
{
Expand Down Expand Up @@ -231,6 +238,46 @@
"line_disconnected"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Inspect the modification of the environment"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For example, we might want to inspect the number of hazards and maintenance of a total scenario, to see how difficult it was."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"nb_hazards = 0\n",
"nb_maintenance = 0\n",
"for act in li_env_modifs:\n",
" dict_ = act.as_dict() # representation of an action as a dictionnary, see the documentation for more information\n",
" if \"nb_hazards\" in dict_:\n",
" nb_hazards += 1\n",
" if \"nb_maintenance\" in dict_:\n",
" nb_maintenance += 1\n",
"nb_maintenance"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"dict_"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
56 changes: 43 additions & 13 deletions grid2op/Action.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ def __init__(self, n_gen, n_load, n_lines, subs_info, dim_topo,
self._subs_impacted = None
self._lines_impacted = None

# add the hazards and maintenance usefull for saving.
self._hazards = np.full(shape=n_lines, fill_value=False, dtype=np.bool)
self._maintenance = np.full(shape=n_lines, fill_value=False, dtype=np.bool)

def get_set_line_status_vect(self):
"""
Computes and return a vector that can be used in the "set_status" keyword if building an :class:`Action`.
Expand Down Expand Up @@ -562,6 +566,10 @@ def reset(self):
self._set_topo_vect = np.full(shape=self._dim_topo, fill_value=0, dtype=np.int)
self._change_bus_vect = np.full(shape=self._dim_topo, fill_value=False, dtype=np.bool)

# add the hazards and maintenance usefull for saving.
self._hazards = np.full(shape=self._n_lines, fill_value=False, dtype=np.bool)
self._maintenance = np.full(shape=self._n_lines, fill_value=False, dtype=np.bool)

self.as_vect = None

def __call__(self):
Expand Down Expand Up @@ -754,9 +762,8 @@ def _digest_hazards(self, dict_):
elif not np.issubdtype(tmp.dtype, np.dtype(int).type):
raise AmbiguousAction("You can only ask hazards with int or boolean numpy array vector.")

# if len(dict_["outage"]) != self._n_lines:
# raise InvalidNumberOfLines("This action acts on {} lines while there are {} in the _grid".format(len(dict_["outage"]), self._n_lines))
self._set_line_status[tmp] = -1
self._hazards[tmp] = True
# force ignore of any topological actions
self._ignore_topo_action_if_disconnection(tmp)

Expand All @@ -765,10 +772,6 @@ def _digest_maintenance(self, dict_):
# set the values of the power lines to "disconnected" for element being "False"
# does nothing to the others
# a _maintenance operation will never reconnect a powerline

# if len(dict_["_maintenance"]) != self._n_lines:
# raise InvalidNumberOfLines("This action acts on {} lines while there are {} in the _grid".format(len(dict_["_maintenance"]), self._n_lines))

if dict_["maintenance"] is not None:
tmp = dict_["maintenance"]
try:
Expand All @@ -785,6 +788,7 @@ def _digest_maintenance(self, dict_):
raise AmbiguousAction(
"You can only ask to perform lines maintenance with int or boolean numpy array vector.")
self._set_line_status[tmp] = -1
self._maintenance[tmp] = True
self._ignore_topo_action_if_disconnection(tmp)

def _digest_change_status(self, dict_):
Expand Down Expand Up @@ -1036,7 +1040,7 @@ def size(self):
size: ``int``
The size of the flatten array returned by :func:`Action.to_vect`.
"""
return 2 * self._n_gen + 2 * self._n_load + 2 * self._n_lines + 2 * self._dim_topo
return 2 * self._n_gen + 2 * self._n_load + 2 * self._n_lines + 2 * self._dim_topo + 2 * self._n_lines

def to_vect(self):
"""
Expand Down Expand Up @@ -1107,7 +1111,9 @@ def to_vect(self):
self._set_line_status.flatten().astype(np.float),
self._switch_line_status.flatten().astype(np.float),
self._set_topo_vect.flatten().astype(np.float),
self._change_bus_vect.flatten().astype(np.float)
self._change_bus_vect.flatten().astype(np.float),
self._hazards.flatten().astype(np.float),
self._maintenance.flatten().astype(np.float)
))

if self.as_vect.shape[0] != self.size():
Expand Down Expand Up @@ -1154,7 +1160,7 @@ def from_vect(self, vect):
if np.any(np.isfinite(prod_p)):
self._dict_inj["prod_p"] = prod_p
if np.any(np.isfinite(prod_q)):
self._dict_inj["prod_q"] = prod_q
self._dict_inj["prod_v"] = prod_q
if np.any(np.isfinite(load_p)):
self._dict_inj["load_p"] = load_p
if np.any(np.isfinite(load_q)):
Expand All @@ -1166,9 +1172,14 @@ def from_vect(self, vect):
self._switch_line_status = self._switch_line_status.astype(np.bool)
self._set_topo_vect = vect[prev_:next_]; prev_ += self._dim_topo; next_ += self._dim_topo
self._set_topo_vect = self._set_topo_vect.astype(np.int)
self._change_bus_vect = vect[prev_:]; prev_ += self._dim_topo
self._change_bus_vect = vect[prev_:next_]; prev_ += self._dim_topo; next_ += self._n_lines
self._change_bus_vect = self._change_bus_vect.astype(np.bool)

self._hazards = vect[prev_:next_]; prev_ += self._n_lines; next_ += self._n_lines
self._hazards = self._hazards.astype(np.bool)
self._maintenance = vect[prev_:]; prev_ += self._n_lines; next_ += self._n_lines
self._maintenance = self._maintenance.astype(np.bool)

self._check_for_ambiguity()

def sample(self):
Expand Down Expand Up @@ -1238,7 +1249,7 @@ def __str__(self):
res = ["This action will:"]
# handles actions on injections
inj_changed = False
for k in ["load_p", "prod_p", "load_q", "prod_q"]:
for k in ["load_p", "prod_p", "load_q", "prod_v"]:
if k in self._dict_inj:
inj_changed = True
res.append("\t - set {} to {}".format(k, list(self._dict_inj[k])))
Expand Down Expand Up @@ -1292,7 +1303,8 @@ def __str__(self):
def as_dict(self):
"""
Represent an action "as a" dictionnary. This dictionnary is usefull to further inspect on which elements
the actions had an impact. It is not recommended to use it as a way to serialize actions.
the actions had an impact. It is not recommended to use it as a way to serialize actions. The do nothing action
should allways be represented by an empty dictionnary.
The following keys (all optional) are present in the results:
Expand Down Expand Up @@ -1330,6 +1342,16 @@ def as_dict(self):
* `set_bus_vect`: details the objects that are modified. It is also a dictionnary that representes for
each impacted substations (keys) how the elements connected to it are impacted (their "new" bus)
* `hazards` if the action is composed of some hazards. In this case it's simply the index of the powerlines
that are disconnected because of them.
* `nb_hazards` the number of hazards the "action" implemented (eg number of powerlines disconnected because of
hazards.
* `maintenance` if the action is composed of some maintenance. In this case it's simply the index of the
powerlines that are affected by maintenance operation at this time step.
that are disconnected because of them.
* `nb_maintenance` the number of maintenance the "action" implemented eg the number of powerlines
disconnected becaues of maintenance operation.
Returns
-------
res: ``dict``
Expand All @@ -1338,7 +1360,7 @@ def as_dict(self):
res = {}

# saving the injections
for k in ["load_p", "prod_p", "load_q", "prod_q"]:
for k in ["load_p", "prod_p", "load_q", "prod_v"]:
if k in self._dict_inj:
res[k] = self._dict_inj[k]

Expand Down Expand Up @@ -1390,6 +1412,14 @@ def as_dict(self):
res["set_bus_vect"]["nb_modif_subs"] = len(all_subs)
res["set_bus_vect"]["modif_subs_id"] = sorted(all_subs)

if np.any(self._hazards):
res["hazards"] = np.where(self._hazards)[0]
res["nb_hazards"] = np.sum(self._hazards)

if np.any(self._maintenance):
res["maintenance"] = np.where(self._maintenance)[0]
res["nb_maintenance"] = np.sum(self._maintenance)

return res

def effect_on(self, _sentinel=None, load_id=None, gen_id=None, line_id=None, substation_id=None):
Expand Down
14 changes: 7 additions & 7 deletions grid2op/ChronicsHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,6 @@ def initialize(self, order_backend_loads, order_backend_prods, order_backend_lin
self._assert_correct_second_stage(maintenance.columns, self.names_chronics_to_backend, "lines", "maintenance")
order_backend_maintenance = np.array([order_backend_lines[self.names_chronics_to_backend["lines"][el]]
for el in maintenance.columns]).astype(np.int)

self.load_p = copy.deepcopy(load_p.values[:, np.argsort(order_chronics_load_p)])
self.load_q = copy.deepcopy(load_q.values[:, np.argsort(order_backend_load_q)])
self.prod_p = copy.deepcopy(prod_p.values[:, np.argsort(order_backend_prod_p)])
Expand Down Expand Up @@ -721,20 +720,21 @@ def check_validity(self, backend):
-------
``None``
"""

if self.load_p.shape[1] != backend.n_loads:
raise IncorrectNumberOfLoads("for the active part. It should be {} but is in fact {}".format(backend.n_loads, len(self.load_p)))
raise IncorrectNumberOfLoads("for the active part. It should be {} but is in fact {}".format(backend.n_loads, self.load_p.shape[1]))
if self.load_q.shape[1] != backend.n_loads:
raise IncorrectNumberOfLoads("for the reactive part. It should be {} but is in fact {}".format(backend.n_loads, len(self.load_q)))
raise IncorrectNumberOfLoads("for the reactive part. It should be {} but is in fact {}".format(backend.n_loads, self.load_q.shape[1]))

if self.prod_p.shape[1] != backend.n_generators:
raise IncorrectNumberOfGenerators("for the active part. It should be {} but is in fact {}".format(backend.n_generators, len(self.prod_p)))
raise IncorrectNumberOfGenerators("for the active part. It should be {} but is in fact {}".format(backend.n_generators, self.prod_p.shape[1]))
if self.prod_v.shape[1] != backend.n_generators:
raise IncorrectNumberOfGenerators("for the voltage part. It should be {} but is in fact {}".format(backend.n_generators, len(self.prod_v)))
raise IncorrectNumberOfGenerators("for the voltage part. It should be {} but is in fact {}".format(backend.n_generators, self.prod_v.shape[1]))

if self.hazards.shape[1] != backend.n_lines:
raise IncorrectNumberOfLines("for the outage. It should be {} but is in fact {}".format(backend.n_lines, len(self.hazards)))
raise IncorrectNumberOfLines("for the outage. It should be {} but is in fact {}".format(backend.n_lines, self.hazards.shape[1]))
if self.maintenance.shape[1] != backend.n_lines:
raise IncorrectNumberOfLines("for the maintenance. It should be {} but is in fact {}".format(backend.n_lines, len(self.maintenance)))
raise IncorrectNumberOfLines("for the maintenance. It should be {} but is in fact {}".format(backend.n_lines, self.maintenance.shape[1]))

n = self.load_p.shape[0]
for name_arr, arr in zip(["load_q", "load_p", "prod_v", "prod_p", "maintenance", "hazards"],
Expand Down
9 changes: 7 additions & 2 deletions grid2op/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ class Environment:
viewer: ``object``
Used to display the powergrid. Currently not supported.
env_modification: :class:`grid2op.Action.Action`
Representation of the actions of the environment for the modification of the powergrid.
"""
def __init__(self,
init_grid_path: str,
Expand Down Expand Up @@ -304,6 +308,7 @@ def __init__(self,
self._injection = None
self._maintenance = None
self._hazards = None
self.env_modification = None

# reward
self.reward_helper = RewardHelper(rewardClass=rewardClass)
Expand Down Expand Up @@ -449,8 +454,8 @@ def step(self, action):
try:
beg_ = time.time()
self.backend.apply_action(action)
env_modification = self._update_actions()
self.backend.apply_action(env_modification)
self.env_modification = self._update_actions()
self.backend.apply_action(self.env_modification)
self._time_apply_act += time.time() - beg_

self.nb_time_step += 1
Expand Down
14 changes: 14 additions & 0 deletions grid2op/Runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
disconnected when the :class:`grid2op.Agent` takes the :class:`grid2op.Action` at time step `i`.
- "observations.npy" is a numpy 2d array reprensenting the :class:`grid2op.Observation.Observation` at the disposal of the
:class:`grid2op.Agent` when he took his action.
- "env_modifications.npy" is a 2d numpy array representing the modification of the powergrid from the environment.
these modification usually concerns the hazards, maintenance, as well as modification of the generators production
setpoint or the loads consumption.
All of the above should allow to read back, and better understand the behaviour of some :class:`grid2op.Agent.Agent`, even
though such utility functions have not been coded yet.
Expand Down Expand Up @@ -479,6 +482,7 @@ def run_one_episode(self, indx=0, path_save=None):
def _run_one_episode(env, agent, logger, indx, path_save=None):
done = False
time_step = int(0)
dict_ = {}
time_act = 0.
cum_reward = 0.

Expand All @@ -492,9 +496,14 @@ def _run_one_episode(env, agent, logger, indx, path_save=None):
dict_action_space = env.action_space.to_dict()
with open(os.path.join(path_save, "dict_action_space.json"), "w", encoding='utf8') as f:
json.dump(obj=dict_action_space, fp=f, indent=4, sort_keys=True)
if not os.path.exists(os.path.join(path_save, "dict_observation_space.json")):
dict_observation_space = env.observation_space.to_dict()
with open(os.path.join(path_save, "dict_observation_space.json"), "w", encoding='utf8') as f:
json.dump(obj=dict_observation_space, fp=f, indent=4, sort_keys=True)
if not os.path.exists(os.path.join(path_save, "dict_env_modification_space.json")):
dict_action_space = env.helper_action_env.to_dict()
with open(os.path.join(path_save, "dict_env_modification_space.json"), "w", encoding='utf8') as f:
json.dump(obj=dict_action_space, fp=f, indent=4, sort_keys=True)

this_path = os.path.join(path_save, "{}".format(os.path.split(env.chronics_handler.get_id())[-1]))
if not os.path.exists(this_path):
Expand Down Expand Up @@ -522,6 +531,7 @@ def _run_one_episode(env, agent, logger, indx, path_save=None):
times = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float)
rewards = np.full(nb_timestep_max, fill_value=np.NaN, dtype=np.float)
actions = np.full((nb_timestep_max, env.action_space.n), fill_value=np.NaN, dtype=np.float)
env_actions = np.full((nb_timestep_max, env.helper_action_env.n), fill_value=np.NaN, dtype=np.float)
observations = np.full((nb_timestep_max, env.observation_space.n), fill_value=np.NaN, dtype=np.float)
disc_lines = np.full((nb_timestep_max, env.backend.n_lines), fill_value=np.NaN, dtype=np.bool)
disc_lines_templ = np.full((1, env.backend.n_lines), fill_value=False, dtype=np.bool)
Expand All @@ -540,11 +550,13 @@ def _run_one_episode(env, agent, logger, indx, path_save=None):

# save the results
if path_save is not None:
env_act = env.env_modification
if efficient_storing:
# efficient way of writing
times[time_step-1] = end__ - beg__
rewards[time_step-1] = reward
actions[time_step-1, :] = act.to_vect()
env_actions[time_step-1, :] = env_act.to_vect()
observations[time_step-1, :] = obs.to_vect()
if "disc_lines" in info:
arr = info["disc_lines"]
Expand All @@ -557,6 +569,7 @@ def _run_one_episode(env, agent, logger, indx, path_save=None):
times = np.concatenate((times, (end__ - beg__, )))
rewards = np.concatenate((rewards, (reward, )))
actions = np.concatenate((actions, act.to_vect()))
env_actions = np.concatenate((actions, env_act.to_vect()))
observations = np.concatenate((observations, obs.to_vect()))
if "disc_lines" in info:
arr = info["disc_lines"]
Expand All @@ -575,6 +588,7 @@ def _run_one_episode(env, agent, logger, indx, path_save=None):

np.save(os.path.join(this_path, "agent_exec_times.npy"), times)
np.save(os.path.join(this_path, "actions.npy"), actions)
np.save(os.path.join(this_path, "env_modifications.npy"), env_actions)
np.save(os.path.join(this_path, "observations.npy"), observations)
np.save(os.path.join(this_path, "disc_lines_cascading_failure.npy"), disc_lines)
np.save(os.path.join(this_path, "rewards.npy"), rewards)
Expand Down
2 changes: 1 addition & 1 deletion grid2op/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import pkg_resources

__version__ = '0.3.4'
__version__ = '0.3.5'

__all__ = ['Action', "BackendPandaPower", "Agent", "Backend", "ChronicsHandler", "Environment", "Exceptions",
"Observation", "Parameters", "GameRules", "Reward", "Runner", "main", "Utils"]
Expand Down

0 comments on commit f4ac30d

Please sign in to comment.