Skip to content

Commit

Permalink
fixing bug #89
Browse files Browse the repository at this point in the history
  • Loading branch information
BDonnot committed May 15, 2020
1 parent ed518ae commit 544675c
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 32 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ 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.0] - 2020-05-??
----------------------
- [FIXED] `Issue #84 <https://github.com/rte-france/Grid2Op/issues/84>`_: it is now possible to load multiple
environments in the same python script and perform random action on each.
- [FIXED] `Issue #89 <https://github.com/rte-france/Grid2Op/issues/89>`_: pandapower backend should not be compatible
with changing the bus of the generator representing the slack bus.


[0.8.2] - 2020-05-13
----------------------
- [FIXED] `Issue #75 <https://github.com/rte-france/Grid2Op/issues/75>`_: PlotGrid displays double powerlines correctly.
Expand Down
35 changes: 22 additions & 13 deletions grid2op/Action/SerializableActionSpace.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,18 +398,19 @@ def get_all_unitary_topologies_change(action_space):
res = []
S = [0, 1]
for sub_id, num_el in enumerate(action_space.sub_info):
if num_el < 4:
pass

for tup in itertools.product(S, repeat=num_el - 1):
indx = np.full(shape=num_el, fill_value=False, dtype=dt_bool)
tup = np.array((0, *tup)).astype(dt_bool) # add a zero to first element -> break symmetry
indx[tup] = True
# TODO this need to be checked
# if np.sum(indx) >= 2 and np.sum(~indx) >= 2:
# i need 2 elements on each bus at least
action = action_space({"change_bus": {"substations_id": [(sub_id, indx)]}})
res.append(action)
already_set = set()
for tup_ in itertools.product(S, repeat=num_el):
if tup_ not in already_set:
indx = np.full(shape=num_el, fill_value=False, dtype=dt_bool)
# tup = np.array((0, *tup)).astype(dt_bool) # add a zero to first element -> break symmetry
tup = np.array(tup_).astype(dt_bool) # add a zero to first element -> break symmetry
indx[tup] = True
action = action_space({"change_bus": {"substations_id": [(sub_id, indx)]}})
already_set.add(tup_)
already_set.add(tuple([1-el for el in tup_]))
res.append(action)
# otherwise, the change has already beend added (NB by symmetry , if there are A, B, C and D in
# a substation, changing A,B or changing C,D always has the same effect.
return res

@staticmethod
Expand Down Expand Up @@ -451,7 +452,8 @@ def get_all_unitary_topologies_set(action_space):
tup = np.array((0, *tup)).astype(dt_bool) # add a zero to first element -> break symmetry
indx[tup] = True
if np.sum(indx) >= 2 and np.sum(~indx) >= 2:
# i need 2 elements on each bus at least
# i need 2 elements on each bus at least (almost all the times, except when a powerline
# is alone on its bus)
new_topo = np.full(shape=num_el, fill_value=1, dtype=dt_int)
new_topo[~indx] = 2

Expand All @@ -461,6 +463,13 @@ def get_all_unitary_topologies_set(action_space):

action = action_space({"set_bus": {"substations_id": [(sub_id, new_topo)]}})
tmp.append(action)
else:
# i need to take into account the case where 1 powerline is alone on a bus too
if np.sum(indx[powerlines_id]) >= 1 and np.sum(~indx[powerlines_id]) >= 1:
new_topo = np.full(shape=num_el, fill_value=1, dtype=dt_int)
new_topo[~indx] = 2
action = action_space({"set_bus": {"substations_id": [(sub_id, new_topo)]}})
tmp.append(action)

if len(tmp) >= 2:
# if i have only one single topology on this substation, it doesn't make any action
Expand Down
8 changes: 4 additions & 4 deletions grid2op/Agent/GreedyAgent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from abc import abstractmethod
import numpy as np
from grid2op.Agent.BaseAgent import BaseAgent
from grid2op.dtypes import dt_float
import pdb


