Skip to content

Commit

Permalink
Merge pull request #52 from BDonnot/bd_091
Browse files Browse the repository at this point in the history
Bd 091
  • Loading branch information
BDonnot committed May 21, 2020
2 parents 63fec6d + c493602 commit 81b212a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 44 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Change Log
- [???] modeled dumps in grid2op (stuff that have a given energy max, and cannot produce more than the available energy)
- [???] fix notebook 5 texts

[0.9.1] - 2020-05-20
---------------------
- [FIXED] a bug preventing to save gif with episode replay when there has been a game over before starting time step
- [FIXED] the issue of the random seed used in the environment for the runner.

[0.9.0] - 2020-05-19
----------------------
- [BREAKING] `Issue #83 <https://github.com/rte-france/Grid2Op/issues/83>`_: attributes name of the Parameters class
Expand Down
1 change: 0 additions & 1 deletion grid2op/Chronics/MultiFolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def __init__(self, path,
except FileNotFoundError:
raise ChronicsError("Path \"{}\" doesn't exists.".format(self.path)) from None


if len(self.subpaths) == 0:
raise ChronicsNotFoundError("Not chronics are found in \"{}\". Make sure there are at least "
"1 chronics folder there.".format(self.path))
Expand Down
6 changes: 5 additions & 1 deletion grid2op/Episode/EpisodeData.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def set_parameters(self, env):
if self.serialize:
self.parameters = env.parameters.to_dict()

def set_meta(self, env, time_step, cum_reward):
def set_meta(self, env, time_step, cum_reward, seed):
if self.serialize:
self.meta = {}
self.meta["chronics_path"] = "{}".format(
Expand All @@ -224,6 +224,10 @@ def set_meta(self, env, time_step, cum_reward):
self.meta["env_type"] = "{}".format(type(env).__name__)
self.meta["nb_timestep_played"] = time_step
self.meta["cumulative_reward"] = cum_reward
if seed is None:
self.meta["env_seed"] = seed
else:
self.meta["env_seed"] = int(seed)

def incr_store(self, efficient_storing, time_step, time_step_duration,
reward, env_act, act, obs, info):
Expand Down
23 changes: 13 additions & 10 deletions grid2op/Episode/EpisodeReplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,20 @@ def replay_episode(self, episode_id, fps=2.0, gif_name=None,
time.sleep(wait_time)

# Export all frames as gif if enabled
if gif_name is not None:
imageio.mimwrite(gif_path, frames, fps=fps)
# Try to compress
if gif_name is not None and len(frames) > 0:
try:
from pygifsicle import optimize
optimize(gif_path, options=["-w", "--no-conserve-memory"])
except:
warn_msg = "Failed to optimize .GIF size, but gif is still saved:\n" \
"Install dependencies to reduce size by ~3 folds\n" \
"apt-get install gifsicle && pip3 install pygifsicle"
warnings.warn(warn_msg)
imageio.mimwrite(gif_path, frames, fps=fps)
# Try to compress
try:
from pygifsicle import optimize
optimize(gif_path, options=["-w", "--no-conserve-memory"])
except:
warn_msg = "Failed to optimize .GIF size, but gif is still saved:\n" \
"Install dependencies to reduce size by ~3 folds\n" \
"apt-get install gifsicle && pip3 install pygifsicle"
warnings.warn(warn_msg)
except Exception as e:
warnings.warn("Impossible to save gif with error :\n{}".format(e))


def episode_replay_cli():
Expand Down
112 changes: 90 additions & 22 deletions grid2op/Runner/Runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from grid2op.VoltageControler import ControlVoltageFromFile
from grid2op.Opponent import BaseOpponent
from grid2op.dtypes import dt_float
from grid2op.Space import RandomObject


# TODO have a vectorized implementation of everything in case the agent is able to act on multiple environment
# at the same time. This might require a lot of work, but would be totally worth it! (especially for Neural Net based agents)
Expand Down Expand Up @@ -197,6 +199,11 @@ class Runner(object):
thermal_limit_a: ``numpy.ndarray``
The thermal limit for the environment (if any).
seed: ``int``
The seed used, for reproducible experiments (**NB** to ensure reproducible experiments even in the case of
parallel evaluation, there are absolutely not warrantee the seed used by any of the envrionment generated
will be the seed passed in this parameter.)
"""

def __init__(self,
Expand Down Expand Up @@ -225,9 +232,7 @@ def __init__(self,
opponent_action_class=DontAct,
opponent_class=BaseOpponent,
opponent_init_budget=0,
grid_layout=None,
forbid_dispatch_off=False,
ignore_min_up_down_times=True):
grid_layout=None):
"""
Initialize the Runner.
Expand Down Expand Up @@ -291,8 +296,9 @@ def __init__(self,
Whether or not to ignore gen_min_uptime and gen_min_downtime when applying redispatching actions.
(default True)
seed: ``int``
Seed used (default ``None``)
"""

self.name_env = name_env
if not isinstance(envClass, type):
raise Grid2OpException(
Expand Down Expand Up @@ -522,7 +528,7 @@ def reset(self):
else:
self.env.reset()

def run_one_episode(self, indx=0, path_save=None, pbar=False):
def run_one_episode(self, indx=0, path_save=None, pbar=False, seed=None, max_iter=None):
"""
Function used to run one episode of the :attr:`Runner.agent` and see how it performs in the :attr:`Runner.env`.
Expand All @@ -546,21 +552,32 @@ def run_one_episode(self, indx=0, path_save=None, pbar=False):
"""
self.reset()
res = self._run_one_episode(self.env, self.agent, self.logger, indx, path_save, pbar=pbar)
res = self._run_one_episode(self.env, self.agent, self.logger, indx, path_save,
pbar=pbar, seed=seed, max_iter=max_iter)
return res

@staticmethod
def _run_one_episode(env, agent, logger, indx, path_save=None, pbar=False):
def _run_one_episode(env, agent, logger, indx, path_save=None, pbar=False, seed=None, max_iter=None):
done = False
time_step = int(0)
dict_ = {}
time_act = 0.
cum_reward = dt_float(0.0)

# reset the environment
env.chronics_handler.tell_id(indx-1)
# the "-1" above is because the environment will be reset. So it will increase id of 1.

# set the seed
if seed is not None:
env.seed(seed)

# handle max_iter
if max_iter is not None:
env.chronics_handler.set_max_iter(max_iter)

# reset it
obs = env.reset()

# reset the agent
agent.reset(obs)

Expand Down Expand Up @@ -641,7 +658,7 @@ def _run_one_episode(env, agent, logger, indx, path_save=None, pbar=False):
float(reward), env.env_modification, act, obs, info)
end_ = time.time()

episode.set_meta(env, time_step, float(cum_reward))
episode.set_meta(env, time_step, float(cum_reward), seed)

li_text = ["Env: {:.2f}s", "\t - apply act {:.2f}s", "\t - run pf: {:.2f}s",
"\t - env update + observation: {:.2f}s", "Agent: {:.2f}s", "Total time: {:.2f}s",
Expand Down Expand Up @@ -675,7 +692,7 @@ def _make_progress_bar(pbar, total, next_pbar):
and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
and "desc" when being built, and the closing is ensured by this method.
- if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
(episode) but not at lower levels (setp during the episode)
(episode) but not at lower levels (step during the episode)
"""
pbar_ = _FakePbar()
next_pbar[0] = False
Expand All @@ -695,7 +712,7 @@ def _make_progress_bar(pbar, total, next_pbar):
pbar_ = pbar
return pbar_

def run_sequential(self, nb_episode, path_save=None, pbar=False):
def run_sequential(self, nb_episode, path_save=None, pbar=False, seeds=None, max_iter=None):
"""
This method is called to see how well an agent performed on a sequence of episode.
Expand All @@ -720,6 +737,10 @@ def run_sequential(self, nb_episode, path_save=None, pbar=False):
- if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
(episode) but not at lower levels (setp during the episode)
seeds: ``list``
An iterable of the seed used for the experiments. By default ``None``, no seeds are set. If provided,
its size should match ``nb_episode``.
Returns
-------
res: ``list``
Expand All @@ -737,15 +758,22 @@ def run_sequential(self, nb_episode, path_save=None, pbar=False):
next_pbar = [False]
with self._make_progress_bar(pbar, nb_episode, next_pbar) as pbar_:
for i in range(nb_episode):
name_chron, cum_reward, nb_time_step = self.run_one_episode(path_save=path_save, indx=i, pbar=next_pbar[0])
seed = None
if seeds is not None:
seed = seeds[i]
name_chron, cum_reward, nb_time_step = self.run_one_episode(path_save=path_save,
indx=i,
pbar=next_pbar[0],
seed=seed,
max_iter=max_iter)
id_chron = self.chronics_handler.get_id()
max_ts = self.chronics_handler.max_timestep()
res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step, max_ts)
pbar_.update(1)
return res

@staticmethod
def _one_process_parrallel(runner, episode_this_process, process_id, path_save=None):
def _one_process_parrallel(runner, episode_this_process, process_id, path_save=None, seeds=None, max_iter=None):
chronics_handler = ChronicsHandler(chronicsClass=runner.gridStateclass,
path=runner.path_chron,
**runner.gridStateclass_kwargs)
Expand All @@ -757,14 +785,17 @@ def _one_process_parrallel(runner, episode_this_process, process_id, path_save=N
env, agent = runner._new_env(chronics_handler=chronics_handler,
backend=backend,
parameters=parameters)
seed = None
if seeds is not None:
seed = seeds[i]
name_chron, cum_reward, nb_time_step = Runner._run_one_episode(
env, agent, runner.logger, p_id, path_save)
env, agent, runner.logger, p_id, path_save, seed=seed, max_iter=max_iter)
id_chron = chronics_handler.get_id()
max_ts = chronics_handler.max_timestep()
res[i] = (id_chron, name_chron, float(cum_reward), nb_time_step, max_ts)
return res

def run_parrallel(self, nb_episode, nb_process=1, path_save=None):
def run_parrallel(self, nb_episode, nb_process=1, path_save=None, seeds=None, max_iter=None):
"""
This method will run in parrallel, independantly the nb_episode over nb_process.
Expand All @@ -791,6 +822,10 @@ def run_parrallel(self, nb_episode, nb_process=1, path_save=None):
If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
more information
seeds: ``list``
An iterable of the seed used for the experiments. By default ``None``, no seeds are set. If provided,
its size should match ``nb_episode``.
Returns
-------
res: ``list``
Expand All @@ -809,7 +844,7 @@ def run_parrallel(self, nb_episode, nb_process=1, path_save=None):
if nb_process == 1 or self.__can_copy_agent is False:
warnings.warn(
"Runner.run_parrallel: number of process set to 1. Failing back into sequential mod.")
return [self.run_sequential(nb_episode, path_save=path_save)]
return [self.run_sequential(nb_episode, path_save=path_save, seeds=seeds)]
else:
if self.env is not None:
self.env.close()
Expand All @@ -821,15 +856,23 @@ def run_parrallel(self, nb_episode, nb_process=1, path_save=None):
for i in range(nb_episode):
process_ids[i % nb_process].append(i)

if seeds is None:
seeds_res = [None for _ in range(nb_process)]
else:
# split the seeds according to the process
seeds_res = [[] for i in range(nb_process)]
for i in range(nb_episode):
seeds_res[i % nb_process].append(seeds[i])

res = []
with Pool(nb_process) as p:
tmp = p.starmap(Runner._one_process_parrallel,
[(self, pn, i, path_save) for i, pn in enumerate(process_ids)])
[(self, pn, i, path_save, seeds_res[i], max_iter) for i, pn in enumerate(process_ids)])
for el in tmp:
res += el
return res

def run(self, nb_episode, nb_process=1, path_save=None, max_iter=None, pbar=False):
def run(self, nb_episode, nb_process=1, path_save=None, max_iter=None, pbar=False, seeds=None):
"""
Main method of the :class:`Runner` class. It will either call :func:`Runner.run_sequential` if "nb_process" is
1 or :func:`Runner.run_parrallel` if nb_process >= 2.
Expand All @@ -846,6 +889,25 @@ def run(self, nb_episode, nb_process=1, path_save=None, max_iter=None, pbar=Fals
If not None, it specifies where to store the data. See the description of this module :mod:`Runner` for
more information
max_iter: ``int``
Maximum number of iteration you want the runner to perform.
pbar: ``bool`` or ``type`` or ``object``
How to display the progress bar, understood as follow:
- if pbar is ``None`` nothing is done.
- if pbar is a boolean, tqdm pbar are used, if tqdm package is available and installed on the system
[if ``true``]. If it's false it's equivalent to pbar being ``None``
- if pbar is a ``type`` ( a class), it is used to build a progress bar at the highest level (episode) and
and the lower levels (step during the episode). If it's a type it muyst accept the argument "total"
and "desc" when being built, and the closing is ensured by this method.
- if pbar is an object (an instance of a class) it is used to make a progress bar at this highest level
(episode) but not at lower levels (setp during the episode)
seeds: ``list``
An iterable of the seed used for the experiments. By default ``None``, no seeds are set. If provided,
its size should match ``nb_episode``.
Returns
-------
res: ``list``
Expand All @@ -860,18 +922,24 @@ def run(self, nb_episode, nb_process=1, path_save=None, max_iter=None, pbar=Fals
if nb_episode < 0:
raise RuntimeError("Impossible to run a negative number of scenarios.")

if seeds is not None:
if len(seeds) != nb_episode:
raise RuntimeError("You want to compute \"{}\" run(s) but provide only \"{}\" different seeds."
"".format(nb_episode, len(seeds)))

max_iter = int(max_iter)

if nb_episode == 0:
res = []
else:
if nb_process <= 0:
raise RuntimeError("Impossible to run using less than 1 process.")
if max_iter is not None:
self.chronics_handler.set_max_iter(max_iter)

if nb_process == 1:
self.logger.info("Sequential runner used.")
res = self.run_sequential(nb_episode, path_save=path_save, pbar=pbar)
res = self.run_sequential(nb_episode, path_save=path_save, pbar=pbar, seeds=seeds, max_iter=max_iter)
else:
self.logger.info("Parallel runner used.")
res = self.run_parrallel(nb_episode, nb_process=nb_process, path_save=path_save)
res = self.run_parrallel(nb_episode, nb_process=nb_process, path_save=path_save, seeds=seeds,
max_iter=max_iter)
return res
Loading

0 comments on commit 81b212a

Please sign in to comment.