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

Merged
merged 6 commits into from
Jul 6, 2020
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
9 changes: 8 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ Change Log
- [???] properly model interconnecting powerlines
- [???] model curtailment
- [???] model batteries / pumped storage in grid2op (generator but that can be charged / discharged)
- [???] model dumps in grid2op (stuff that have a given energy max, and cannot produce more than the available energy)
- [???] model dumps (as in dump storage) in grid2op (stuff that have a given energy max, and cannot produce more than the available energy)

[1.1.1] - 2020-07-06
---------------------
- [FIXED] the EpisodeData now properly propagates the end of the episode
- [UPDATED] notebook 3 to reflect the change made a long time ago for the ambiguous action
(when a powerline is reconnected)
- [FIXED] `MultiFolder.split_and_save` function did not use properly the "seed"

[1.1.0] - 2020-07-03
---------------------
Expand Down
8 changes: 4 additions & 4 deletions getting_started/3_Action_GridManipulation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"* action $a_3$ is \"**set** the bus of $c_1$ to bus 1\". It is equivalent to doing nothing since $c_1$ is already connected on bus 1.\n",
"* action $a_4$ is \"**set** the bus of $g_1$ to bus 1\". It will change the bus of $g_1$ and assign it to bus 1.\n",
"\n",
"\\* **NB** Another breaking change compared to the pypownet implementation is the introduction of \"ambiguous\" actions. When a powerline is disconneted, it is not connected to any bus (by definition). So if you reconnect it without specifying on which \"bus\" it's \"ambiguous\". This action will not be implemented and the episode will terminate. Here, we try to reconnect the powerline $l_3$ without specifying on which bus we want to reconnect it, which will lead to an \"ambiguous\" action. More details about this will be given later in this notebook."
"\\* **NB** Another breaking change compared to the pypownet implementation is the introduction of \"ambiguous\" actions. When an action can be understood in different ways or have different meanings, then it will be replaced by a \"do nothing\" action by the environment."
]
},
{
Expand Down Expand Up @@ -158,7 +158,7 @@
" - It affects the \"*powerlines*\" in an incorrect manner:\n",
"\n",
" - it tries to change the status or to assign to a specific bus a powerline that doesn't exist\n",
" - somes lines are reconnected but the action doesn't specify on which bus.\n",
" - ~~somes lines are reconnected but the action doesn't specify on which bus.~~ It used to be like this, but now an \"automaton\" directly coded in the environment will assign the previous bus if that is the case. **You can reconnect a powerline without specifying on which bus** and in that case the last known buses when the powerline was connected will be used.\n",
" - for some powerline, the status is both **changed** and **set**\n",
"\n",
" - It has an ambiguous behaviour concerning the topology of some substations\n",
Expand All @@ -171,14 +171,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## II) Action Helper / action space"
"## II) Action space"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**IMPORTANT NOTICE** Each *Agent* has its own `action helper` attribute that can be called with `self.action_space`. This is the only recommended way to create a valid *Action*. Using its constructor is strongly NOT recommended, as it requires a deep knowledge of all the elements in the powergrid, as well as their names, their type, the order in which they are used in the backend, etc. For performance reasons, no sanity check are performed to make sure the action that would be created this way is compatible with the backend.\n",
"**IMPORTANT NOTICE** Each *Agent* has its own `action space` attribute that can be called with `self.action_space`. This is the only recommended way to create a valid *Action*. Using its constructor is strongly NOT recommended, as it requires a deep knowledge of all the elements in the powergrid, as well as their names, their type, the order in which they are used in the backend, etc. For performance reasons, no sanity check are performed to make sure the action that would be created this way is compatible with the backend.\n",
"\n",
"In the next cell, we retrieve the action space used by the Agent."
]
Expand Down
11 changes: 3 additions & 8 deletions grid2op/Action/BaseAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1115,9 +1115,8 @@ def update(self, dict_):
then it will be moved on bus 2, and if it were on bus 2, it will be moved on bus 1. If the object is
disconnected then the action is ambiguous, and calling it will throw an AmbiguousAction exception.

**NB**: if a powerline is reconnected, it should be specified on the "set_bus" action at which buses it
should be reconnected. Otherwise, action cannot be used. Trying to apply the action to the grid will
lead to an "AmbiguousAction" exception.
**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.

