Skip to content

Commit 6c6bcef

Browse files
committed
add a memory for the last known setpoints when object was connected
Signed-off-by: DONNOT Benjamin <[email protected]>
1 parent bb6c8ac commit 6c6bcef

File tree

6 files changed

+227
-32
lines changed

6 files changed

+227
-32
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
from typing import Optional, Type
10+
import numpy as np
11+
from grid2op.Space import GridObjects
12+
import grid2op.Backend
13+
from grid2op.Exceptions import Grid2OpException
14+
15+
16+
class _EnvPreviousState(object):
17+
def __init__(self,
18+
grid_obj_cls: Type[GridObjects],
19+
init_load_p : np.ndarray,
20+
init_load_q : np.ndarray,
21+
init_gen_p : np.ndarray,
22+
init_gen_v : np.ndarray,
23+
init_topo_vect : np.ndarray,
24+
init_storage_p : np.ndarray,
25+
init_shunt_p : np.ndarray,
26+
init_shunt_q : np.ndarray,
27+
init_shunt_bus : np.ndarray):
28+
self._can_modif = True
29+
self._grid_obj_cls : Type[GridObjects] = grid_obj_cls
30+
self._load_p : np.ndarray = 1. * init_load_p
31+
self._load_q : np.ndarray = 1. * init_load_q
32+
self._gen_p : np.ndarray = 1. * init_gen_p
33+
self._gen_v : np.ndarray = 1. * init_gen_v
34+
self._storage_p : np.ndarray = 1. * init_storage_p
35+
self._topo_vect : np.ndarray = 1 * init_topo_vect
36+
self._shunt_p : np.ndarray = 1. * init_shunt_p
37+
self._shunt_q : np.ndarray = 1. * init_shunt_q
38+
self._shunt_bus : np.ndarray = 1. * init_shunt_bus
39+
40+
def update(self,
41+
load_p : np.ndarray,
42+
load_q : np.ndarray,
43+
gen_p : np.ndarray,
44+
gen_v : np.ndarray,
45+
topo_vect : np.ndarray,
46+
storage_p : Optional[np.ndarray],
47+
shunt_p : Optional[np.ndarray],
48+
shunt_q : Optional[np.ndarray],
49+
shunt_bus : Optional[np.ndarray]):
50+
if not self._can_modif:
51+
raise Grid2OpException(f"Impossible to modifiy this _EnvPreviousState")
52+
53+
self._aux_update(topo_vect[self._grid_obj_cls.load_pos_topo_vect],
54+
self._load_p,
55+
load_p,
56+
self._load_q,
57+
load_q)
58+
self._aux_update(topo_vect[self._grid_obj_cls.gen_pos_topo_vect],
59+
self._gen_p,
60+
gen_p,
61+
self._gen_v,
62+
gen_v)
63+
self._topo_vect[topo_vect > 0] = 1 * topo_vect[topo_vect > 0]
64+
65+
# update storage units
66+
if self._grid_obj_cls.n_storage > 0:
67+
self._aux_update(topo_vect[self._grid_obj_cls.storage_pos_topo_vect],
68+
self._storage_p,
69+
storage_p)
70+
71+
# handle shunts, if present
72+
if shunt_p is not None:
73+
self._aux_update(shunt_bus,
74+
self._shunt_p,
75+
shunt_p,
76+
self._shunt_q,
77+
shunt_q)
78+
self._shunt_bus[shunt_bus > 0] = 1 * shunt_bus[shunt_bus > 0]
79+
80+
def update_from_backend(self,
81+
backend: "grid2op.Backend.Backend"):
82+
if not self._can_modif:
83+
raise Grid2OpException(f"Impossible to modifiy this _EnvPreviousState")
84+
topo_vect = backend.get_topo_vect()
85+
load_p, load_q, *_ = backend.loads_info()
86+
gen_p, gen_q, gen_v = backend.generators_info()
87+
if self._grid_obj_cls.n_storage > 0:
88+
storage_p, *_ = backend.storages_info()
89+
else:
90+
storage_p = None
91+
if type(backend).shunts_data_available:
92+
shunt_p, shunt_q, shunt_v, shunt_bus = backend.shunt_info()
93+
else:
94+
shunt_p, shunt_q, shunt_v, shunt_bus = None, None, None, None
95+
self.update(load_p, load_q,
96+
gen_p, gen_v,
97+
topo_vect,
98+
storage_p,
99+
shunt_p, shunt_q, shunt_bus)
100+
101+
def update_from_other(self,
102+
other : "_EnvPreviousState"):
103+
for attr_nm in ["_load_p",
104+
"_load_q",
105+
"_gen_p",
106+
"_gen_v",
107+
"_storage_p",
108+
"_topo_vect",
109+
"_shunt_p",
110+
"_shunt_q",
111+
"_shunt_bus"]:
112+
getattr(self, attr_nm)[:] = getattr(other, attr_nm)
113+
114+
def prevent_modification(self):
115+
for attr_nm in ["_load_p",
116+
"_load_q",
117+
"_gen_p",
118+
"_gen_v",
119+
"_storage_p",
120+
"_topo_vect",
121+
"_shunt_p",
122+
"_shunt_q",
123+
"_shunt_bus"]:
124+
getattr(self, attr_nm).flags.writeable = False
125+
self._can_modif = False
126+
127+
def _aux_update(self,
128+
el_topo_vect : np.ndarray,
129+
arr1 : np.ndarray,
130+
arr1_new : np.ndarray,
131+
arr2 : Optional[np.ndarray] = None,
132+
arr2_new : Optional[np.ndarray] = None):
133+
el_co = el_topo_vect > 0
134+
arr1[el_co] = 1. * arr1_new[el_co]
135+
if arr2 is not None:
136+
arr2[el_co] = 1. * arr2_new[el_co]
137+

