Skip to content

Commit 400757d

Browse files
committed
first proposal for issue rte-france#617
1 parent d1586d3 commit 400757d

File tree

5 files changed

+179
-77
lines changed

5 files changed

+179
-77
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ Next release
7373
- [FIXED] another issue with the seeding of `MultifolderWithCache`: the seed was not used
7474
correctly on the cache data when calling `chronics_handler.reset` multiple times without
7575
any changes
76+
- [FIXED] `Backend` now properly raise EnvError (grid2op exception) instead of previously
77+
`EnvironmentError` (python default exception)
78+
- [FIXED] a bug in `PandaPowerBackend` (missing attribute) causing directly
79+
https://github.com/rte-france/Grid2Op/issues/617
80+
- [FIXED] a bug in `Environment`: the thermal limit were used when loading the environment
81+
even before the "time series" are applied (and before the user defined thermal limits were set)
82+
which could lead to disconnected powerlines even before the initial step (t=0, when time
83+
series are loaded)
7684
- [ADDED] possibility to skip some step when calling `env.reset(..., options={"init ts": ...})`
7785
- [ADDED] possibility to limit the duration of an episode with `env.reset(..., options={"max step": ...})`
7886
- [ADDED] possibility to specify the "reset_options" used in `env.reset` when

grid2op/Backend/backend.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,12 +1019,7 @@ def _runpf_with_diverging_exception(self, is_dc : bool) -> Optional[Exception]:
10191019
conv, exc_me = self.runpf(is_dc=is_dc) # run powerflow
10201020
except Grid2OpException as exc_:
10211021
exc_me = exc_
1022-
# except Exception as exc_:
1023-
# exc_me = DivergingPowerflow(
1024-
# f" An unexpected error occurred during the computation of the powerflow."
1025-
# f"The error is: \n {exc_} \n. This is game over"
1026-
# )
1027-
1022+
10281023
if not conv and exc_me is None:
10291024
exc_me = DivergingPowerflow(
10301025
"GAME OVER: Powerflow has diverged during computation "
@@ -2160,22 +2155,22 @@ def assert_grid_correct_after_powerflow(self) -> None:
21602155
if tmp.shape[0] != self.n_line:
21612156
raise IncorrectNumberOfLines('returned by "backend.get_line_status()"')
21622157
if (~np.isfinite(tmp)).any():
2163-
raise EnvironmentError(type(self).ERR_INIT_POWERFLOW)
2158+
raise EnvError(type(self).ERR_INIT_POWERFLOW)
21642159
tmp = self.get_line_flow()
21652160
if tmp.shape[0] != self.n_line:
21662161
raise IncorrectNumberOfLines('returned by "backend.get_line_flow()"')
21672162
if (~np.isfinite(tmp)).any():
2168-
raise EnvironmentError(type(self).ERR_INIT_POWERFLOW)
2163+
raise EnvError(type(self).ERR_INIT_POWERFLOW)
21692164
tmp = self.get_thermal_limit()
21702165
if tmp.shape[0] != self.n_line:
21712166
raise IncorrectNumberOfLines('returned by "backend.get_thermal_limit()"')
21722167
if (~np.isfinite(tmp)).any():
2173-
raise EnvironmentError(type(self).ERR_INIT_POWERFLOW)
2168+
raise EnvError(type(self).ERR_INIT_POWERFLOW)
21742169
tmp = self.get_line_overflow()
21752170
if tmp.shape[0] != self.n_line:
21762171
raise IncorrectNumberOfLines('returned by "backend.get_line_overflow()"')
21772172
if (~np.isfinite(tmp)).any():
2178-
raise EnvironmentError(type(self).ERR_INIT_POWERFLOW)
2173+
raise EnvError(type(self).ERR_INIT_POWERFLOW)
21792174

21802175
tmp = self.generators_info()
21812176
if len(tmp) != 3:

grid2op/Backend/pandaPowerBackend.py

Lines changed: 56 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def __init__(
223223
self._in_service_line_col_id = None
224224
self._in_service_trafo_col_id = None
225225
self._in_service_storage_cold_id = None
226+
self.div_exception = None
226227

227228
def _check_for_non_modeled_elements(self):
228229
"""This function check for elements in the pandapower grid that will have no impact on grid2op.
@@ -353,30 +354,15 @@ def load_grid(self,
353354
i_ref = None
354355
self._iref_slack = None
355356
self._id_bus_added = None
356-
with warnings.catch_warnings():
357-
warnings.filterwarnings("ignore")
358-
try:
359-
pp.runpp(
360-
self._grid,
361-
numba=self.with_numba,
362-
lightsim2grid=self._lightsim2grid,
363-
distributed_slack=self._dist_slack,
364-
max_iteration=self._max_iter,
365-
)
366-
except pp.powerflow.LoadflowNotConverged:
367-
pp.rundcpp(
368-
self._grid,
369-
numba=self.with_numba,
370-
lightsim2grid=self._lightsim2grid,
371-
distributed_slack=self._dist_slack,
372-
max_iteration=self._max_iter,
373-
)
357+
358+
self._aux_run_pf_init() # run an intiail powerflow, just in case
359+
374360
new_pp_version = False
375361
if not "slack_weight" in self._grid.gen:
376362
self._grid.gen["slack_weight"] = 1.0
377363
else:
378364
new_pp_version = True
379-
365+
380366
if np.all(~self._grid.gen["slack"]):
381367
# there are not defined slack bus on the data, i need to hack it up a little bit
382368
pd2ppc = self._grid._pd2ppc_lookups["bus"] # pd2ppc[pd_id] = ppc_id
@@ -438,24 +424,7 @@ def load_grid(self,
438424
else:
439425
self.slack_id = (self._grid.gen["slack"].values).nonzero()[0]
440426

441-
with warnings.catch_warnings():
442-
warnings.filterwarnings("ignore")
443-
try:
444-
pp.runpp(
445-
self._grid,
446-
numba=self.with_numba,
447-
lightsim2grid=self._lightsim2grid,
448-
distributed_slack=self._dist_slack,
449-
max_iteration=self._max_iter,
450-
)
451-
except pp.powerflow.LoadflowNotConverged:
452-
pp.rundcpp(
453-
self._grid,
454-
numba=self.with_numba,
455-
lightsim2grid=self._lightsim2grid,
456-
distributed_slack=self._dist_slack,
457-
max_iteration=self._max_iter,
458-
)
427+
self._aux_run_pf_init() # run another powerflow with the added generator
459428

460429
self.__nb_bus_before = self._grid.bus.shape[0]
461430
self.__nb_powerline = self._grid.line.shape[0]
@@ -567,12 +536,25 @@ def load_grid(self,
567536
for ind, el in add_topo.iterrows():
568537
pp.create_bus(self._grid, index=ind, **el)
569538
self._init_private_attrs()
539+
self._aux_run_pf_init() # run yet another powerflow with the added buses
570540

571541
# do this at the end
572542
self._in_service_line_col_id = int((self._grid.line.columns == "in_service").nonzero()[0][0])
573543
self._in_service_trafo_col_id = int((self._grid.trafo.columns == "in_service").nonzero()[0][0])
574544
self._in_service_storage_cold_id = int((self._grid.storage.columns == "in_service").nonzero()[0][0])
575-
545+
self.comp_time = 0.
546+
547+
def _aux_run_pf_init(self):
548+
"""run a powerflow when the file is being loaded. This is called three times for each call to "load_grid" """
549+
with warnings.catch_warnings():
550+
warnings.filterwarnings("ignore")
551+
try:
552+
self._aux_runpf_pp(False)
553+
if not self._grid.converged:
554+
raise pp.powerflow.LoadflowNotConverged
555+
except pp.powerflow.LoadflowNotConverged:
556+
self._aux_runpf_pp(True)
557+
576558
def _init_private_attrs(self) -> None:
577559
# number of elements per substation
578560
self.sub_info = np.zeros(self.n_sub, dtype=dt_int)
@@ -691,23 +673,23 @@ def _init_private_attrs(self) -> None:
691673
"prod_v"
692674
] = self._load_grid_gen_vm_pu # lambda grid: grid.gen["vm_pu"]
693675

694-
self.load_pu_to_kv = self._grid.bus["vn_kv"][self.load_to_subid].values.astype(
676+
self.load_pu_to_kv = 1. * self._grid.bus["vn_kv"][self.load_to_subid].values.astype(
695677
dt_float
696678
)
697-
self.prod_pu_to_kv = self._grid.bus["vn_kv"][self.gen_to_subid].values.astype(
679+
self.prod_pu_to_kv = 1. * self._grid.bus["vn_kv"][self.gen_to_subid].values.astype(
698680
dt_float
699681
)
700-
self.lines_or_pu_to_kv = self._grid.bus["vn_kv"][
682+
self.lines_or_pu_to_kv = 1. * self._grid.bus["vn_kv"][
701683
self.line_or_to_subid
702684
].values.astype(dt_float)
703-
self.lines_ex_pu_to_kv = self._grid.bus["vn_kv"][
685+
self.lines_ex_pu_to_kv = 1. * self._grid.bus["vn_kv"][
704686
self.line_ex_to_subid
705687
].values.astype(dt_float)
706-
self.storage_pu_to_kv = self._grid.bus["vn_kv"][
688+
self.storage_pu_to_kv = 1. * self._grid.bus["vn_kv"][
707689
self.storage_to_subid
708690
].values.astype(dt_float)
709691

710-
self.thermal_limit_a = 1000 * np.concatenate(
692+
self.thermal_limit_a = 1000. * np.concatenate(
711693
(
712694
self._grid.line["max_i_ka"].values,
713695
self._grid.trafo["sn_mva"].values
@@ -827,7 +809,7 @@ def apply_action(self, backendAction: Union["grid2op.Action._backendAction._Back
827809
"""
828810
if backendAction is None:
829811
return
830-
812+
831813
cls = type(self)
832814

833815
(
@@ -1012,13 +994,14 @@ def _aux_runpf_pp(self, is_dc: bool):
1012994
)
1013995
warnings.filterwarnings("ignore", category=RuntimeWarning)
1014996
warnings.filterwarnings("ignore", category=DeprecationWarning)
1015-
nb_bus = self.get_nb_active_bus()
1016-
if self._nb_bus_before is None:
1017-
self._pf_init = "dc"
1018-
elif nb_bus == self._nb_bus_before:
1019-
self._pf_init = "results"
1020-
else:
1021-
self._pf_init = "auto"
997+
self._pf_init = "dc"
998+
# nb_bus = self.get_nb_active_bus()
999+
# if self._nb_bus_before is None:
1000+
# self._pf_init = "dc"
1001+
# elif nb_bus == self._nb_bus_before:
1002+
# self._pf_init = "results"
1003+
# else:
1004+
# self._pf_init = "auto"
10221005

10231006
if (~self._grid.load["in_service"]).any():
10241007
# TODO see if there is a better way here -> do not handle this here, but rather in Backend._next_grid_state
@@ -1081,12 +1064,13 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
10811064
"""
10821065
try:
10831066
self._aux_runpf_pp(is_dc)
1084-
1085-
cls = type(self)
1067+
cls = type(self)
10861068
# if a connected bus has a no voltage, it's a divergence (grid was not connected)
10871069
if self._grid.res_bus.loc[self._grid.bus["in_service"]]["va_degree"].isnull().any():
1088-
raise pp.powerflow.LoadflowNotConverged("Isolated bus")
1089-
1070+
buses_ko = self._grid.res_bus.loc[self._grid.bus["in_service"]]["va_degree"].isnull()
1071+
buses_ko = buses_ko.values.nonzero()[0]
1072+
raise pp.powerflow.LoadflowNotConverged(f"Isolated bus, check buses {buses_ko} with `env.backend._grid.res_bus.iloc[{buses_ko}, :]`")
1073+
10901074
(
10911075
self.prod_p[:],
10921076
self.prod_q[:],
@@ -1104,7 +1088,7 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
11041088
if not np.isfinite(self.load_v).all():
11051089
# TODO see if there is a better way here
11061090
# some loads are disconnected: it's a game over case!
1107-
raise pp.powerflow.LoadflowNotConverged("Isolated load")
1091+
raise pp.powerflow.LoadflowNotConverged(f"Isolated load: check loads {np.isfinite(self.load_v).nonzero()[0]}")
11081092
else:
11091093
# fix voltages magnitude that are always "nan" for dc case
11101094
# self._grid.res_bus["vm_pu"] is always nan when computed in DC
@@ -1130,7 +1114,7 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
11301114
self.p_or[:] = self._aux_get_line_info("p_from_mw", "p_hv_mw")
11311115
self.q_or[:] = self._aux_get_line_info("q_from_mvar", "q_hv_mvar")
11321116
self.v_or[:] = self._aux_get_line_info("vm_from_pu", "vm_hv_pu")
1133-
self.a_or[:] = self._aux_get_line_info("i_from_ka", "i_hv_ka") * 1000
1117+
self.a_or[:] = self._aux_get_line_info("i_from_ka", "i_hv_ka") * 1000.
11341118
self.theta_or[:] = self._aux_get_line_info(
11351119
"va_from_degree", "va_hv_degree"
11361120
)
@@ -1140,7 +1124,7 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
11401124
self.p_ex[:] = self._aux_get_line_info("p_to_mw", "p_lv_mw")
11411125
self.q_ex[:] = self._aux_get_line_info("q_to_mvar", "q_lv_mvar")
11421126
self.v_ex[:] = self._aux_get_line_info("vm_to_pu", "vm_lv_pu")
1143-
self.a_ex[:] = self._aux_get_line_info("i_to_ka", "i_lv_ka") * 1000
1127+
self.a_ex[:] = self._aux_get_line_info("i_to_ka", "i_lv_ka") * 1000.
11441128
self.theta_ex[:] = self._aux_get_line_info(
11451129
"va_to_degree", "va_lv_degree"
11461130
)
@@ -1158,7 +1142,9 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
11581142
self.theta_ex[~np.isfinite(self.theta_ex)] = 0.0
11591143

11601144
self._nb_bus_before = None
1161-
self._grid._ppc["gen"][self._iref_slack, 1] = 0.0
1145+
if self._iref_slack is not None:
1146+
# a gen has been added to represent the slack, modeled as an "ext_grid"
1147+
self._grid._ppc["gen"][self._iref_slack, 1] = 0.0
11621148

11631149
# handle storage units
11641150
# note that we have to look ourselves for disconnected storage
@@ -1179,13 +1165,17 @@ def runpf(self, is_dc : bool=False) -> Tuple[bool, Union[Exception, None]]:
11791165
self._grid.storage["in_service"].values[deact_storage] = False
11801166

11811167
self._topo_vect[:] = self._get_topo_vect()
1182-
return self._grid.converged, None
1168+
if not self._grid.converged:
1169+
raise pp.powerflow.LoadflowNotConverged("Divergence without specific reason (self._grid.converged is False)")
1170+
self.div_exception = None
1171+
return True, None
11831172

11841173
except pp.powerflow.LoadflowNotConverged as exc_:
11851174
# of the powerflow has not converged, results are Nan
1175+
self.div_exception = exc_
11861176
self._reset_all_nan()
11871177
msg = exc_.__str__()
1188-
return False, BackendError(f'powerflow diverged with error :"{msg}"')
1178+
return False, BackendError(f'powerflow diverged with error :"{msg}", you can check `env.backend.div_exception` for more information')
11891179

11901180
def _reset_all_nan(self) -> None:
11911181
self.p_or[:] = np.NaN
@@ -1221,7 +1211,6 @@ def copy(self) -> "PandaPowerBackend":
12211211
12221212
This should return a deep copy of the Backend itself and not just the `self._grid`
12231213
"""
1224-
# res = copy.deepcopy(self) # this was really slow...
12251214
res = type(self)(**self._my_kwargs)
12261215

12271216
# copy from base class (backend)
@@ -1298,11 +1287,10 @@ def copy(self) -> "PandaPowerBackend":
12981287
with warnings.catch_warnings():
12991288
warnings.simplefilter("ignore", FutureWarning)
13001289
res.__pp_backend_initial_grid = copy.deepcopy(self.__pp_backend_initial_grid)
1301-
1302-
res.tol = (
1303-
self.tol
1304-
) # this is NOT the pandapower tolerance !!!! this is used to check if a storage unit
1290+
1291+
# this is NOT the pandapower tolerance !!!! this is used to check if a storage unit
13051292
# produce / absorbs anything
1293+
res.tol = self.tol
13061294

13071295
# TODO storage doc (in grid2op rst) of the backend
13081296
res.can_output_theta = self.can_output_theta # I support the voltage angle
@@ -1316,6 +1304,7 @@ def copy(self) -> "PandaPowerBackend":
13161304
res._in_service_trafo_col_id = self._in_service_trafo_col_id
13171305

13181306
res._missing_two_busbars_support_info = self._missing_two_busbars_support_info
1307+
res.div_exception = self.div_exception
13191308
return res
13201309

13211310
def close(self) -> None:

grid2op/Environment/environment.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,14 @@ def _init_backend(
444444
self._reset_redispatching()
445445
self._reward_to_obs = {}
446446
do_nothing = self._helper_action_env({})
447+
448+
# see issue https://github.com/rte-france/Grid2Op/issues/617
449+
# thermal limits are set AFTER this initial step
450+
_no_overflow_disconnection = self._no_overflow_disconnection
451+
self._no_overflow_disconnection = True
447452
*_, fail_to_start, info = self.step(do_nothing)
453+
self._no_overflow_disconnection = _no_overflow_disconnection
454+
448455
if fail_to_start:
449456
raise Grid2OpException(
450457
"Impossible to initialize the powergrid, the powerflow diverge at iteration 0. "

0 commit comments

Comments
 (0)