@@ -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 :
0 commit comments