3535 DivergingPowerflow ,
3636 Grid2OpException ,
3737)
38+ import grid2op .Environment # for type hints
3839from grid2op .Space import GridObjects , ElTypeInfo , DEFAULT_N_BUSBAR_PER_SUB , DEFAULT_ALLOW_DETACHMENT
3940import grid2op .Observation # for type hints
4041import grid2op .Action # for type hints
@@ -193,6 +194,7 @@ def __init__(self,
193194 self ._load_bus_target = None
194195 self ._gen_bus_target = None
195196 self ._storage_bus_target = None
197+ self ._shunt_bus_target = None
196198
197199 #: .. versionadded: 1.11.0
198200 # will be used later on in future grid2op version
@@ -259,7 +261,6 @@ def cannot_handle_more_than_2_busbar(self):
259261 "upgrade it to a newer version." )
260262 self .n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB
261263
262-
263264 def can_handle_detachment (self ):
264265 """
265266 .. versionadded:: 1.11.0
@@ -278,7 +279,8 @@ def can_handle_detachment(self):
278279 :func:`Backend.cannot_handle_detachment`.
279280
280281 If not, then the environments created with your backend will not be able to
281- "operate" grid with load and generator detachment.
282+ "operate" the grid with load and generator detached (episode will be terminated
283+ if this happens).
282284
283285 .. danger::
284286 We highly recommend you do not try to override this function.
@@ -390,6 +392,8 @@ def load_grid_public(self,
390392 self ._load_bus_target = np .empty (self .n_load , dtype = dt_int )
391393 self ._gen_bus_target = np .empty (self .n_gen , dtype = dt_int )
392394 self ._storage_bus_target = np .empty (self .n_storage , dtype = dt_int )
395+ if self .shunts_data_available :
396+ self ._shunt_bus_target = np .empty (self .n_shunt , dtype = dt_int )
393397
394398 if self ._missing_detachment_support_info :
395399 self .detachment_is_allowed = DEFAULT_ALLOW_DETACHMENT
@@ -474,10 +478,16 @@ def apply_action_public(self, backend_action: Union["grid2op.Action._backendActi
474478 self ._storage_bus_target .flags .writeable = True
475479 self ._storage_bus_target [stos_bus .changed ] = stos_bus .values [stos_bus .changed ]
476480 self ._storage_bus_target .flags .writeable = False
477- # TODO shunts
481+
482+ if type (self ).shunts_data_available :
483+ shunts_bus = backend_action .shunt_bus
484+ self ._shunt_bus_target .flags .writeable = True
485+ self ._shunt_bus_target [shunts_bus .changed ] = shunts_bus .values [shunts_bus .changed ]
486+ self ._shunt_bus_target .flags .writeable = False
487+
478488 return self .apply_action (backend_action )
479489
480- def update_bus_target_after_pf (self , loads_bus , gens_bus , stos_bus ):
490+ def update_bus_target_after_pf (self , loads_bus , gens_bus , stos_bus , shunt_bus = None ):
481491 self ._load_bus_target .flags .writeable = True
482492 self ._load_bus_target [:] = loads_bus
483493 self ._load_bus_target .flags .writeable = False
@@ -487,6 +497,10 @@ def update_bus_target_after_pf(self, loads_bus, gens_bus, stos_bus):
487497 self ._storage_bus_target .flags .writeable = True
488498 self ._storage_bus_target [:] = stos_bus
489499 self ._storage_bus_target .flags .writeable = False
500+ if type (self ).shunts_data_available and shunt_bus is not None :
501+ self ._shunt_bus_target .flags .writeable = True
502+ self ._shunt_bus_target [:] = shunt_bus
503+ self ._shunt_bus_target .flags .writeable = False
490504
491505 def handle_grid2op_compat (self ):
492506 """This function will resize the _load_bus_target, _gen_bus_target and _storage_bus_target
@@ -500,8 +514,13 @@ def handle_grid2op_compat(self):
500514 self ._gen_bus_target .resize (cls .n_gen )
501515 self ._gen_bus_target .flags .writeable = False
502516 self ._storage_bus_target .flags .writeable = True
503- self ._storage_bus_target .resize (cls .n_storage )
517+ self ._storage_bus_target .resize (cls .n_storage , refcheck = False )
504518 self ._storage_bus_target .flags .writeable = False
519+ if cls .shunts_data_available :
520+ self ._shunt_bus_target .flags .writeable = True
521+ self ._shunt_bus_target .resize (cls .n_shunt )
522+ self ._shunt_bus_target .flags .writeable = False
523+
505524
506525 @abstractmethod
507526 def apply_action (self , backend_action : "grid2op.Action._backendAction._BackendAction" ) -> None :
@@ -847,6 +866,7 @@ def copy_public(self) -> Self:
847866 res ._load_bus_target = copy .deepcopy (self ._load_bus_target )
848867 res ._gen_bus_target = copy .deepcopy (self ._gen_bus_target )
849868 res ._storage_bus_target = copy .deepcopy (self ._storage_bus_target )
869+ res ._shunt_bus_target = copy .deepcopy (self ._shunt_bus_target )
850870 res ._prevent_automatic_disconnection = copy .deepcopy (self ._prevent_automatic_disconnection )
851871 return res
852872
@@ -1318,46 +1338,87 @@ def _runpf_with_diverging_exception(self, is_dc : bool) -> Optional[Exception]:
13181338 # .. versionadded:: 1.11.0
13191339 topo_vect = self .get_topo_vect ()
13201340 load_buses = topo_vect [cls .load_pos_topo_vect ]
1321- if not cls .detachment_is_allowed and (load_buses == - 1 ).any ():
1322- raise BackendError (cls .ERR_DETACHMENT .format ("loads" , "loads" , (load_buses == - 1 ).nonzero ()[0 ]))
1341+ load_disco = (load_buses == - 1 )
1342+ if not cls .detachment_is_allowed and load_disco .any ():
1343+ raise BackendError (cls .ERR_DETACHMENT .format ("loads" , "loads" , load_disco .nonzero ()[0 ]))
13231344
13241345 gen_buses = topo_vect [cls .gen_pos_topo_vect ]
1325- if not cls .detachment_is_allowed and (gen_buses == - 1 ).any ():
1326- raise BackendError (cls .ERR_DETACHMENT .format ("gens" , "gens" , (gen_buses == - 1 ).nonzero ()[0 ]))
1346+ gen_disco = (gen_buses == - 1 )
1347+ if not cls .detachment_is_allowed and gen_disco .any ():
1348+ raise BackendError (cls .ERR_DETACHMENT .format ("gens" , "gens" , gen_disco .nonzero ()[0 ]))
13271349
13281350 if cls .n_storage > 0 :
13291351 storage_buses = topo_vect [cls .storage_pos_topo_vect ]
13301352 storage_p , * _ = self .storages_info ()
1331- sto_maybe_error = (storage_buses == - 1 ) & (np .abs (storage_p ) >= 1e-6 )
1353+ storage_p_withpower = np .abs (storage_p ) >= 1e-6
1354+ sto_maybe_error = (storage_buses == - 1 ) & storage_p_withpower
13321355 if not cls .detachment_is_allowed and sto_maybe_error .any ():
13331356 raise BackendError ((cls .ERR_DETACHMENT .format ("storages" , "storages" , sto_maybe_error .nonzero ()[0 ]) +
13341357 " NB storage units are allowed to be disconnected even if "
1335- "`detachment_is_allowed` is False but only if the don't produce active power." ))
1336-
1358+ "`detachment_is_allowed` is False but only if the don't produce / absorb active power." ))
1359+ else :
1360+ sto_maybe_error = None
13371361 # additional check: if the backend detach some things incorrectly
13381362 if cls .detachment_is_allowed :
13391363 # if the backend automatically disconnect things, I need to catch them
13401364 # with grid2op 1.11.0 it is not feasible
1341- if self ._prevent_automatic_disconnection and ((self ._load_bus_target != - 1 ) & (load_buses == - 1 )).any ():
1342- issue = (self ._load_bus_target != - 1 ) & (load_buses == - 1 )
1343- raise BackendError (f"Your backend apparently disconnected load(s) id { issue .nonzero ()[0 ]} , "
1344- f"named { type (self ).name_load [issue .nonzero ()[0 ]]} " )
1345- if self ._prevent_automatic_disconnection and ((self ._gen_bus_target != - 1 ) & (gen_buses == - 1 )).any ():
1346- issue = (self ._gen_bus_target != - 1 ) & (gen_buses == - 1 )
1347- raise BackendError (f"Your backend apparently disconnected gens(s) id { issue .nonzero ()[0 ]} , "
1348- f"named { type (self ).name_gen [issue .nonzero ()[0 ]]} " )
1349- # TODO storage units
1365+ self ._catch_automatic_disconnection (load_disco , gen_disco , sto_maybe_error )
13501366
13511367 except Grid2OpException as exc_ :
13521368 exc_me = exc_
13531369
13541370 if not conv and exc_me is None :
13551371 exc_me = BackendError (
1356- "GAME OVER: Powerflow has diverged during computation "
1357- "or a load has been disconnected or a generator has been disconnected."
1372+ f"GAME OVER: { exc_me } "
13581373 )
13591374 return exc_me
13601375
1376+ def _catch_automatic_disconnection (self ,
1377+ load_disco : np .ndarray ,
1378+ gen_disco : np .ndarray ,
1379+ sto_maybe_error : Optional [np .ndarray ]):
1380+ """
1381+ INTERNAL
1382+
1383+ .. warning:: /!\\ \\ Internal, do not use unless you know what you are doing /!\\ \\
1384+
1385+ This function "automatically" detects if elements have been disconnected (bus -1 in the results table but bus > 0 in
1386+ the target table). If that is the case, it is expected that (provided that
1387+ :attr:`Backend._prevent_automatic_disconnection` is ``True`` - default) the backend raises a BackendError exception.
1388+
1389+ Args:
1390+ load_disco (np.ndarray): _description_
1391+ gen_disco (np.ndarray): _description_
1392+ sto_maybe_error (Optional[np.ndarray]): _description_
1393+
1394+ """
1395+ if not self ._prevent_automatic_disconnection :
1396+ # in this case, the backend is allowed to disconnect some things
1397+ return
1398+
1399+ cls = type (self )
1400+ if ((self ._load_bus_target != - 1 ) & load_disco ).any ():
1401+ issue = (self ._load_bus_target != - 1 ) & load_disco
1402+ raise BackendError (f"Your backend apparently disconnected load(s) id { issue .nonzero ()[0 ]} , "
1403+ f"named { cls .name_load [issue .nonzero ()[0 ]]} " )
1404+ if ((self ._gen_bus_target != - 1 ) & gen_disco ).any ():
1405+ issue = (self ._gen_bus_target != - 1 ) & gen_disco
1406+ raise BackendError (f"Your backend apparently disconnected gens(s) id { issue .nonzero ()[0 ]} , "
1407+ f"named { cls .name_gen [issue .nonzero ()[0 ]]} " )
1408+
1409+ if cls .shunts_data_available :
1410+ * _ , shunt_buses = self .shunt_info ()
1411+ if ((self ._shunt_bus_target != - 1 ) & (shunt_buses == - 1 )).any ():
1412+ issue = (self ._shunt_bus_target != - 1 ) & (shunt_buses == - 1 )
1413+ raise BackendError (f"Your backend apparently disconnected shunt(s) id { issue .nonzero ()[0 ]} , "
1414+ f"named { cls .name_shunt [issue .nonzero ()[0 ]]} " )
1415+
1416+ if cls .n_storage > 0 :
1417+ if ((self ._storage_bus_target != - 1 ) & sto_maybe_error ).any ():
1418+ issue = (self ._storage_bus_target != - 1 ) & (sto_maybe_error )
1419+ raise BackendError (f"Your backend apparently disconnected stprage unit(s) id { issue .nonzero ()[0 ]} , "
1420+ f"named { cls .name_storage [issue .nonzero ()[0 ]]} " )
1421+
13611422 def next_grid_state (self ,
13621423 env : "grid2op.Environment.BaseEnv" ,
13631424 is_dc : Optional [bool ]= False ):
@@ -1392,14 +1453,15 @@ def next_grid_state(self,
13921453
13931454 """
13941455 infos = []
1395- disconnected_during_cf = np .full (self .n_line , fill_value = - 1 , dtype = dt_int )
1456+ disconnected_during_cf = np .full (type ( self ) .n_line , fill_value = - 1 , dtype = dt_int )
13961457 conv_ = self ._runpf_with_diverging_exception (is_dc )
13971458 if env ._no_overflow_disconnection or conv_ is not None :
13981459 return disconnected_during_cf , infos , conv_
13991460
14001461 # the environment disconnect some powerlines
14011462 init_time_step_overflow = copy .deepcopy (env ._timestep_overflow )
1402- ts = 0
1463+ counter_increased = np .zeros_like (init_time_step_overflow , dtype = dt_bool )
1464+ iter_num = 0
14031465 while True :
14041466 # simulate the cascading failure
14051467 lines_flows = 1.0 * self .get_line_flow ()
@@ -1412,7 +1474,10 @@ def next_grid_state(self,
14121474 ) & lines_status
14131475
14141476 # b) deals with soft overflow (disconnect them if lines still connected)
1415- init_time_step_overflow [(lines_flows >= thermal_limits ) & lines_status ] += 1
1477+ mask_inc = (lines_flows >= thermal_limits ) & lines_status
1478+ mask_inc [counter_increased ] = False
1479+ init_time_step_overflow [mask_inc ] += 1
1480+ counter_increased [mask_inc ] = True
14161481 to_disc [
14171482 (init_time_step_overflow > env ._nb_timestep_overflow_allowed )
14181483 & lines_status
@@ -1423,7 +1488,7 @@ def next_grid_state(self,
14231488 # no powerlines have been disconnected at this time step,
14241489 # i stop the computation there
14251490 break
1426- disconnected_during_cf [to_disc ] = ts
1491+ disconnected_during_cf [to_disc ] = iter_num
14271492
14281493 # perform the disconnection action
14291494 for i , el in enumerate (to_disc ):
@@ -1437,7 +1502,7 @@ def next_grid_state(self,
14371502
14381503 if conv_ is not None :
14391504 break
1440- ts += 1
1505+ iter_num += 1
14411506 return disconnected_during_cf , infos , conv_
14421507
14431508 def storages_info (self ) -> Tuple [np .ndarray , np .ndarray , np .ndarray ]:
0 commit comments