Skip to content

Commit bb6c8ac

Browse files
committed
forget to rename ForecastEnv in one file, identify and put in test a bug with the reconnection of a generator or load
Signed-off-by: DONNOT Benjamin <[email protected]>
1 parent 570a7e8 commit bb6c8ac

File tree

6 files changed

+259
-24
lines changed

6 files changed

+259
-24
lines changed

grid2op/Environment/_obsEnv.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.
88

99
import copy
10+
import datetime
1011
import numpy as np
1112
import warnings
1213
from typing import Dict, Union, Tuple, List, Optional, Any, Literal
1314

1415
import grid2op
16+
import grid2op.Action
17+
import grid2op.Observation # for type hints
1518
from grid2op.typing_variables import STEP_INFO_TYPING
1619
from grid2op.dtypes import dt_int, dt_float, dt_bool
1720
from grid2op.Exceptions import EnvError
@@ -315,10 +318,10 @@ def _reset_to_orig_state(self, obs):
315318

316319
def init(
317320
self,
318-
new_state_action,
319-
time_stamp,
320-
obs,
321-
time_step=1
321+
new_state_action : "grid2op.Action.BaseAction",
322+
time_stamp: datetime.datetime,
323+
obs : "grid2op.Observation.CompleteObservation",
324+
time_step : int=1
322325
):
323326
"""
324327
INTERNAL
@@ -381,18 +384,21 @@ def init(
381384
else:
382385
set_status = self._line_status_me
383386
topo_vect = self._topo_vect
384-
385387
# TODO set the shunts here
386388
# update the action that set the grid to the real value
389+
gen_p = obs._get_gen_p_for_forecasts()
390+
gen_v = obs._get_gen_v_for_forecasts()
391+
load_p = obs._get_load_p_for_forecasts()
392+
load_q = obs._get_load_q_for_forecasts()
387393
self._backend_action_set += self._helper_action_env(
388394
{
389395
"set_line_status": set_status,
390396
"set_bus": topo_vect,
391397
"injection": {
392-
"prod_p": obs.gen_p,
393-
"prod_v": obs.gen_v,
394-
"load_p": obs.load_p,
395-
"load_q": obs.load_q,
398+
"prod_p": gen_p,
399+
"prod_v": gen_v,
400+
"load_p": load_p,
401+
"load_q": load_q,
396402
},
397403
}
398404
)
@@ -401,8 +407,8 @@ def init(
401407
if time_step > 0:
402408
self._backend_action_set.storage_power.values[:] = 0.0
403409
self._backend_action_set.all_changed()
410+
self._backend_action_set._assign_0_to_disco_el()
404411
self._backend_action = copy.deepcopy(self._backend_action_set)
405-
406412
# for curtailment
407413
if self._env_modification is not None:
408414
self._env_modification._dict_inj = {}

grid2op/Environment/baseEnv.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3272,8 +3272,6 @@ def _aux_register_env_converged(self, disc_lines, action, init_line_status, new_
32723272
# finally, build the observation (it's a different one at each step, we cannot reuse the same one)
32733273
# THIS SHOULD BE DONE AFTER EVERYTHING IS INITIALIZED !
32743274
self.current_obs = self.get_obs(_do_copy=False)
3275-
# TODO storage: get back the result of the storage ! with the illegal action when a storage unit
3276-
# TODO is non zero and disconnected, this should be ok.
32773275
self._time_extract_obs += time.perf_counter() - beg_res
32783276
return None
32793277

@@ -4400,8 +4398,8 @@ def generate_classes(self, *, local_dir_id=None, _guard=None, sys_path=None, _is
44004398
_init_txt += txt_
44014399

44024400
# for the forecast env (we do this even if it's not used)
4403-
from grid2op.Environment.forecast_env import _ForecastEnv
4404-
for_env_cls = _ForecastEnv.init_grid(type(self.backend), _local_dir_cls=self._local_dir_cls)
4401+
from grid2op.Environment import ForecastEnv
4402+
for_env_cls = ForecastEnv.init_grid(type(self.backend), _local_dir_cls=self._local_dir_cls)
44054403
txt_ = self._aux_gen_classes(for_env_cls, sys_path, _add_class_output=False)
44064404
if txt_ is not None:
44074405
_init_txt += txt_

grid2op/Observation/baseObservation.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4458,18 +4458,48 @@ def _update_obs_complete(self, env: "grid2op.Environment.BaseEnv", with_forecast
44584458
# handle alerts
44594459
self._update_alert(env)
44604460

4461+
def _get_gen_p_for_forecasts(self) -> np.ndarray:
4462+
return self._get_array_for_forecast(self.gen_p, self.gen_detached)
4463+
4464+
def _get_gen_v_for_forecasts(self) -> np.ndarray:
4465+
return self._get_array_for_forecast(self.gen_v, self.gen_detached)
4466+
4467+
def _get_load_p_for_forecasts(self) -> np.ndarray:
4468+
return self._get_array_for_forecast(self.load_p, self.load_detached)
4469+
4470+
def _get_load_q_for_forecasts(self) -> np.ndarray:
4471+
return self._get_array_for_forecast(self.load_q, self.load_detached)
4472+
4473+
@staticmethod
4474+
def _get_array_for_forecast(arr, mask_detached) -> np.ndarray:
4475+
res = (1.0 * arr).astype(dt_float)
4476+
res[mask_detached] = np.nan
4477+
return res
4478+
44614479
def _update_forecast(self, env: "grid2op.Environment.BaseEnv", with_forecast: bool) -> None:
44624480
if not with_forecast:
44634481
return
4464-
4482+
cls = type(self)
44654483
inj_action = {}
44664484
dict_ = {}
4467-
dict_["load_p"] = dt_float(1.0 * self.load_p)
4468-
dict_["load_q"] = dt_float(1.0 * self.load_q)
4469-
dict_["prod_p"] = dt_float(1.0 * self.gen_p)
4470-
dict_["prod_v"] = dt_float(1.0 * self.gen_v)
4485+
dict_["load_p"] = self._get_load_p_for_forecasts()
4486+
dict_["load_q"] = self._get_load_q_for_forecasts()
4487+
dict_["prod_p"] = self._get_gen_p_for_forecasts()
4488+
dict_["prod_v"] = self._get_gen_v_for_forecasts()
44714489
inj_action["injection"] = dict_
4472-
# inj_action = self.action_helper(inj_action)
4490+
if self.gen_detached.any():
4491+
if "set_bus" not in inj_action:
4492+
inj_action["set_bus"] = {}
4493+
tmp_gen = np.zeros(cls.n_gen, dtype=dt_int)
4494+
tmp_gen[self.gen_detached] = -1
4495+
inj_action["set_bus"]["generators_id"] = tmp_gen
4496+
if self.load_detached.any():
4497+
if "set_bus" not in inj_action:
4498+
inj_action["set_bus"] = {}
4499+
tmp_load = np.zeros(cls.n_load, dtype=dt_int)
4500+
tmp_load[self.load_detached] = -1
4501+
inj_action["set_bus"]["loads_id"] = tmp_load
4502+
44734503
timestamp = self.get_time_stamp()
44744504
self._forecasted_inj = [(timestamp, inj_action)]
44754505
self._forecasted_inj += env.forecasts()

grid2op/tests/aaa_test_backend_interface.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,7 +1769,7 @@ def test_33_allow_detachment(self):
17691769
- :attr:`TestBackendAPI.test_20_disconnected_load_stops_computation`
17701770
- :attr:`TestBackendAPI.test_21_disconnected_gen_stops_computation`
17711771
1772-
When your backend is initialized with "allow_detachment".
1772+
When your backend is initialized with "allow_detachment=True".
17731773
17741774
NB: of course these tests have been modified such that things that should pass
17751775
will pass and things that should fail will fail.
@@ -1795,4 +1795,8 @@ def test_33_allow_detachment(self):
17951795
self.test_20_disconnected_load_stops_computation(allow_detachment=True)
17961796
self.test_21_disconnected_gen_stops_computation(allow_detachment=True)
17971797
self.test_31_disconnected_storage_with_p_stops_computation(allow_detachment=True)
1798-
1798+
1799+
# TODO test: gen_v of disconnected generator are 0
1800+
# TODO test : load_v of disco load are 0
1801+
# TODO test: storage_v of disco sto are 0
1802+
# TODO test: disconnect a gen a load a conso and then connect it again and see what you end up with.

grid2op/tests/automatic_classes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def test_all_classes_from_file(self,
155155
f"PandaPowerBackend_{classes_name}",
156156
name_action_cls,
157157
f"VoltageOnlyAction_{classes_name}",
158-
f"_ForecastEnv_{classes_name}",
158+
f"ForecastEnv_{classes_name}",
159159
]
160160
names_attr = ["action_space",
161161
"_backend_action_class",
@@ -168,7 +168,7 @@ def test_all_classes_from_file(self,
168168
"backend",
169169
"_actionClass",
170170
None, # VoltageOnlyAction not in env
171-
None, # _ForecastEnv_ not in env
171+
None, # ForecastEnv_ not in env
172172
]
173173
# NB: these imports needs to be consistent with what is done in
174174
# base_env.generate_classes() and gridobj.init_grid(...)
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# Copyright (c) 2025, RTE (https://www.rte-france.com)
2+
# See AUTHORS.txt
3+
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
4+
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
5+
# you can obtain one at http://mozilla.org/MPL/2.0/.
6+
# SPDX-License-Identifier: MPL-2.0
7+
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.
8+
9+
import copy
10+
import grid2op
11+
from grid2op.Agent import BaseAgent
12+
from grid2op.Action import CompleteAction
13+
from grid2op.Chronics import ChangeNothing
14+
import unittest
15+
import warnings
16+
import numpy as np
17+
import pdb
18+
# from pypowsybl2grid import PyPowSyBlBackend
19+
# from lightsim2grid import LightSimBackend
20+
21+
class TestDetachSimulate(unittest.TestCase):
22+
def setUp(self):
23+
with warnings.catch_warnings():
24+
warnings.filterwarnings("ignore")
25+
self.env = grid2op.make("educ_case14_storage",
26+
test=True,
27+
allow_detachment=True,
28+
action_class=CompleteAction,
29+
_add_to_name=type(self).__name__,
30+
chronics_class=ChangeNothing,
31+
data_feeding_kwargs={"h_forecast": [5, 10, 15, 20, 25, 30]},
32+
# backend=PyPowSyBlBackend()
33+
)
34+
paramerters = self.env.parameters
35+
paramerters.MAX_SUB_CHANGED = 99999
36+
paramerters.MAX_LINE_STATUS_CHANGED = 99999
37+
paramerters.NB_TIMESTEP_COOLDOWN_LINE = 0
38+
paramerters.NB_TIMESTEP_COOLDOWN_SUB = 0
39+
self.env.change_parameters(paramerters)
40+
self.init_obs = self.env.reset(seed=0, options={"time serie id": 0})
41+
42+
def tearDown(self):
43+
self.env.close()
44+
return super().tearDown()
45+
46+
def test_detach_gen(self, tol=1e-5):
47+
normal_v = 1. * self.init_obs.gen_v
48+
normal_p = 1. * self.init_obs.gen_p
49+
gen_id = 0
50+
act_deco = self.env.action_space({"set_bus": {"generators_id": [(gen_id, -1)]}})
51+
act_reco = self.env.action_space({"set_bus": {"generators_id": [(gen_id, +1)]}})
52+
next_obs, reward, done, info = self.env.step(act_deco)
53+
assert not done
54+
assert len(info["exception"]) == 0
55+
assert next_obs.gen_detached[gen_id]
56+
assert next_obs.gen_v[gen_id] == 0.
57+
assert next_obs.gen_p[gen_id] == 0.
58+
59+
# test simulate
60+
next_obs_sim00, reward, done, info = next_obs.simulate(self.env.action_space())
61+
assert not done
62+
assert len(info["exception"]) == 0
63+
assert next_obs_sim00.gen_detached[gen_id]
64+
assert next_obs_sim00.gen_v[gen_id] == 0.
65+
assert next_obs_sim00.gen_p[gen_id] == 0.
66+
print("here here here")
67+
next_obs_sim0, reward, done, info = next_obs.simulate(act_reco)
68+
assert not done
69+
assert len(info["exception"]) == 0
70+
assert not next_obs_sim0.gen_detached[gen_id]
71+
assert np.abs(next_obs_sim0.gen_v[gen_id] - normal_v[gen_id]) <= tol, f"error {np.abs(next_obs_sim0.gen_v[gen_id] - normal_v[gen_id]).max()}"
72+
assert np.abs(next_obs_sim0.gen_p[gen_id] - normal_p[gen_id]) <= tol, f"error {np.abs(next_obs_sim0.gen_p[gen_id] - normal_p[gen_id]).max()}"
73+
next_obs_sim1, reward, done, info = next_obs.simulate(act_deco)
74+
assert not done
75+
assert len(info["exception"]) == 0
76+
assert next_obs_sim1.gen_detached[gen_id]
77+
assert next_obs_sim1.gen_v[gen_id] == 0.
78+
assert next_obs_sim1.gen_p[gen_id] == 0.
79+
next_obs_sim2, reward, done, info = next_obs.simulate(self.env.action_space())
80+
assert not done
81+
assert len(info["exception"]) == 0
82+
assert next_obs_sim2.gen_detached[gen_id]
83+
assert next_obs_sim2.gen_v[gen_id] == 0.
84+
assert next_obs_sim2.gen_p[gen_id] == 0.
85+
next_obs_sim3, reward, done, info = next_obs.simulate(act_reco)
86+
assert not done
87+
assert len(info["exception"]) == 0
88+
assert not next_obs_sim3.gen_detached[gen_id]
89+
assert np.abs(next_obs_sim3.gen_v[gen_id] - normal_v[gen_id]) <= tol, f"error {np.abs(next_obs_sim3.gen_v[gen_id] - normal_v[gen_id]).max()}"
90+
assert np.abs(next_obs_sim3.gen_p[gen_id] - normal_p[gen_id]) <= tol, f"error {np.abs(next_obs_sim3.gen_p[gen_id] - normal_p[gen_id]).max()}"
91+
# now change the setpoint
92+
next_obs_sim4, reward, done, info = next_obs.simulate(act_reco + self.env.action_space({"injection": {"prod_v": normal_v * 1.01}}))
93+
assert not done
94+
assert len(info["exception"]) == 0
95+
assert not next_obs_sim4.gen_detached[gen_id]
96+
assert np.abs(next_obs_sim4.gen_v[gen_id] - 1.01 * normal_v[gen_id]) <= tol, f"error {np.abs(next_obs_sim4.gen_v[gen_id] - 1.01 * normal_v[gen_id]).max()}"
97+
assert np.abs(next_obs_sim4.gen_p[gen_id] - normal_p[gen_id]) <= tol, f"error {np.abs(next_obs_sim4.gen_p[gen_id] - normal_p[gen_id]).max()}"
98+
# disco
99+
next_obs_sim5, reward, done, info = next_obs.simulate(act_deco)
100+
assert not done
101+
assert len(info["exception"]) == 0
102+
assert next_obs_sim5.gen_detached[gen_id]
103+
assert next_obs_sim5.gen_v[gen_id] == 0.
104+
assert next_obs_sim5.gen_p[gen_id] == 0.
105+
# reco again (and check the setpoint is not the last one used in obs.simulate but is the one of the observation)
106+
next_obs_sim6, reward, done, info = next_obs.simulate(act_reco)
107+
assert not done
108+
assert len(info["exception"]) == 0
109+
assert not next_obs_sim6.gen_detached[gen_id]
110+
assert np.abs(next_obs_sim6.gen_v[gen_id] - normal_v[gen_id]) <= tol, f"error {np.abs(next_obs_sim6.gen_v[gen_id] - normal_v[gen_id]).max()}"
111+
assert np.abs(next_obs_sim6.gen_p[gen_id] - normal_p[gen_id]) <= tol, f"error {np.abs(next_obs_sim6.gen_p[gen_id] - normal_p[gen_id]).max()}"
112+
113+
114+
# test step
115+
next_next_obs, reward, done, info = self.env.step(act_reco)
116+
assert not done
117+
assert len(info["exception"]) == 0
118+
assert not next_next_obs.gen_detached[gen_id]
119+
assert np.abs(next_next_obs.gen_v[gen_id] - normal_v[gen_id]) <= tol, f"error {np.abs(next_next_obs.gen_v[gen_id] - normal_v[gen_id]).max()}"
120+
assert np.abs(next_next_obs.gen_p[gen_id] - normal_p[gen_id]) <= tol, f"error {np.abs(next_next_obs.gen_p[gen_id] - normal_p[gen_id]).max()}"
121+
122+
def test_detach_load(self, tol=1e-5):
123+
normal_q = 1. * self.init_obs.load_q
124+
normal_p = 1. * self.init_obs.load_p
125+
load_id = 0
126+
act_deco = self.env.action_space({"set_bus": {"loads_id": [(load_id, -1)]}})
127+
act_reco = self.env.action_space({"set_bus": {"loads_id": [(load_id, +1)]}})
128+
next_obs, reward, done, info = self.env.step(act_deco)
129+
assert not done
130+
assert len(info["exception"]) == 0
131+
assert next_obs.load_detached[load_id]
132+
assert next_obs.load_q[load_id] == 0.
133+
assert next_obs.load_p[load_id] == 0.
134+
135+
# test simulate
136+
next_obs_sim00, reward, done, info = next_obs.simulate(self.env.action_space())
137+
assert not done
138+
assert len(info["exception"]) == 0
139+
assert next_obs_sim00.load_detached[load_id]
140+
assert next_obs_sim00.load_q[load_id] == 0.
141+
assert next_obs_sim00.load_p[load_id] == 0.
142+
143+
next_obs_sim0, reward, done, info = next_obs.simulate(act_reco)
144+
assert not done
145+
assert len(info["exception"]) == 0
146+
assert not next_obs_sim0.load_detached[load_id]
147+
assert np.abs(next_obs_sim0.load_q[load_id] - normal_q[load_id]) <= tol, f"error {np.abs(next_obs_sim0.load_q[load_id] - normal_q[load_id]).max()}"
148+
assert np.abs(next_obs_sim0.load_p[load_id] - normal_p[load_id]) <= tol, f"error {np.abs(next_obs_sim0.load_p[load_id] - normal_p[load_id]).max()}"
149+
next_obs_sim1, reward, done, info = next_obs.simulate(act_deco)
150+
assert not done
151+
assert len(info["exception"]) == 0
152+
assert next_obs_sim1.load_detached[load_id]
153+
assert next_obs_sim1.load_q[load_id] == 0.
154+
assert next_obs_sim1.load_p[load_id] == 0.
155+
next_obs_sim2, reward, done, info = next_obs.simulate(self.env.action_space())
156+
assert not done
157+
assert len(info["exception"]) == 0
158+
assert next_obs_sim2.load_detached[load_id]
159+
assert next_obs_sim2.load_q[load_id] == 0.
160+
assert next_obs_sim2.load_p[load_id] == 0.
161+
next_obs_sim3, reward, done, info = next_obs.simulate(act_reco)
162+
assert not done
163+
assert len(info["exception"]) == 0
164+
assert not next_obs_sim3.load_detached[load_id]
165+
assert np.abs(next_obs_sim3.load_q[load_id] - normal_q[load_id]) <= tol, f"error {np.abs(next_obs_sim3.load_q[load_id] - normal_q[load_id]).max()}"
166+
assert np.abs(next_obs_sim3.load_p[load_id] - normal_p[load_id]) <= tol, f"error {np.abs(next_obs_sim3.load_p[load_id] - normal_p[load_id]).max()}"
167+
# now change the setpoint
168+
next_obs_sim4, reward, done, info = next_obs.simulate(act_reco + self.env.action_space({"injection": {"load_q": normal_q * 1.01}}))
169+
assert not done
170+
assert len(info["exception"]) == 0
171+
assert not next_obs_sim4.load_detached[load_id]
172+
assert np.abs(next_obs_sim4.load_q[load_id] - 1.01 * normal_q[load_id]) <= tol, f"error {np.abs(next_obs_sim4.load_q[load_id] - 1.01 * normal_q[load_id]).max()}"
173+
assert np.abs(next_obs_sim4.load_p[load_id] - normal_p[load_id]) <= tol, f"error {np.abs(next_obs_sim4.load_p[load_id] - normal_p[load_id]).max()}"
174+
# disco
175+
next_obs_sim5, reward, done, info = next_obs.simulate(act_deco)
176+
assert not done
177+
assert len(info["exception"]) == 0
178+
assert next_obs_sim5.load_detached[load_id]
179+
assert next_obs_sim5.load_q[load_id] == 0.
180+
assert next_obs_sim5.load_p[load_id] == 0.
181+
# reco again (and check the setpoint is not the last one used in obs.simulate but is the one of the observation)
182+
next_obs_sim6, reward, done, info = next_obs.simulate(act_reco)
183+
assert not done
184+
assert len(info["exception"]) == 0
185+
assert not next_obs_sim6.load_detached[load_id]
186+
assert np.abs(next_obs_sim6.load_q[load_id] - normal_q[load_id]) <= tol, f"error {np.abs(next_obs_sim6.load_q[load_id] - normal_q[load_id]).max()}"
187+
assert np.abs(next_obs_sim6.load_p[load_id] - normal_p[load_id]) <= tol, f"error {np.abs(next_obs_sim6.load_p[load_id] - normal_p[load_id]).max()}"
188+
189+
# test step
190+
next_next_obs, reward, done, info = self.env.step(act_reco)
191+
assert not done
192+
assert len(info["exception"]) == 0
193+
assert not next_next_obs.load_detached[load_id]
194+
assert np.abs(next_next_obs.load_q[load_id] - normal_q[load_id]) <= tol, f"error {np.abs(next_next_obs.load_q[load_id] - normal_q[load_id]).max()}"
195+
assert np.abs(next_next_obs.load_p[load_id] - normal_p[load_id]) <= tol, f"error {np.abs(next_next_obs.load_p[load_id] - normal_p[load_id]).max()}"
196+
197+

0 commit comments

Comments
 (0)