**NB**: if for a given powerline, both switch_line_status and set_line_status is set, the action will not
be usable.
Expand Down Expand Up @@ -1164,8 +1163,7 @@ def update(self, dict_):
disconnect_powerline2 = env.disconnect_powerline(line_id=1)

*Example 3*: force the reconnection of the powerline of id 5 by connected it to bus 1 on its origin end and
bus 2 on its extremity end. Note that this is mandatory to specify on which bus to reconnect each
extremity of the powerline. Otherwise it's an ambiguous action.
bus 2 on its extremity end.

.. code-block:: python

Expand Down Expand Up @@ -1269,9 +1267,6 @@ def _check_for_ambiguity(self):

- :code:`self._switch_line_status` has not the same size as the number of powerlines
- :code:`self._set_line_status` has not the same size as the number of powerlines
- somes lines are reconnected (:code:`self._switch_line_status[i] == True` for some powerline *i* but it is
not specified on which bus to connect it ( the element corresponding to powerline *i* in
:code:`self._set_topo_vect` is set to 0)
- the status of some powerline is both *changed* (:code:`self._switch_line_status[i] == True` for some *i*)
and *set* (:code:`self._set_line_status[i]` for the same *i* is not 0)

Expand Down
1 change: 0 additions & 1 deletion grid2op/Chronics/GridStateFromFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,6 @@ def split_and_save(self, datetime_beg, datetime_end, path_out):
while curr_dt < datetime_start:
curr_dt, *_ = tmp.load_next()
real_init_dt = curr_dt

arrays = self._init_res_split(nb_rows)
i = 0
while curr_dt < datetime_end:
Expand Down
25 changes: 24 additions & 1 deletion grid2op/Chronics/MultiFolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.

import os
import json
import warnings
import numpy as np
from datetime import timedelta, datetime

Expand Down Expand Up @@ -358,7 +360,7 @@ def split_and_save(self, datetime_beg, datetime_end, path_out):
for subpath in self.subpaths:
id_this_chron = os.path.split(subpath)[-1]
datetime_end[id_this_chron] = datetime_end_orig

seed_chronics_all = {}
for subpath in self.subpaths:
id_this_chron = os.path.split(subpath)[-1]
if not id_this_chron in datetime_beg:
Expand All @@ -368,6 +370,12 @@ def split_and_save(self, datetime_beg, datetime_end, path_out):
path=subpath,
max_iter=self.max_iter,
chunk_size=self.chunk_size)
seed_chronics = None
if self.seed is not None:
max_int = np.iinfo(dt_int).max
seed_chronics = self.space_prng.randint(max_int)
tmp.seed(seed_chronics)
seed_chronics_all[subpath] = seed_chronics
tmp.initialize(self._order_backend_loads,
self._order_backend_prods,
self._order_backend_lines,
Expand All @@ -376,6 +384,21 @@ def split_and_save(self, datetime_beg, datetime_end, path_out):
path_out_chron = os.path.join(path_out, id_this_chron)
tmp.split_and_save(datetime_beg[id_this_chron], datetime_end[id_this_chron], path_out_chron)

meta_params = {}
meta_params["datetime_beg"] = datetime_beg
meta_params["datetime_end"] = datetime_end
meta_params["path_out"] = path_out
meta_params["all_seeds"] = seed_chronics_all
try:
with open(os.path.join(path_out, "split_and_save_meta_params.json"), "w", encoding="utf-8") as f:
json.dump(obj=meta_params, fp=f,
sort_keys=True,
indent=4
)
except Exception as exc_:
warnings.warn("Impossible to save the \"metadata\" for the chronics with error:\n\"{}\""
"".format(exc_))

def fast_forward(self, nb_timestep):
"""
This method allows you to skip some time step at the beginning of the chronics.
Expand Down
25 changes: 23 additions & 2 deletions grid2op/Environment/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ def __init__(self,
if _raw_backend_class is None:
self._raw_backend_class = type(backend)
else:
_raw_backend_class = _raw_backend_class
self._raw_backend_class = _raw_backend_class

# for plotting
self.init_backend(init_grid_path, chronics_handler, backend,
names_chronics_to_backend, actionClass, observationClass,
Expand Down Expand Up @@ -426,7 +427,7 @@ def set_id(self, id_):
from grid2op import make
from grid2op.BaseAgent import DoNothingAgent

env = make("case14_redisp") # create an environment
env = make("rte_case14_realistic") # create an environment
agent = DoNothingAgent(env.action_space) # create an BaseAgent

for i in range(10):
Expand All @@ -438,6 +439,26 @@ def set_id(self, id_):
act = agent.act(obs, reward, done)
obs, reward, done, info = env.step(act)

And here you have an example on how you can loop through the scenarios in a given order:

.. code-block:: python

import grid2op
from grid2op import make
from grid2op.BaseAgent import DoNothingAgent

env = make("rte_case14_realistic") # create an environment
agent = DoNothingAgent(env.action_space) # create an BaseAgent
scenario_order = [1,2,3,4,5,10,8,6,5,7,78, 8]
for id_ in scenario_order:
env.set_id(id_) # tell the environment you simply want to use the chronics with ID 0
obs = env.reset() # it is necessary to perform a reset
reward = env.reward_range[0]
done = False
while not done:
act = agent.act(obs, reward, done)
obs, reward, done, info = env.step(act)

"""
try:
id_ = int(id_)
Expand Down
4 changes: 2 additions & 2 deletions grid2op/Environment/MultiMixEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from grid2op.dtypes import dt_int, dt_float
from grid2op.Space import GridObjects, RandomObject
from grid2op.Exceptions import EnvError
from grid2op.Exceptions import EnvError, Grid2OpException


class MultiMixEnvironment(GridObjects, RandomObject):
Expand Down Expand Up @@ -183,7 +183,7 @@ def seed(self, seed=None):
except Exception as e:
raise Grid2OpException("Cannot to seed with the seed provided." \
"Make sure it can be converted to a" \
"numpy 64 integer.")
"numpy 32 bits integer.")

