2222 # python version is probably bellow 3.11
2323 from typing_extensions import Self
2424
25+ import grid2op
2526from grid2op .dtypes import dt_int , dt_float , dt_bool
2627from grid2op .Exceptions import (
2728 EnvError ,
@@ -66,19 +67,22 @@ class Backend(GridObjects, ABC):
6667 All the abstract methods (that need to be implemented for a backend to work properly) are (more information given
6768 in the :ref:`create-backend-module` page):
6869
69- - :func:`Backend.load_grid`
70- - :func:`Backend.apply_action`
71- - :func:`Backend.runpf`
72- - :func:`Backend.get_topo_vect`
73- - :func:`Backend.generators_info`
74- - :func:`Backend.loads_info`
75- - :func:`Backend.lines_or_info`
76- - :func:`Backend.lines_ex_info`
70+ - :func:`Backend.load_grid` (called once per episode, or if :func:`Backend.reset` is implemented, once for the entire
71+ lifetime of the environment)
72+ - :func:`Backend.apply_action` (called once per episode -initialization- and at least once per step)
73+ - :func:`Backend.runpf` (called once per episode -initialization- and at least once per step)
74+ - :func:`Backend.get_topo_vect` (called once per episode -initialization- and at least once per step)
75+ - :func:`Backend.generators_info` (called once per episode -initialization- and at least once per step)
76+ - :func:`Backend.loads_info` (called once per episode -initialization- and at least once per step)
77+ - :func:`Backend.lines_or_info` (called once per episode -initialization- and at least once per step)
78+ - :func:`Backend.lines_ex_info` (called once per episode -initialization- and at least once per step)
7779
7880 And optionally:
7981
82+ - :func:`Backend.reset` will reload the powergrid from the hard drive by default. This is rather slow and we
83+ recommend to overload it.
8084 - :func:`Backend.close` (this is mandatory if your backend implementation (`self._grid`) is relying on some
81- c / c++ code that do not free memory automatically.
85+ c / c++ code that do not free memory automatically.)
8286 - :func:`Backend.copy` (not that this is mandatory if your backend implementation (in `self._grid`) cannot be
8387 deep copied using the python copy.deepcopy function) [as of grid2op >= 1.7.1 it is no more
8488 required. If not implemented, you won't be able to use some of grid2op feature however]
@@ -88,8 +92,6 @@ class Backend(GridObjects, ABC):
8892 at the "origin" side and just return the "a_or" vector. You want to do something smarter here.
8993 - :func:`Backend._disconnect_line`: has a default slow implementation using "apply_action" that might
9094 can most likely be optimized in your backend.
91- - :func:`Backend.reset` will reload the powergrid from the hard drive by default. This is rather slow and we
92- recommend to overload it.
9395
9496 And, if the flag :attr:Backend.shunts_data_available` is set to ``True`` the method :func:`Backend.shunt_info`
9597 should also be implemented.
@@ -99,12 +101,6 @@ class Backend(GridObjects, ABC):
99101 `shunt_to_subid`, `name_shunt` and function `shunt_info` and handle the modification of shunts
100102 bus, active value and reactive value in the "apply_action" function).
101103
102-
103- In order to be valid and carry out some computations, you should call :func:`Backend.load_grid` and later
104- :func:`grid2op.Spaces.GridObjects.assert_grid_correct`. It is also more than recommended to call
105- :func:`Backend.assert_grid_correct_after_powerflow` after the first powerflow. This is all carried ou in the
106- environment properly.
107-
108104 Attributes
109105 ----------
110106 detailed_infos_for_cascading_failures: :class:`bool`
@@ -119,9 +115,7 @@ class Backend(GridObjects, ABC):
119115
120116 """
121117 IS_BK_CONVERTER : bool = False
122-
123- env_name : str = "unknown"
124-
118+
125119 # action to set me
126120 my_bk_act_class : "Optional[grid2op.Action._backendAction._BackendAction]" = None
127121 _complete_action_class : "Optional[grid2op.Action.CompleteAction]" = None
@@ -224,7 +218,7 @@ def cannot_handle_more_than_2_busbar(self):
224218 If not called, then the `environment` will not be able to use more than 2 busbars per substations.
225219
226220 .. seealso::
227- :func:`Backend.cnot_handle_more_than_2_busbar `
221+ :func:`Backend.cannot_handle_more_than_2_busbar `
228222
229223 .. note::
230224 From grid2op 1.10.0 it is preferable that your backend calls one of
@@ -1418,6 +1412,119 @@ def check_kirchoff(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray
14181412 diff_v_bus [:, :] = v_bus [:, :, 1 ] - v_bus [:, :, 0 ]
14191413 return p_subs , q_subs , p_bus , q_bus , diff_v_bus
14201414
1415+ def _fill_names_obj (self ):
1416+ """fill the name vectors (**eg** name_line) if not done already in the backend.
1417+ This function is used to fill the name of an object of a class. It will also check the existence
1418+ of these vectors in the class.
1419+ """
1420+ cls = type (self )
1421+ if self .name_line is None :
1422+ if cls .name_line is None :
1423+ line_or_to_subid = cls .line_or_to_subid if cls .line_or_to_subid is not None else self .line_or_to_subid
1424+ line_ex_to_subid = cls .line_ex_to_subid if cls .line_ex_to_subid is not None else self .line_ex_to_subid
1425+ self .name_line = [
1426+ "{}_{}_{}" .format (or_id , ex_id , l_id )
1427+ for l_id , (or_id , ex_id ) in enumerate (
1428+ zip (line_or_to_subid , line_ex_to_subid )
1429+ )
1430+ ]
1431+ self .name_line = np .array (self .name_line )
1432+ warnings .warn (
1433+ "name_line is None so default line names have been assigned to your grid. "
1434+ "(FYI: Line names are used to make the correspondence between the chronics and the backend)"
1435+ "This might result in impossibility to load data."
1436+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1437+ )
1438+ else :
1439+ self .name_line = cls .name_line
1440+
1441+ if self .name_load is None :
1442+ if cls .name_load is None :
1443+ load_to_subid = cls .load_to_subid if cls .load_to_subid is not None else self .load_to_subid
1444+ self .name_load = [
1445+ "load_{}_{}" .format (bus_id , load_id )
1446+ for load_id , bus_id in enumerate (load_to_subid )
1447+ ]
1448+ self .name_load = np .array (self .name_load )
1449+ warnings .warn (
1450+ "name_load is None so default load names have been assigned to your grid. "
1451+ "(FYI: load names are used to make the correspondence between the chronics and the backend)"
1452+ "This might result in impossibility to load data."
1453+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1454+ )
1455+ else :
1456+ self .name_load = cls .name_load
1457+
1458+ if self .name_gen is None :
1459+ if cls .name_gen is None :
1460+ gen_to_subid = cls .gen_to_subid if cls .gen_to_subid is not None else self .gen_to_subid
1461+ self .name_gen = [
1462+ "gen_{}_{}" .format (bus_id , gen_id )
1463+ for gen_id , bus_id in enumerate (gen_to_subid )
1464+ ]
1465+ self .name_gen = np .array (self .name_gen )
1466+ warnings .warn (
1467+ "name_gen is None so default generator names have been assigned to your grid. "
1468+ "(FYI: generator names are used to make the correspondence between the chronics and "
1469+ "the backend)"
1470+ "This might result in impossibility to load data."
1471+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1472+ )
1473+ else :
1474+ self .name_gen = cls .name_gen
1475+
1476+ if self .name_sub is None :
1477+ if cls .name_sub is None :
1478+ n_sub = cls .n_sub if cls .n_sub is not None and cls .n_sub > 0 else self .n_sub
1479+ self .name_sub = ["sub_{}" .format (sub_id ) for sub_id in range (n_sub )]
1480+ self .name_sub = np .array (self .name_sub )
1481+ warnings .warn (
1482+ "name_sub is None so default substation names have been assigned to your grid. "
1483+ "(FYI: substation names are used to make the correspondence between the chronics and "
1484+ "the backend)"
1485+ "This might result in impossibility to load data."
1486+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1487+ )
1488+ else :
1489+ self .name_sub = cls .name_sub
1490+
1491+ if self .name_storage is None :
1492+ if cls .name_storage is None :
1493+ storage_to_subid = cls .storage_to_subid if cls .storage_to_subid is not None else self .storage_to_subid
1494+ self .name_storage = [
1495+ "storage_{}_{}" .format (bus_id , sto_id )
1496+ for sto_id , bus_id in enumerate (storage_to_subid )
1497+ ]
1498+ self .name_storage = np .array (self .name_storage )
1499+ warnings .warn (
1500+ "name_storage is None so default storage unit names have been assigned to your grid. "
1501+ "(FYI: storage names are used to make the correspondence between the chronics and "
1502+ "the backend)"
1503+ "This might result in impossibility to load data."
1504+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1505+ )
1506+ else :
1507+ self .name_storage = cls .name_storage
1508+
1509+ if cls .shunts_data_available :
1510+ if self .name_shunt is None :
1511+ if cls .name_shunt is None :
1512+ shunt_to_subid = cls .shunt_to_subid if cls .shunt_to_subid is not None else self .shunt_to_subid
1513+ self .name_shunt = [
1514+ "shunt_{}_{}" .format (bus_id , sh_id )
1515+ for sh_id , bus_id in enumerate (shunt_to_subid )
1516+ ]
1517+ self .name_shunt = np .array (self .name_shunt )
1518+ warnings .warn (
1519+ "name_shunt is None so default storage unit names have been assigned to your grid. "
1520+ "(FYI: storage names are used to make the correspondence between the chronics and "
1521+ "the backend)"
1522+ "This might result in impossibility to load data."
1523+ '\n \t If "env.make" properly worked, you can safely ignore this warning.'
1524+ )
1525+ else :
1526+ self .name_shunt = cls .name_shunt
1527+
14211528 def load_redispacthing_data (self ,
14221529 path : Union [os .PathLike , str ],
14231530 name : Optional [str ]= "prods_charac.csv" ) -> None :
@@ -1430,6 +1537,13 @@ def load_redispacthing_data(self,
14301537
14311538 We don't recommend at all to modify this function.
14321539
1540+ Notes
1541+ -----
1542+ Before you use this function, make sure the names of the generators are properly set.
1543+
1544+ For example you can either read them from the grid (setting self.name_gen) or call
1545+ self._fill_names_obj() beforehand (this later is done in the environment.)
1546+
14331547 Parameters
14341548 ----------
14351549 path: ``str``
@@ -1458,7 +1572,6 @@ def load_redispacthing_data(self,
14581572 to change it.
14591573
14601574 """
1461- self ._fill_names ()
14621575 self .redispatching_unit_commitment_availble = False
14631576
14641577 # for redispatching
@@ -1574,6 +1687,13 @@ def load_storage_data(self,
15741687 This method will load everything needed in presence of storage unit on the grid.
15751688
15761689 We don't recommend at all to modify this function.
1690+
1691+ Notes
1692+ -----
1693+ Before you use this function, make sure the names of the generators are properly set.
1694+
1695+ For example you can either read them from the grid (setting self.name_gen) or call
1696+ self._fill_names_obj() beforehand (this later is done in the environment.)
15771697
15781698 Parameters
15791699 ----------
@@ -1623,7 +1743,7 @@ def load_storage_data(self,
16231743 fullpath = os .path .join (path , name )
16241744 if not os .path .exists (fullpath ):
16251745 raise BackendError (
1626- f"There are storage unit on the grid, yet we could not locate their description."
1746+ f"There are { self . n_storage } storage unit(s) on the grid, yet we could not locate their description."
16271747 f'Please make sure to have a file "{ name } " where the environment data are located.'
16281748 f'For this environment the location is "{ path } "'
16291749 )
@@ -1983,19 +2103,44 @@ def assert_grid_correct(self) -> None:
19832103 self .__class__ .__name__ ,
19842104 self .__class__ ,
19852105 )
2106+
19862107 # reset the attribute of the grid2op.Backend.Backend class
19872108 # that can be messed up with depending on the initialization of the backend
19882109 Backend ._clear_class_attribute () # reset totally the grid2op Backend type
1989- # orig_type._clear_class_attribute()
1990- orig_type ._clear_grid_dependant_class_attributes () # only reset the attributes that could be modified by user
1991-
2110+
2111+ # only reset the attributes that could be modified by the environment while keeping the
2112+ # attribute that can be defined in the Backend implementation (eg support of shunt)
2113+ orig_type ._clear_grid_dependant_class_attributes ()
2114+
19922115 my_cls = type (self )
19932116 my_cls .my_bk_act_class = _BackendAction .init_grid (my_cls )
19942117 my_cls ._complete_action_class = CompleteAction .init_grid (my_cls )
19952118 my_cls ._complete_action_class ._add_shunt_data ()
19962119 my_cls ._complete_action_class ._update_value_set ()
19972120 my_cls .assert_grid_correct_cls ()
2121+ self ._remove_my_attr_cls ()
19982122
2123+ def _remove_my_attr_cls (self ):
2124+ """
2125+ INTERNAL
2126+
2127+ .. warning:: /!\\ \\ Internal, do not use unless you know what you are doing /!\\ \\
2128+
2129+ This function is called at the end of :func:`Backend.assert_grid_correct` and it "cleans" the attribute of the
2130+ backend object that are stored in the class now, to avoid discrepency between what has been read from the
2131+ grid and what have been processed by grid2op (for example in "compatibility" mode, storage are deactivated, so
2132+ `self.n_storage` would be different that `type(self).n_storage`)
2133+
2134+ For this to work, the grid must first be initialized correctly, with the proper type (name of the environment
2135+ in the class name !)
2136+ """
2137+ cls = type (self )
2138+ if cls ._CLS_DICT_EXTENDED is not None :
2139+ for attr_nm , val in cls ._CLS_DICT_EXTENDED .items ():
2140+ if hasattr (self , attr_nm ) and hasattr (cls , attr_nm ):
2141+ if id (getattr (self , attr_nm )) != id (getattr (cls , attr_nm )):
2142+ delattr (self , attr_nm )
2143+
19992144 def assert_grid_correct_after_powerflow (self ) -> None :
20002145 """
20012146 INTERNAL
0 commit comments