@@ -215,11 +215,29 @@ Basically the `load_grid` function would look something like:
215215
216216 # from grid2op 1.10.0 you need to call one of
217217 self .can_handle_more_than_2_busbar() # see doc for more information
218- OR
218+ # OR
219219 self .cannot_handle_more_than_2_busbar() # see doc for more information
220220 # It is important you include it at the top of this method, otherwise you
221221 # will not have access to self.n_busbar_per_sub
222222
223+ # from grid2op 1.11.0 you need to call one of
224+ self .can_handle_detachment() # see doc for more information
225+ # OR
226+ self .cannot_handle_detachment() # see doc for more information
227+ # It is important you include it at the top of this method,
228+ # if you don't, by default grid2op will suppose that your backend
229+ # cannot disconnect element
230+
231+ # from grid2op 1.11.0, "detachment" feature is implemented. This
232+ # means the agent is able to disconnect element from the grid.
233+ # But it is still "required" (post processing) that the backend
234+ # does not automatically disconnect elements.
235+ # If you backend disconnects automatically elements, you probably want
236+ # to turn off the internal checks (so that if your backend disconnect
237+ # things, the environment is not "done")
238+ # To do that, set the flags
239+ # self._prevent_automatic_disconnection = False
240+
223241 # load the grid in your favorite format, located at `full_path`:
224242 self ._grid = ... # the way you do that depends on the "solver" you use
225243
@@ -539,10 +557,8 @@ At the end, the `apply_action` function of the backend should look something lik
539557
540558.. code-block :: python
541559
542- def apply_action (self , backendAction = None ):
543- if backendAction is None :
544- return
545- active_bus, (prod_p, prod_v, load_p, load_q), _, shunts__ = backendAction()
560+ def apply_action (self , backend_action = None ):
561+ active_bus, (prod_p, prod_v, load_p, load_q, storage_p), _, shunts__ = backend_action()
546562
547563 # modify the injections [see paragraph "Modifying the injections (productions and loads)"]
548564 for gen_id, new_p in prod_p:
@@ -560,7 +576,7 @@ At the end, the `apply_action` function of the backend should look something lik
560576 ... # the way you do that depends on the `internal representation of the grid`
561577
562578 # modify the topology [see paragraph "Modifying the topology (status and busbar)"]
563- loads_bus = backendAction .get_loads_bus()
579+ loads_bus = backend_action .get_loads_bus()
564580 for load_id, new_bus in loads_bus:
565581 # modify the "busbar" of the loads
566582 if new_bus == - 1 :
@@ -570,7 +586,7 @@ At the end, the `apply_action` function of the backend should look something lik
570586 # the load is moved to either busbar 1 (in this case `new_bus` will be `1`)
571587 # or to busbar 2 (in this case `new_bus` will be `2`)
572588 ... # the way you do that depends on the `internal representation of the grid`
573- gens_bus = backendAction .get_gens_bus()
589+ gens_bus = backend_action .get_gens_bus()
574590 for gen_id, new_bus in gens_bus:
575591 # modify the "busbar" of the generators
576592 if new_bus == - 1 :
@@ -580,7 +596,7 @@ At the end, the `apply_action` function of the backend should look something lik
580596 # the gen is moved to either busbar 1 (in this case `new_bus` will be `1`)
581597 # or to busbar 2 (in this case `new_bus` will be `2`)
582598 ... # the way you do that depends on the `internal representation of the grid`
583- lines_or_bus = backendAction .get_lines_or_bus()
599+ lines_or_bus = backend_action .get_lines_or_bus()
584600 for line_id, new_bus in lines_or_bus:
585601 # modify the "busbar" of the origin side of powerline line_id
586602 if new_bus == - 1 :
@@ -590,7 +606,7 @@ At the end, the `apply_action` function of the backend should look something lik
590606 # the origin side of powerline is moved to either busbar 1 (in this case `new_bus` will be `1`)
591607 # or to busbar 2 (in this case `new_bus` will be `2`)
592608 ... # the way you do that depends on the `internal representation of the grid`
593- lines_ex_bus = backendAction .get_lines_ex_bus()
609+ lines_ex_bus = backend_action .get_lines_ex_bus()
594610 for line_id, new_bus in lines_ex_bus:
595611 # modify the "busbar" of the extremity side of powerline line_id
596612 if new_bus == - 1 :
@@ -979,8 +995,242 @@ TODO there are ways to use the `create_test_suite` but they have not been tested
979995
980996Advanced usage and speed optimization
981997--------------------------------------
998+
999+ Storage units (feature)
1000+ ++++++++++++++++++++++++
1001+
1002+ You can deactivate the support of storage units by calling `self.set_no_storage() ` in the
1003+ `load_grid() ` implementation.
1004+
1005+ If you want to support storage units, you need to implement:
1006+
1007+ - in `load_grid() `:
1008+
1009+ - `self.n_storage ` : number of storage unit on the grid
1010+ - `self.storages_to_subid ` : for each storage units, at which substation they are connected on the grid
1011+ - (optional): `self.name_storage `, `self.storage_to_sub_pos `, `self.storage_pos_topo_vect `
1012+
1013+ - in `apply_action() `:
1014+
1015+ - you receive same kind of information for which bus storages needs to be
1016+ connected to with `stos_bus = backend_action.get_storages_bus() ` (use it like
1017+ you would other element)
1018+ - you receive the information in the `storage_p ` variable about the amount of
1019+ power that the storage unit is asked to absorb / produce. This is given in MW
1020+ with load convention (positive power = power is absorbed by the storage unit)
1021+
1022+ - in the "getters" you need to implement the :func: `grid2op.Backend.Backend.storages_info() ` methods that returns
1023+ 3 vectors: `storage_p `, `storage_q `, `storage_v ` (similar to `loads_info ` or `generators_info `)
1024+
1025+ .. note ::
1026+ In order for the "storage units" to be configured correctly, for now, the default behaviour
1027+ is to have a separate file giving the storage information. Please
1028+ consult the documentation of :func: `grid2op.Backend.Backend.load_storage_data ` for
1029+ more information.
1030+
1031+ Shunts (feature)
1032+ +++++++++++++++++
1033+
1034+ It is also possible to support shunts in grid2op.
1035+
1036+ For now the support of shunts is pretty basic. User provides a target in p and q as well
1037+ as a busbar on which to connect it.
1038+
1039+ There is no (yet) notion of `tap ` or anything more realistic at the moment.
1040+
1041+ .. note ::
1042+ The above sentence does NOT mean grid2op cannot handle this or that
1043+ if your backend supports it then you cannot make it a grid2op backend.
1044+
1045+ This means that a grid2op agent, using only grid2op functions will not
1046+ be able to operate the shunts in a "fine grained" / "more realistic"
1047+ fashion.
1048+
1049+ If you don't do anything about shunts, they will be deactivated, the agent
1050+ will not be able to directly operate the shunts using grid2op functions (
1051+ this does not mean shunts will be (or should be) removed from the powerflow
1052+ computation)
1053+
1054+ If you want to support them you need to define:
1055+
1056+ - in `load_grid() `:
1057+
1058+ - call `self.shunts_data_available = True `
1059+ - `self.n_shunt ` : number of shunts on the grid
1060+ - `self.shunts_to_subid ` : for each shunts, at which substation they are connected on the grid
1061+ - (optional): `self.name_shunt `. There is nothing comparable to
1062+ `self.gen_to_sub_pos ` or `self.gen_pos_topo_vect ` for shunts.
1063+
1064+ - in `apply_action() `:
1065+
1066+ - you receive most of the information on the shunt with `shunts_ ` variable. It is a
1067+ tuple with 3 elements (`shunt_p, shunt_q, shunt_bus = shunts__ `) with:
1068+
1069+ - `shunt_p ` the each shunt MW (given in the form a `ValueStore ` as always)
1070+ - `shunt_q ` the each shunt MVAr (given in the form a `ValueStore ` as always)
1071+ - `shunt_bus ` the each shunt MVAr (given in the form a `ValueStore ` as always)
1072+
1073+ - in the "getters" you need to implement the :func: `grid2op.Backend.Backend.shunt_info() ` methods that returns
1074+ 4 vectors: `shunt_p `, `shunt_q `, `shunt_v ` and `shunt_bus `
1075+ This is similar to :func: `grid2op.Backend.Backend.loads_info ` or
1076+ :func: `grid2op.Backend.Backend.generators_info ` for the `p `, `q ` and `v ` attributes.
1077+ The `bus ` gives the bus to which each shunt is connected.
1078+
1079+
1080+ Voltage angle (feature)
1081+ ++++++++++++++++++++++++
1082+
1083+ If you want your backend to output voltage angle values, you can specify it by invoking
1084+ (in the `__init__ ` or in `load_grid `) `self.can_output_theta = True `
1085+
1086+ In this case you need to implement :func: `grid2op.Backend.Backend.get_theta() `
1087+
1088+ .. note ::
1089+ At time of writing, grid2op does not offer a way to retrieve the theta values
1090+ of shunts. Shunts theta should not be retrieved by `get_theta `
1091+
1092+ .. warning ::
1093+ Even if your backend does not support storage units, you are expected to
1094+ return a `storage_theta ` vector which would be empty, for example
1095+ `storage_theta = np.empty(0, dtype=float) `
1096+
1097+ .. note ::
1098+ Voltage angles are expected to be given in degree and not in radian
1099+
1100+
1101+ Copy (feature)
1102+ +++++++++++++++++
1103+
1104+ By default, your backend is supposed to be "copy able".
1105+
1106+ The default implementation of :func: `grid2op.Backend.Backend.copy ` might
1107+ not be optimal for all backend.
1108+
1109+ You might want to overload it for a faster copy.
1110+
1111+ Non copy-able (feature)
1112+ ++++++++++++++++++++++++
1113+
1114+ If your backend cannot be copied (for example if it relies on a software
1115+ with license check), you can specify that `self._can_be_copied = False `
1116+ in the `load_grid ` implementation.
1117+
1118+ Faster line disconnection (speed)
1119+ ++++++++++++++++++++++++++++++++++
1120+
1121+ During the emulation of the "cascading failure" / "protections", performed
1122+ in :func: `grid2op.Backend.Backend.next_grid_state `, the special function
1123+ :func: `grid2op.Backend.Backend._disconnect_line ` is called.
1124+
1125+ It has a default implementation relying on the building
1126+ of a `backend_action ` and then calling
1127+ :func: `grid2op.Backend.Backend.apply_action ` which might be rather slow.
1128+
1129+ You can bypass everything by overloading this function
1130+ with a faster implementation.
1131+
1132+ .. note ::
1133+ This is definitely a "marginal gain".
1134+
1135+ Number of independant buses per substation
1136+ +++++++++++++++++++++++++++++++++++++++++++
1137+
1138+ Before grid2op <1.10.0, the "grid2op modelling" imposed that every substations
1139+ counted exactly 2 independant buses, and that each elements could be either connected
1140+ to one busbar OR the other.
1141+
1142+ From grid2op >=1.10.0 onwargs, the user might indicate the maximum number of
1143+ busbars wanted per substation with a call to `grid2op.make(..., n_busbar_per_sub=XXX) `.
1144+
1145+ If your backend does not provide this feature (yet ?) then you need to call
1146+ `self.cannot_handle_more_than_2_busbar() ` in the `load_grid ` implementation, preferably
1147+ at the beginning of it.
1148+
1149+ However, if your implementation can handle this (which is the case for most real
1150+ powerflow...) you need to call `self.can_handle_more_than_2_busbar() `
1151+
1152+ .. warning ::
1153+
1154+ In all cases then, you should either call :
1155+
1156+ - :func: `grid2op.Backend.Backend.cannot_handle_more_than_2_busbar `
1157+ - OR
1158+ - :func: `grid2op.Backend.Backend.can_handle_more_than_2_busbar `
1159+
1160+ In the implementation of `load_grid `
1161+
1162+ If you don't, the default grid2op behaviour is to fall back to "legacy mode"
1163+ where only 2 independant busbars per substation was supported.
1164+
1165+ This fall back erases the user defined behaviour in the call to env.make
1166+
1167+
1168+ Detachment
1169+ +++++++++++
1170+
1171+ Before grid2op <1.11, if an element (load, gen, storage units producing power)
1172+ was disconnected, then the episode stopped (done=True).
1173+
1174+ Starting from grid2op 1.11, the user can decide to allow the detachement of some
1175+ elements (load, gen, storage units) by calling `grid2op.make(..., allow_detachment=XXX) `
1176+
1177+ If your backend has been coded before this feature, then you need to call
1178+ `self.cannot_handle_detachment() `, preferably at the top of the `load_grid ` function.
1179+
1180+ If however your backend can support this feature, then you need to call
1181+ `self.can_handle_detachment() `, preferably at the top of the `load_grid ` function.
1182+
1183+ .. warning ::
1184+
1185+ In all cases then, you should either call :
1186+
1187+ - :func: `grid2op.Backend.Backend.cannot_handle_detachment `
1188+ - OR
1189+ - :func: `grid2op.Backend.Backend.can_handle_detachment `
1190+
1191+ In the implementation of `load_grid `
1192+
1193+ If you don't, the default grid2op behaviour is to fall back to "legacy mode"
1194+ where detachment was not supported: agent will not have the possibility
1195+ to disconnect things.
1196+
1197+ This fall back erases the user defined behaviour in the call to env.make
1198+
1199+ Automatic disconnection of elements
1200+ ++++++++++++++++++++++++++++++++++++
1201+
1202+ One of the stopping criteria of a grid2op episode is that the grid is split in
1203+ 2 or more independant connected component.
1204+
1205+ This is (at time of writing, grid2op 1.11.0) still "more or less" the case (but might evolve in the
1206+ next major release).
1207+
1208+ Realistic backends (used for real powergrid) might be "robust" to such cases, for example
1209+ by running different powerflow, one for each connected component, or by running a
1210+ powerflow only on the "main" connected component.
1211+
1212+ If that is the case, then the backend will "automatically" disconnect element not in the
1213+ "main" connected component before performing the computation for example.
1214+
1215+ As of writing, grid2op will stop an episode if the backend automatically disconnects
1216+ elements (loads, generators, lines etc.) by default.
1217+
1218+ To bypass the default behaviour, a "partial" support for this feature (=possibility for
1219+ a backend to continue a episode even if part of the grid is in blackout) is possible
1220+ if the backend implementation calls `self._prevent_automatic_disconnection = False `
1221+
1222+ Internally, this will deactivate some checking that ensure "done=True" is returned.
1223+
1224+ Vectorization in `apply_action ` (speed)
1225+ ++++++++++++++++++++++++++++++++++++++++
9821226TODO this will be explained "soon".
9831227
1228+ Overriding the public methods (speed)
1229+ ++++++++++++++++++++++++++++++++++++++++
1230+ TODO this will be explained "soon".
1231+
1232+ *eg * `load_grid_public `
1233+
9841234Detailed Documentation by class
9851235-------------------------------
9861236A first example of a working backend that can be easily understood (without nasty gory speed optimization)
0 commit comments