s = super().seed(seed)
seeds = [s]
Expand Down
23 changes: 22 additions & 1 deletion grid2op/Episode/EpisodeData.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,29 @@ def __init__(self,
self.env_actions = CollectionWrapper(env_actions,
helper_action_env,
"env_actions")
self.attack = attack
# gives a unique game over for everyone
# TODO this needs testing!
action_go = self.actions._game_over
obs_go = self.observations._game_over
env_go = self.env_actions._game_over
real_go = action_go
if real_go is None:
real_go = obs_go
else:
if obs_go is not None:
real_go = min(obs_go, real_go)
if real_go is None:
real_go = env_go
else:
if env_go is not None:
real_go = min(env_go, real_go)
if real_go is not None:
# there is a real game over, i assign the proper value for each collection
self.actions._game_over = real_go
self.observations._game_over = real_go + 1
self.env_actions._game_over = real_go + 1

self.attack = attack
self.other_rewards = other_rewards
self.observation_space = observation_space
self.rewards = rewards
Expand Down
1 change: 0 additions & 1 deletion grid2op/Episode/EpisodeReplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ def replay_episode(self, episode_id, fps=2.0, gif_name=None,
# Load episode observations
self.episode_data = EpisodeData.from_disk(agent_path=self.agent_path, name=episode_id)
all_obs = [el for el in self.episode_data.observations]

# Create a plotter
width, height = resolution
plot_runner = PlotMatplot(self.episode_data.observation_space,
Expand Down
8 changes: 2 additions & 6 deletions grid2op/Plot/BasePlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,9 @@ def _draw_powerlines(self, fig=None, vals=None, colormap=None, unit=None):
texts = [self._get_text_unit(val, unit) for val in vals[0]]

if colormap is not None:
if colormap == "line":
if colormap == "line" and unit != "%":
# normalize the value for the color map
if unit == "%":
# I dont touch percentage that are supposed to be scaled already
vals_0 = vals_0
else:
vals_0 = self._get_vals(vals[0])
vals_0 = self._get_vals(vals[0])

for line_id in range(self.n_line):
pos_or, pos_ex, *_ = self._get_line_coord(line_id)
Expand Down
16 changes: 8 additions & 8 deletions grid2op/Plot/PlotMatplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,17 @@ def _draw_loads_one_load(self, fig, l_id, pos_load, txt_, pos_end_line, pos_load

def _draw_gens_one_gen(self, fig, g_id, pos_gen, txt_, pos_end_line, pos_gen_sub, how_center, this_col):
fig, ax = fig
pos_end_line, pos_gen_sub, pos_gen, how_center = self._get_gen_coord(g_id)
ax.plot([pos_gen_sub[0], pos_gen.real],
[pos_gen_sub[1], pos_gen.imag],
pos_end_line_, pos_gen_sub_, pos_gen_, how_center_ = self._get_gen_coord(g_id)
ax.plot([pos_gen_sub_[0], pos_gen_.real],
[pos_gen_sub_[1], pos_gen_.imag],
color=this_col, alpha=self.alpha_obj)
if txt_ is not None:
verticalalignment = self._getverticalalignment(how_center)
ax.text(pos_gen.real,
pos_gen.imag,
txt_,
verticalalignment = self._getverticalalignment(how_center_)
ax.text(pos_gen_.real,
pos_gen_.imag,
txt_,
color=this_col,
horizontalalignment=how_center.split('|')[1],
horizontalalignment=how_center_.split('|')[1],
verticalalignment=verticalalignment)

def _draw_powerlines_one_powerline(self, fig, l_id, pos_or, pos_ex, status, value, txt_, or_to_ex, this_col):
Expand Down
6 changes: 3 additions & 3 deletions grid2op/PlotGrid/BasePlot.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,14 +608,14 @@ def plot_info(self,
line_unit: ``str``
Unit string for the :line_values: argument, displayed after the line value

load_info: ``list``
load_values: ``list``
information to display for the loads
[must have the same size as observation.n_load and convertible to float]

load_unit: ``str``
Unit string for the :load_values: argument, displayed after the load value

gen_info: ``list``
gen_values: ``list``
information to display in the generators
[must have the same size as observation.n_gen and convertible to float]

Expand All @@ -639,7 +639,7 @@ def plot_info(self,
"provided for {} loads in the grid".format(len(load_values), self.observation_space.n_load))
if gen_values is not None and len(gen_values) != self.observation_space.n_gen:
raise PlotError("Impossible to display these values on the generators: there are {} values"
"provided for {} generators in the grid".format(len(gen_info), self.observation_space.n_gen))
"provided for {} generators in the grid".format(len(gen_values), self.observation_space.n_gen))

# Get a valid figure to draw into
if figure is None:
Expand Down
15 changes: 11 additions & 4 deletions grid2op/Space/GridObjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,17 @@ def from_vect(self, vect):
"from a vector. Found {} elements instead of {}".format(
vect.shape[0], self.size()))

try:
vect = np.array(vect).astype(dt_float)
except Exception as exc_:
raise AmbiguousAction("Impossible to convert the input vector to a floating point numy array with error:\n"
"\"{}\".".format(exc_))

self._raise_error_attr_list_none()
prev_ = 0
for attr_nm, sh, dt in zip(self.attr_list_vect, self.shape(), self.dtype()):
self._assign_attr_from_name(attr_nm, vect[prev_:(prev_ + sh)].astype(dt))
tmp = vect[prev_:(prev_ + sh)].astype(dt)
self._assign_attr_from_name(attr_nm, tmp)
prev_ += sh
self.check_space_legit()

Expand Down Expand Up @@ -1248,7 +1255,7 @@ def get_lines_id(self, _sentinel=None, from_=None, to_=None):
if ori == from_ and ext == to_:
res.append(i)

if res is []:
if not res: # res is empty here
raise BackendError("ObservationSpace.get_line_id: impossible to find a powerline with connected at "
"origin at {} and extremity at {}".format(from_, to_))

Expand Down Expand Up @@ -1284,7 +1291,7 @@ def get_generators_id(self, sub_id):
if s_id_gen == sub_id:
res.append(i)

if res is []:
if not res: # res is empty here
raise BackendError(
"GridObjects.get_generators_id: impossible to find a generator connected at "
"substation {}".format(sub_id))
Expand Down Expand Up @@ -1320,7 +1327,7 @@ def get_loads_id(self, sub_id):
if s_id_gen == sub_id:
res.append(i)

if res is []:
if not res: # res is empty here
raise BackendError(
"GridObjects.get_loads_id: impossible to find a load connected at substation {}".format(sub_id))

Expand Down
2 changes: 2 additions & 0 deletions grid2op/tests/test_Action.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import json
import re
import warnings
import unittest
import numpy as np
import pdb
from abc import ABC, abstractmethod

Expand Down
Loading