class GreedyAgent(BaseAgent):
Expand Down Expand Up @@ -52,14 +54,12 @@ def act(self, observation, reward, done=False):
"""
self.tested_action = self._get_tested_action(observation)
if len(self.tested_action) > 1:
all_rewards = np.full(shape=len(self.tested_action), fill_value=np.NaN, dtype=np.float)
all_rewards = np.full(shape=len(self.tested_action), fill_value=np.NaN, dtype=dt_float)
for i, action in enumerate(self.tested_action):
simul_obs, simul_reward, simul_has_error, simul_info = observation.simulate(action)
all_rewards[i] = simul_reward

reward_idx = np.argmax(all_rewards) # rewards.index(max(rewards))
reward_idx = int(np.argmax(all_rewards)) # rewards.index(max(rewards))
best_action = self.tested_action[reward_idx]
# print("reward_idx: {}".format(reward_idx))
else:
best_action = self.tested_action[0]
return best_action
Expand Down
1 change: 1 addition & 0 deletions grid2op/Agent/TopologyGreedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, action_space):
def _get_tested_action(self, observation):
if self.li_actions is None:
res = [self.action_space({})] # add the do nothing
# better change are still "bugged" for now
# res += self.action_space.get_all_unitary_topologies_set(self.action_space)
res += self.action_space.get_all_unitary_topologies_change(self.action_space)
self.li_actions = res
Expand Down
13 changes: 9 additions & 4 deletions grid2op/Backend/PandaPowerBackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def load_grid(self, path=None, filename=None):
raise RuntimeError("Impossible to recognize the powergrid")
bus_gen_added = ppc2pd[int(el)]
i_ref = i

break
self._iref_slack = i_ref
self._id_bus_added = self._grid.gen.shape[0]
# see https://matpower.org/docs/ref/matpower5.0/idx_gen.html for details on the comprehension of self._grid._ppc
Expand Down Expand Up @@ -494,6 +494,10 @@ def apply_action(self, backendAction=None):
elif type_obj == "gen":
new_bus_backend = self._pp_bus_from_grid2op_bus(new_bus, self._init_bus_gen[id_el_backend])
self._grid.gen["bus"].iloc[id_el_backend] = new_bus_backend
if self._iref_slack is not None:
# remember in this case slack bus is actually 2 generators for pandapower !
if id_el_backend == self._grid.gen.shape[0] -1:
self._grid.ext_grid["bus"].iloc[0] = new_bus_backend
elif type_obj == "lineor":
new_bus_backend = self._pp_bus_from_grid2op_bus(new_bus, self._init_bus_lor[id_el_backend])
if id_el_backend < self.__nb_powerline:
Expand Down Expand Up @@ -790,9 +794,10 @@ def _gens_info(self):
prod_v = self.cst_1 * self._grid.res_gen["vm_pu"].values.astype(dt_float) * self.prod_pu_to_kv
if self._iref_slack is not None:
# slack bus and added generator are on same bus. I need to add power of slack bus to this one.
if self._grid.gen["bus"].iloc[self._id_bus_added] == self.gen_to_subid[self._id_bus_added]:
prod_p[self._id_bus_added] += self._grid._ppc["gen"][self._iref_slack, 1]
prod_q[self._id_bus_added] += self._grid._ppc["gen"][self._iref_slack, 2]

# if self._grid.gen["bus"].iloc[self._id_bus_added] == self.gen_to_subid[self._id_bus_added]:
prod_p[self._id_bus_added] += self._grid._ppc["internal"]["gen"][self._iref_slack, 1]
prod_q[self._id_bus_added] += self._grid._ppc["internal"]["gen"][self._iref_slack, 2]
return prod_p, prod_q, prod_v

def generators_info(self):
Expand Down
13 changes: 8 additions & 5 deletions grid2op/Environment/BaseEnv.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def __init__(self,
thermal_limit_a=None,
epsilon_poly=1e-2,
tol_poly=1e-6,
other_rewards={}
other_rewards={},
ignore_min_up_down_times=True
):
GridObjects.__init__(self)

Expand All @@ -105,6 +106,7 @@ def __init__(self,
# data relative to interpolation
self._epsilon_poly = dt_float(epsilon_poly)
self._tol_poly = dt_float(tol_poly)
self.ignore_min_up_down_times = ignore_min_up_down_times

# define logger
self.logger = None
Expand Down Expand Up @@ -713,7 +715,8 @@ def _handle_updown_times(self, gen_up_before, redisp_act):
gen_still_connected = gen_up_before & gen_up_after
gen_still_disconnected = (~gen_up_before) & (~gen_up_after)

if np.any(self.gen_downtime[gen_connected_this_timestep] < self.gen_min_downtime[gen_connected_this_timestep]):
if np.any(self.gen_downtime[gen_connected_this_timestep] < self.gen_min_downtime[gen_connected_this_timestep]) \
and not self.ignore_min_up_down_times:
# i reconnected a generator before the minimum time allowed
id_gen = self.gen_downtime[gen_connected_this_timestep] < self.gen_min_downtime[gen_connected_this_timestep]
id_gen = np.where(id_gen)[0]
Expand All @@ -724,7 +727,8 @@ def _handle_updown_times(self, gen_up_before, redisp_act):
self.gen_downtime[gen_connected_this_timestep] = -1
self.gen_uptime[gen_connected_this_timestep] = 1

if np.any(self.gen_uptime[gen_disconnected_this] < self.gen_min_uptime[gen_disconnected_this]):
if np.any(self.gen_uptime[gen_disconnected_this] < self.gen_min_uptime[gen_disconnected_this]) and \
not self.ignore_min_up_down_times:
# i disconnected a generator before the minimum time allowed
id_gen = self.gen_uptime[gen_disconnected_this] < self.gen_min_uptime[gen_disconnected_this]
id_gen = np.where(id_gen)[0]
Expand Down Expand Up @@ -901,7 +905,6 @@ def step(self, action):
try:
# compute the next _grid state
beg_ = time.time()
# print("line status: {}".format(np.sum(self.backend.get_line_status())))
disc_lines, infos = self.backend.next_grid_state(env=self, is_dc=self.env_dc)
self._time_powerflow += time.time() - beg_

Expand Down Expand Up @@ -945,7 +948,6 @@ def step(self, action):
# of the system. So basically, when it's too high (higher than the ramp) it can
# mess up the rest of the environment
self.gen_activeprod_t_redisp[:] = new_p + self.actual_dispatch

has_error = False
except Grid2OpException as e:
except_.append(e)
Expand All @@ -972,6 +974,7 @@ def step(self, action):
is_ambiguous)
infos["rewards"] = other_reward
# TODO documentation on all the possible way to be illegal now

return self.current_obs, self.current_reward, self.done, infos

def _get_reward(self, action, has_error, is_done, is_illegal, is_ambiguous):
Expand Down
7 changes: 5 additions & 2 deletions grid2op/Environment/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,16 @@ def __init__(self,
tol_poly=1e-6,
opponent_action_class=DontAct,
opponent_class=BaseOpponent,
opponent_init_budget=0
opponent_init_budget=0,
ignore_min_up_down_times=True,
):
BaseEnv.__init__(self,
parameters=parameters,
thermal_limit_a=thermal_limit_a,
epsilon_poly=epsilon_poly,
tol_poly=tol_poly,
other_rewards=other_rewards)
other_rewards=other_rewards,
ignore_min_up_down_times=ignore_min_up_down_times)
if name == "unknown":
warnings.warn("It is NOT recommended to create an environment without \"make\" and EVEN LESS "
"to use an environment without a name")
Expand Down Expand Up @@ -654,6 +656,7 @@ def get_kwargs(self):
res["opponent_action_class"] = self.opponent_action_class
res["opponent_class"] = self.opponent_class
res["opponent_init_budget"] = self.opponent_init_budget
res["ignore_min_up_down_times"] = self.ignore_min_up_down_times
res["name"] = self.name
return res

Expand Down
15 changes: 12 additions & 3 deletions grid2op/tests/test_Agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,23 @@ def _aux_test_agent(self, agent, i_max=30):
i = 0
beg_ = time.time()
cum_reward = dt_float(0.0)
act = self.env.helper_action_player({})
obs = self.env.get_obs()
reward = 0.
time_act = 0.
all_acts = []
while not done:
# print("---------")
obs, reward, done, info = self.env.step(act) # should load the first time stamp
# print("_______________")
beg__ = time.time()
act = agent.act(obs, reward, done)
all_acts.append(act)
end__ = time.time()
obs, reward, done, info = self.env.step(act) # should load the first time stamp
time_act += end__ - beg__
cum_reward += reward
# print("reward: {}".format(reward))
# print("_______________")
# if reward <= 0 or np.any(obs.prod_p < 0):
# pdb.set_trace()
i += 1
if i > i_max:
break
Expand Down Expand Up @@ -112,6 +117,10 @@ def test_2_busswitch(self):
expected_reward = dt_float(12075.389) # i have more actions now, so this is not correct (though it should be..
# yet a proof that https://github.com/rte-france/Grid2Op/issues/86 is grounded
expected_reward = dt_float(12277.632)
pdb.set_trace()
# 12076.356
# 12076.191
expected_reward = dt_float(12076.356)
assert np.abs(cum_reward - expected_reward) <= self.tol_one, "The reward has not been properly computed"


Expand Down
21 changes: 20 additions & 1 deletion grid2op/tests/test_PandaPowerBackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import pdb
import warnings

import grid2op
from grid2op.tests.helper_path_test import HelperTests, PATH_DATA_TEST_PP

from grid2op.Action import ActionSpace, CompleteAction
from grid2op.Backend import PandaPowerBackend
from grid2op.Parameters import Parameters
Expand Down Expand Up @@ -1198,5 +1198,24 @@ def test_this(self):
assert obs.v_or[line_id] == 0. # is not 0 however line is not connected


class TestChangeBusSlack(unittest.TestCase):
def setUp(self):
self.tolvect = 1e-2
self.tol_one = 1e-5

def test_change_slack_case14(self):
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
env = grid2op.make("rte_case14_realistic", test=True)
action = env.action_space({"set_bus": {"generators_id": [(-1, 2)], "lines_or_id": [(0, 2)]}})
obs, reward, am_i_done, info = env.step(action)
assert am_i_done is False
assert np.all(obs.prod_p >= 0.)
assert np.sum(obs.prod_p) >= np.sum(obs.load_p)
p_subs, q_subs, p_bus, q_bus = env.backend.check_kirchoff()
assert np.all(np.abs(p_subs) <= self.tol_one)
assert np.all(np.abs(p_bus) <= self.tol_one)


if __name__ == "__main__":
unittest.main()

0 comments on commit 544675c

Please sign in to comment.