grid2op/Environment/_obsEnv.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import grid2op
1616
import grid2op.Action
17+
from grid2op.Environment._env_prev_state import _EnvPreviousState
1718
import grid2op.Observation # for type hints
1819
from grid2op.typing_variables import STEP_INFO_TYPING
1920
from grid2op.dtypes import dt_int, dt_float, dt_bool
@@ -61,18 +62,22 @@ def __init__(
6162
tol_poly,
6263
max_episode_duration,
6364
delta_time_seconds,
64-
other_rewards={},
65+
other_rewards=None,
6566
has_attention_budget=False,
6667
attention_budget_cls=LinearAttentionBudget,
67-
kwargs_attention_budget={},
68+
kwargs_attention_budget=None,
6869
logger=None,
6970
highres_sim_counter=None,
7071
_complete_action_cls=None,
7172
allow_detachment:bool=DEFAULT_ALLOW_DETACHMENT,
7273
_ptr_orig_obs_space=None,
73-
_local_dir_cls=None, # only set at the first call to `make(...)` after should be false
74+
_local_dir_cls=None,
7475
_read_from_local_dir=None,
7576
):
77+
if other_rewards is None:
78+
other_rewards = {}
79+
if kwargs_attention_budget is None:
80+
kwargs_attention_budget = {}
7681
BaseEnv.__init__(
7782
self,
7883
init_env_path=init_env_path,
@@ -153,12 +158,12 @@ def __init__(
153158
if self.__unusable:
154159
self._backend_action_set = None
155160
else:
156-
self._backend_action_set = self._backend_action_class()
161+
self._backend_action_set : grid2op.Action._BackendAction._BackendAction = self._backend_action_class()
157162

158163
if self.__unusable:
159164
self._disc_lines = np.zeros(shape=0, dtype=dt_int) - 1
160165
else:
161-
self._disc_lines = np.zeros(shape=self.n_line, dtype=dt_int) - 1
166+
self._disc_lines = np.zeros(shape=cls.n_line, dtype=dt_int) - 1
162167

163168
self._max_episode_duration = max_episode_duration
164169

@@ -321,7 +326,7 @@ def init(
321326
new_state_action : "grid2op.Action.BaseAction",
322327
time_stamp: datetime.datetime,
323328
obs : "grid2op.Observation.CompleteObservation",
324-
time_step : int=1
329+
time_step : int=1,
325330
):
326331
"""
327332
INTERNAL
@@ -384,8 +389,10 @@ def init(
384389
else:
385390
set_status = self._line_status_me
386391
topo_vect = self._topo_vect
392+
387393
# TODO set the shunts here
388394
# update the action that set the grid to the real value
395+
self._previous_conn_state.update_from_other(obs._prev_conn)
389396
gen_p = obs._get_gen_p_for_forecasts()
390397
gen_v = obs._get_gen_v_for_forecasts()
391398
load_p = obs._get_load_p_for_forecasts()
@@ -407,7 +414,6 @@ def init(
407414
if time_step > 0:
408415
self._backend_action_set.storage_power.values[:] = 0.0
409416
self._backend_action_set.all_changed()
410-
self._backend_action_set._assign_0_to_disco_el()
411417
self._backend_action = copy.deepcopy(self._backend_action_set)
412418
# for curtailment
413419
if self._env_modification is not None:
@@ -417,7 +423,7 @@ def init(
417423
self.current_obs.reset()
418424
self.time_stamp = time_stamp
419425

420-
def _get_new_prod_setpoint(self, action):
426+
def _get_new_prod_setpoint(self, action : "grid2op.Action.BaseAction"):
421427
new_p = 1.0 * self._backend_action_set.prod_p.values
422428
if "prod_p" in action._dict_inj:
423429
tmp = action._dict_inj["prod_p"]
@@ -490,7 +496,7 @@ def simulate(self, action : "grid2op.Action.BaseAction") -> Tuple["grid2op.Obser
490496
obs, reward, done, info = self.step(action)
491497
return obs, reward, done, info
492498

493-
def get_obs(self, _update_state=True, _do_copy=True):
499+
def get_obs(self, _update_state=True, _do_copy=True) -> "grid2op.Observation.BaseObservation":
494500
"""
495501
INTERNAL
496502
@@ -515,7 +521,7 @@ def get_obs(self, _update_state=True, _do_copy=True):
515521
res = self.current_obs
516522
return res
517523

518-
def update_grid(self, env):
524+
def update_grid(self, env : BaseEnv):
519525
"""
520526
INTERNAL
521527

grid2op/Environment/baseEnv.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from scipy.optimize import (minimize, LinearConstraint)
2323

2424
from abc import ABC, abstractmethod
25+
from grid2op.Environment._env_prev_state import _EnvPreviousState
2526
from grid2op.Observation import (BaseObservation,
2627
ObservationSpace,
2728
HighResSimCounter)
@@ -665,7 +666,11 @@ def __init__(
665666

666667
# slack (1.11.0)
667668
self._delta_gen_p = None
668-
669+
670+
# required in 1.11.0 : the previous state when the element was last connected
671+
self._previous_conn_state = None
672+
self._cst_prev_state_at_init = None
673+
669674
@property
670675
def highres_sim_counter(self):
671676
return self._highres_sim_counter
@@ -985,6 +990,10 @@ def _custom_deepcopy_for_copy(self, new_obj, dict_=None):
985990
# slack (1.11.0)
986991
new_obj._delta_gen_p = 1. * self._delta_gen_p
987992

993+
# previous connected state
994+
new_obj._previous_conn_state = copy.deepcopy(self._previous_conn_state)
995+
new_obj._cst_prev_state_at_init = self._cst_prev_state_at_init # no need to deep copy this
996+
988997
def get_path_env(self):
989998
"""
990999
Get the path that allows to create this environment.
@@ -1454,7 +1463,28 @@ def _has_been_initialized(self):
14541463

14551464
# slack (1.11.0)
14561465
self._delta_gen_p = np.zeros(bk_type.n_gen, dtype=dt_float)
1457-
1466+
1467+
# previous state (complete)
1468+
self._previous_conn_state = _EnvPreviousState(bk_type,
1469+
np.zeros(bk_type.n_load, dtype=dt_float),
1470+
np.zeros(bk_type.n_load, dtype=dt_float),
1471+
np.zeros(bk_type.n_gen, dtype=dt_float),
1472+
np.zeros(bk_type.n_gen, dtype=dt_float),
1473+
np.zeros(bk_type.dim_topo, dtype=dt_int),
1474+
np.zeros(bk_type.n_storage, dtype=dt_float),
1475+
np.zeros(bk_type.n_shunt, dtype=dt_float),
1476+
np.zeros(bk_type.n_shunt, dtype=dt_float),
1477+
np.zeros(bk_type.n_shunt, dtype=dt_int),
1478+
)
1479+
try:
1480+
self._previous_conn_state.update_from_backend(self.backend)
1481+
except Exception as exc_:
1482+
# nothing to do in this case
1483+
self.logger.warning(f"Impossible to retrieve the initial state of the grid before running the initial powerflow: {exc_}")
1484+
self._previous_conn_state._topo_vect[:] = 1 # I force assign everything to busbar 1 by default...
1485+
self._cst_prev_state_at_init = copy.deepcopy(self._previous_conn_state)
1486+
self._cst_prev_state_at_init.prevent_modification()
1487+
14581488
def _update_parameters(self):
14591489
"""update value for the new parameters"""
14601490
self._parameters = self.__new_param
@@ -3168,7 +3198,11 @@ def _update_alert_properties(self, action, lines_attacked, subs_attacked):
31683198
# TODO after alert budget will be implemented !
31693199
# self._is_alert_illegal
31703200

3171-
def _aux_register_env_converged(self, disc_lines, action, init_line_status, new_p) -> Optional[Grid2OpException]:
3201+
def _aux_register_env_converged(self,
3202+
disc_lines,
3203+
action: BaseAction,
3204+
init_line_status,
3205+
new_p) -> Optional[Grid2OpException]:
31723206
cls = type(self)
31733207
beg_res = time.perf_counter()
31743208
# update the thermal limit, for DLR for example
@@ -3272,6 +3306,10 @@ def _aux_register_env_converged(self, disc_lines, action, init_line_status, new_
32723306
# finally, build the observation (it's a different one at each step, we cannot reuse the same one)
32733307
# THIS SHOULD BE DONE AFTER EVERYTHING IS INITIALIZED !
32743308
self.current_obs = self.get_obs(_do_copy=False)
3309+
3310+
# update the previous state
3311+
self._previous_conn_state.update_from_backend(self.backend)
3312+
32753313
self._time_extract_obs += time.perf_counter() - beg_res
32763314
return None
32773315

grid2op/Environment/environment.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -945,10 +945,11 @@ def reset_grid(self,
945945
If the thermal has been modified, it also modify them into the new backend.
946946
947947
"""
948-
self.backend.reset(
949-
self._init_grid_path,
950-
) # the real powergrid of the environment
948+
# the real powergrid of the environment
949+
self.backend.reset(self._init_grid_path)
950+
951951
# self.backend.assert_grid_correct()
952+
self._previous_conn_state.update_from_other(self._cst_prev_state_at_init)
952953

953954
if self._thermal_limit_a is not None:
954955
self.backend.set_thermal_limit(self._thermal_limit_a.astype(dt_float))

0 commit comments

Comments
 (0)