Creating a new backend¶
This page is organized as follow:
Objectives¶
Warning
Backends are internal to grid2op.
This page details how to create a backend from an available solver (a “things” that is able to compute flows and voltages). This is an advanced usage.
You will also find in this file the complete description on how the “powergrid” is represented in grid2op.
Backend is an abstraction that represents the physical system (the powergrid). In theory every powerflow can be
used as a backend. And example of a backend using Pandapower is available with
the grid2op.Backend.EducPandaPowerBackend.EducPandaPowerBackend
(only for demonstration purpose)
If you want working backend, please use the grid2op.Backend.PandaPowerBackend
that
uses Pandapower and
a port in c++ to a subset of pandapower called LightSim2Grid .
To implement completely a backend, you should implement all the abstract function defined here Backend. This file is an overview of what is needed and aims at contextualizing these requirements to make them clearer and easier to implement.
This section assume you have already a “code” that is able to compute some powerflow given a file stored somewhere, typically on a hard drive representing a powergrid. If you have that, you can probably use it to implement a grid2op backend and benefit from the whole grid2op ecosystem (code once a backend, reuse your “powerflow” everywhere). This includes, but is not limited to:
Save “logs” of your experiment in a standard format (with the runner) to be reused and analyzed graphically with grid2viz
Save “logs” of your experiments and compare the results with “reference” solver available
Act on your powergrid with a unified and “somewhat standard” fashion thanks to grid2op actions
Reuse agents that other people have trained in the context of L2RPN competitions
Train new grid controlers using the grid2op gym_compat module
etc.
Note
Grid2Op do not care about the modeling of the grid (static / steady state or dyanmic / transient) and both types of solver could be implemented as backend. At time of writing (december 2020), only steady state powerflow are available.
Note
The previous note entails that grid2op is also independent on the format used to store a powergrid. It’s expected that the “backend” fails to initialize if it cannot found any file to initialize the grid.
At time of writing (december 2020), all environments are shipped with grid represented as “json” format, which is the default format for the default backend based on PandaPower. If you want your “Backend” to have support for all previous environment, you might need to initialize it from this format or convert the IEEE files to the representation that suits you.
Note
If your backend is somehow available in a c++ library (static or dynamic) and you can link program against it, you can use the interface “lightsim2grid” if you don’t want to worry about grid2op representation (and skip this entire files)
Indeed, lightsim2grid takes care of of exposing a model of the grid (Ybus and Sbus) from the grid2op data and is able to directly expose the results from the internal state to valid grid2op vectors.
It has the advantage of being able to read the data from the default grid2op backend (based on PandaPower) and to allow to focus on the solver rather to focus on grid2op “representation” of the grid. It is also optimize for speed and (work in progress) aims at not copying any data from python -> c++ -> python when it can be avoided.
The code is also relatively well organized in:
modification of the elements
generation of the Ybus (sparse) complex matrix and Sbus complex vector
solving for the complex voltage V (and part of the Sbus vector) the equation V.(Ybus.V)* = Sbus with the “standard” “powerflow constraints”
computing the active power, reactive power, flow on powerllines etc. from the V
So you if you have a solver that can somehow solves the equation V.(Ybus.V)* = Sbus (and meeting the constraints of a standard powerflow) then lightsim2grid might be a good way to make this solver available in grid2op easily.
On the other hands, this “backend” also comes with a special model of each powersystem elements (loads, generators, lines, transformers or shunts for example) that you cannot modify and has some limitation to what it supports.
Main methods to implement¶
Typically, a backend has a internal “modeling” / “representation” of the powergrid stored in the attribute self._grid that can be anything. An more detailed example, with some “working minimal code” is given in the “example/backend_integration” of the grid2op repository.
There are 4 __main__ types of method you need to implement if you want to use a custom powerflow (eg from a physical solver, from a neural network, or any other methods):
grid2op.Backend.Backend.load_grid()
where the environment informs the instance of your backend of where the grid file is located. It is expected that this function defines all the attributes listed ingrid2op.Space.GridObjects
(more information about these attributes are given in the Grid description section of this file. It should not return anything. Its main goal is to “inform” grid2op about relevant information of the current powergrid and to initialize the _grid attribute of the backend.grid2op.Backend.Backend.apply_action()
: that modifies the internal state of the “Backend” you create properly (given the action taken by the agents, or the modifications of the data, or the emulation of some “automaton” of the environment etc.). More detail on how to “understand” a “BackendAction” is given in the BackendAction: modification section of this document. This function should not return anything. Its main goal is to allow the modification of the underlying powergrid from the environment.grid2op.Backend.Backend.runpf()
is called by the environment when a new “simulation” should be carried out. It should returnTrue
if it has converged, orFalse
otherwise. In case of non convergence (this function returnsFalse
), no flows can be inspected on the internal grid and the “environment” will interpret it as a “game over”.the “readers” functions (eg.
grid2op.Backend.Backend.get_topo_vect()
,grid2op.Backend.Backend.generators_info()
,grid2op.Backend.Backend.loads_info()
,grid2op.Backend.Backend.lines_or_info()
,grid2op.Backend.Backend.lines_ex_info()
orgrid2op.Backend.Backend.shunt_info()
) that allows to “export” data from the internal backend representation to a format the environment understands (ie vectors). You can consult the section Read back the results (flows, voltages etc.) of this document for more information. The main goal of these “getters” is to export some internal value of the backend in a “grid2op compliant format”.
Warning
With grid2op version before <1.7.1, you were also required to implement the
grid2op.Backend.Backend.copy()
method.
As of grid2op >= 1.7.1 this is no longer required. Note however that if you
don’t implement it, some features might not be available. This will
be the case for eg grid2op.Observation.BaseObservation.simulate()
or
for grid2op.simulator.Simulator
.
Grid description¶
In this section we explicit what attributes need to be implemented to have a valid backend instance. We focus on the attribute of the Backend you have to set. But don’t forget you also need to load a powergrid and store it in the _grid attribute.
Basically the load_grid function would look something like:
def load_grid(self, path=None, filename=None):
# simply handles different way of inputing the data
if path is None and filename is None:
raise RuntimeError("You must provide at least one of path or file to load a powergrid.")
if path is None:
full_path = filename
elif filename is None:
full_path = path
else:
full_path = os.path.join(path, filename)
if not os.path.exists(full_path):
raise RuntimeError("There is no powergrid at \"{}\"".format(full_path))
# load the grid in your favorite format:
self._grid = ... # the way you do that depends on the "solver" you use
# and now initialize the attributes (see list bellow)
self.n_line = ... # number of lines in the grid should be read from self._grid
self.n_gen = ... # number of generators in the grid should be read from self._grid
self.n_load = ... # number of generators in the grid should be read from self._grid
self.n_sub = ... # number of generators in the grid should be read from self._grid
# other attributes should be read from self._grid (see table below for a full list of the attributes)
self.load_to_subid = ...
self.gen_to_subid = ...
self.line_or_to_subid = ...
self.line_ex_to_subid = ...
# and finish the initialization with a call to this function
self._compute_pos_big_topo()
# the initial thermal limit
self.thermal_limit_a = ...
The grid2op attributes that need to be implemented in the grid2op.Backend.Backend.load_grid()
function are
given in the table bellow:
Name |
See paragraph |
Type |
Size |
Description |
---|---|---|---|---|
int |
NA |
Number of powerline on the grid (remember, in grid2op framework a powerline includes both “powerlines” and “transformer”) |
||
int |
NA |
Number of generators on the grid |
||
int |
NA |
Number of loads on the grid |
||
int |
NA |
Number of substations on the grid |
||
vect, int |
For each load, it gives the substation id to which it is connected |
|||
vect, int |
For each generator, it gives the substation id to which it is connected |
|||
vect, int |
For each powerline, it gives the substation id to which its origin end is connected |
|||
vect, int |
For each powerline, it gives the substation id to which its extremity end is connected |
|||
vect, str |
(optional) name of each load on the grid [if not set, by default it will be “load_$LoadSubID_$LoadID” for example “load_1_10” if the load with id 10 is connected to substation with id 1] |
|||
vect, str |
(optional) name of each generator on the grid [if not set, by default it will be “gen_$GenSubID_$GenID” for example “gen_2_42” if the generator with id 42 is connected to substation with id 2] |
|||
vect, str |
(optional) name of each powerline (and transformers !) on the grid [if not set, by default it will be “$SubOrID_SubExID_LineID” for example “1_4_57” if the powerline with id 57 has its origin end connected to substation with id 1 and its extremity end connected to substation with id 4] |
|||
vect, str |
(optional) name of each substation on the grid [if not set, by default it will be “sub_$SubID” for example “sub_41” for the substation with id 41] |
|||
vect, int |
(can be automatically set if you don’t initialize it) For each substation, it gives the number of elements connected to it (“elements” here denotes: powerline - and transformer- ends, load or generator) |
|||
int |
NA |
(can be automatically set if you don’t initialize it) Total number of elements on the grid (“elements” here denotes: powerline - and transformer- ends, load or generator) |
||
vect, int |
(can be automatically set if you don’t initialize it) See the description for more information (“a picture often speaks a thousand words”) |
|||
vect, int |
(can be automatically set if you don’t initialize it) See the description for more information (“a picture often speaks a thousand words”) |
|||
vect, int |
(can be automatically set if you don’t initialize it) See the description for more information (“a picture often speaks a thousand words”) |
|||
vect, int |
(can be automatically set if you don’t initialize it) See the description for more information (“a picture often speaks a thousand words”) |
|||
vect, int |
Automatically set with a call to self._compute_pos_big_topo |
|||
vect, int |
Automatically set with a call to self._compute_pos_big_topo |
|||
vect, int |
Automatically set with a call to self._compute_pos_big_topo |
|||
vect, int |
Automatically set with a call to self._compute_pos_big_topo |
TODO storage doc and shunts doc !
Example on how to set them¶
Some concrete example on how to create a backend are given in the grid2op.Backend.PandaPowerBackend
(for the default Backend) and in the “lightsim2grid” backend (available at
https://github.com/BDonnot/lightsim2grid ). Feel free to consult
any of these codes for more information.
In this example, we detail what is needed to create a backend and how to set the required attributes.
We explain step by step how to proceed with this powergid:
Prerequisite: Order and label everything¶
The first step is to give names and order to every object on the loaded grid.
For example, you can first assign order to substations this way:
Warning
To be consistent with python ecosystem, index are 0 based. So the first element should have id 0 (and not 1)
Then you decide an ordering of the loads:
Then the generators:
And then you deal with the powerlines. Which is a bit more “complex” as you need also to “assign side” ( “extremity” or “origin”) to each powerline end which is to define an “origin” end and and “extremity” end. This result in a possible ordering this way:
Optionally you also need to come up with a way of assign to each “element” an order in the substation. This is an extremely complex way to say you have to do this:
Note the number for each element in the substation.
In this example, for substaion with id 0 (bottom left) you decided that the powerline with id 0 (connected at this substation at its origin end) will be the “first object of this substation”. Then the “Load 0” is the second object [remember index a 0 based, so the second object has id 1], generator 0 is the third object of this substation (you can know it with the “3” near it) etc.
Note
Grid2op assumes that if the same files is loaded multiple times, then the same grid is defined by the backend. This entails that the loads are in the same order, substations are in the same order, generators are in the same order, powerline are in the same order (and for each powerrline is oriented the same way: same “origin” and same “extremity”).
This powergrid will be used throughout this example. And in the next sections, we suppose that you have chosen a way to assign all these “order”.
Note
The order of the elements has absolutely no impact whatsoever on the solver and the state of the grid. In other words flows, voltages etc. do not depend on this (arbitrary) order.
We could have chosen a different representation of this data (for example by identifying objects with names instead of ID in vector) but it turns out this “index based representation” is extremely fast as it allows manipulation of most data using the numpy package.
This is a reason why grid2op is relatively fast in most cases: very little time is taken to map objects to there properties.
Note
This can be done automatically if you don’t want to take care of this labelling.
If you chose to adopt the “automatic” way it will result in the following ordering:
load (if any is connected to this substation) will be labeled first
gen will be labeled just after
then origin side of powerline
then extremity side of powerline
This will result in a different labelling that the one we adopted here! So the vector *_to_sub_pos will be different, and so will the *_to_pos_topo_vect
Final result¶
For the most impatient readers, the final representation is :
In the next paragraphs we detail step by step why this is this way.
Number of elements (n_line, n_load, n_gen, n_sub)¶
Nothing much to say here, you count each object and assign the right val to the attributes. This gives:
For example, n_line is 8 because there are 8 lines on the grid, labeled from 0 to 7.
(optional) Substation information (sub_info, dim_topo)¶
New in version 1.3.2: This is now done automatically if the user do not set it.
For these attributes too, there is nothing really surprising (we remember that these can be set automatically if you don’t do it). We show how to set them mainly to explain grid2op “encoding” for these attributes that you might want to reuse somewhere else.
For each component of sub_info you tell grid2op of the number of elements connected to it. And then you sum up each of these elements in the dim_topo attributes.
Note
Only the loads, line ends (“origin” or “extremity”) and generators are counted as “elements”.
Substation id (*_to_subid)¶
The attribute explained in this section are load_to_subid, gen_to_subid, line_or_to_subid and line_ex_to_subid.
Again, for each of these vector, you specify to which substation the objects are connected. For example, for the loads this gives:
Indeed, the load with id 0 is connected to substation with id 0, load with id 1 is connected to substation with id 3 and load with id 2 is connected to substation with id 4.
For the other attributes, you follow the same pattern:
(optional) Position in substation (*_to_sub_pos)¶
New in version 1.3.2: This is now done automatically if the user do not set it.
These are the least common (and “most complicated”) attributes to set. This is why we implemented a function that takes care of computing it if you need. This function will make this choice, for each substation:
load (if any is connected to this substation) will be labeled first
gen will be labeled just after
then origin side of powerline
then extremity side of powerline
You are free to chose any other ordering, and this is why we explain in detail how this feature works here.
Note
It’s a “take all or nothing” feature. It means that you either take the full ordering detailed above or you need to implement a complete ordering yourself.
This values allow to uniquely identified, inside each substation. These were represented by the “small” number near each element in the last image of the introductory paragraph Prerequisite: Order and label everything. If you have that image in mind, it’s simple: you set the number of each elements into its vector. And that is it.
If you are confused, we made a detailed example below.
First, have a look at substation 0:
You know that, at this substation 0 there are 6 elements connected. In this example, these are:
origin end of Line 0
Load 0
gen 0
origin end of line 1
origin end of line 2
origin end of line 3
Given that, you can fill:
first component of line_or_to_sub_pos [origin of line 0 is connected at this substation]
first component of load_to_sub_pos [Load 0 is connected at this substation]
first component of gen_to_sub_pos [gen 0 is connected at this substation]
second component of line_or_to_sub_pos [origin of line 1 is connected at this substation]
third component of line_or_to_sub_pos [origin of line 2 is connected at this substation]
fourth component of line_or_to_sub_pos [origin of line 2 is connected at this substation]
These are indicated with the “??” on the figure above. (note that the XX cannot be set right now)
Once you know that, you just have to recall that you already give an order to each of these objects. You defined (in a purely arbitrary manner):
the element 0 of this substation to be “origin of line 0”
the element 1 of this substation to be “load 0”
the element 2 of this substation to be “gen 0”
the element 3 of this substation to be “origin of line 3”
the element 4 of this substation to be “origin of line 2”
the element 5 of this substation to be “origin of line 1”
So you get:
first component of line_or_to_sub_pos is 0 [because “origin end of line 0” is “element 0” of this substation]
first component of load_to_sub_pos is 1 [because “load 0” is “element 1” of this substation]
first component of gen_to_sub_pos is 2 [because “gen 0” is “element 2” of this substation]
fourth component of line_or_to_sub_pos is 3 [because “origin end of line 3” is “element 3” of this substation]
third component of line_or_to_sub_pos is 4 [because “origin end of line 2” is “element 4” of this substation]
second component of line_or_to_sub_pos is 5 [because “origin end of line 1” is “element 5” of this substation]
This is showed in the figure below:
Then you do the same process with substation 1 which will result in the vectors showed in the following plot:
When writing this, we realize it’s really verbose. But really, it simply consist on assigning, at each object, a unique ID to be able to retrieved it when querying something like “set the object of id 2 in subtation 0 to busbar 2”.
Finally, you proceed in the same manner for all substation and you get:
Position in the topology vector (*_pos_topo_vect)¶
This information is redundant with the other vector. It can be initialized with
a call to the function grid2op.Space.GridObjects._compute_pos_big_topo()
that you will need to perform after
having initialized all the other vectors as explained above (simply call self._compute_pos_big_topo() at the end
of your implementation of load_grid function)
BackendAction: modification¶
In this section we detail step by step how to understand the specific format used by grid2op to “inform” the backend on how to modify its internal state before computing a powerflow.
A BackendAction will tell the backend on what is modified among:
the active value of each loads (see paragraph Modifying the injections (productions and loads))
the reactive value of each loads (see paragraph Modifying the injections (productions and loads))
the amount of power produced by each generator (setpoint) (see paragraph Modifying the injections (productions and loads))
the voltage “setpoint” of each generator (see paragraph Modifying the injections (productions and loads))
the status (connected / disconnected) of each element (see paragraph Modifying the topology (status and busbar))
at which busbar each object is connected (see paragraph Modifying the topology (status and busbar))
Note
Typically the apply_action function is called once per step of the environment. The implementation of this function should be rather optimized for the best performance.
In this section we detail the format of these particular “actions”. We assume in the following that backendAction is the action you need to perform.
At the end, the apply_action function of the backend should look something like:
def apply_action(self, backendAction=None):
if backendAction is None:
return
active_bus, (prod_p, prod_v, load_p, load_q), _, shunts__ = backendAction()
# modify the injections [see paragraph "Modifying the injections (productions and loads)"]
for gen_id, new_p in prod_p:
# modify the generator with id 'gen_id' to have the new setpoint "new_p" for production
... # the way you do that depends on the `internal representation of the grid`
for gen_id, new_v in prod_v:
# modify the generator with id 'gen_id' to have the new value "new_v" as voltage setpoint
# be careful here ! new_v are given in kV and NOT in pu. So you need to convert them accordingly
... # the way you do that depends on the `internal representation of the grid`
for load_id, new_p in load_p:
# modify the load with id 'load_id' to have the new value "new_p" as consumption
... # the way you do that depends on the `internal representation of the grid`
for load_id, new_p in load_p:
# modify the load with id 'load_id' to have the new value "new_p" as consumption
... # the way you do that depends on the `internal representation of the grid`
# modify the topology [see paragraph "Modifying the topology (status and busbar)"]
loads_bus = backendAction.get_loads_bus()
for load_id, new_bus in loads_bus:
# modify the "busbar" of the loads
if new_bus == -1:
# the load is disconnected in the action, disconnect it on your internal representation of the grid
... # the way you do that depends on the `internal representation of the grid`
else:
# the load is moved to either busbar 1 (in this case `new_bus` will be `1`)
# or to busbar 2 (in this case `new_bus` will be `2`)
... # the way you do that depends on the `internal representation of the grid`
gens_bus = backendAction.get_gens_bus()
for gen_id, new_bus in gens_bus:
# modify the "busbar" of the generators
if new_bus == -1:
# the gen is disconnected in the action, disconnect it on your internal representation of the grid
... # the way you do that depends on the `internal representation of the grid`
else:
# the gen is moved to either busbar 1 (in this case `new_bus` will be `1`)
# or to busbar 2 (in this case `new_bus` will be `2`)
... # the way you do that depends on the `internal representation of the grid`
lines_or_bus = backendAction.get_lines_or_bus()
for line_id, new_bus in lines_or_bus:
# modify the "busbar" of the origin end of powerline line_id
if new_bus == -1:
# the origin end of powerline is disconnected in the action, disconnect it on your internal representation of the grid
... # the way you do that depends on the `internal representation of the grid`
else:
# the origin end of powerline is moved to either busbar 1 (in this case `new_bus` will be `1`)
# or to busbar 2 (in this case `new_bus` will be `2`)
... # the way you do that depends on the `internal representation of the grid`
lines_ex_bus = backendAction.get_lines_ex_bus()
for line_id, new_bus in lines_ex_bus:
# modify the "busbar" of the extremity end of powerline line_id
if new_bus == -1:
# the extremity end of powerline is disconnected in the action, disconnect it on your internal representation of the grid
... # the way you do that depends on the `internal representation of the grid`
else:
# the extremity end of powerline is moved to either busbar 1 (in this case `new_bus` will be `1`)
# or to busbar 2 (in this case `new_bus` will be `2`)
... # the way you do that depends on the `internal representation of the grid`
New in version 1.3.2: Before version 1.3.2, you could not use the get_loads_bus, get_gens_bus, get_lines_or_bus or get_lines_ex_bus method. Please upgrade to grid2op 1.3.2 or later to use these.
Retrieve what has been modified¶
This is the first step you would need to perform to retrieve what are the modifications you need to implement in the backend. This is achieved with:
active_bus, (prod_p, prod_v, load_p, load_q), _, shunts__ = backendAction()
And all information needed to set the state of your backend is now available. We will explain them step by step in the following paragraphs.
Modifying the injections (productions and loads)¶
The new setpoints for the injections are given in the vectors (prod_p, prod_v, load_p, load_q) retrieved in the above paragraph (see Retrieve what has been modified for more information).
Each of the prod_p, prod_v, load_p and load_q are specific types of “iterable” that stores which values have been modified and what is the new value associated.
The first way to retrieve the modification is with a simple for loop:
for gen_id, new_p in prod_p:
# modify the generator with id 'gen_id' to have the new setpoint "new_p"
... # the way you do that depends on the `internal representation of the grid`
Note
If no changes have affected the “active production setpoint of generator” then it will not be “looped through”: only generators that have been modified between the steps will be showed here. So if you need to passe the values of all generators (for example) you need to remember these values yourself.
Of course it works the same way with the other “iterables”:
for gen_id, new_v in prod_v:
# modify the generator with id 'gen_id' to have the new value "new_v" as voltage setpoint
... # the way you do that depends on the `internal representation of the grid`
for load_id, new_p in load_p:
# modify the load with id 'load_id' to have the new value "new_p" as consumption
... # the way you do that depends on the `internal representation of the grid`
for load_id, new_p in load_p:
# modify the load with id 'load_id' to have the new value "new_p" as consumption
... # the way you do that depends on the `internal representation of the grid`
Modifying the topology (status and busbar)¶
This is probably the most “difficult” part of implementing a new backend based on your solver. This is because modification of topology is probably not as common as modifying the the production or consumption.
For this purpose we recommend to use the get_loads_bus, get_gens_bus, get_lines_or_bus and get_lines_ex_bus functions of the backendAction.
These functions can be used in the following manner:
# modify the topology
loads_bus = backendAction.get_loads_bus()
for load_id, new_bus in loads_bus:
# modify the "busbar" of the loads
if new_bus == -1:
# the load is disconnected in the action, disconnect it on your internal representation of the grid
... # the way you do that depends on the `internal representation of the grid`
else:
# the load is moved to either busbar 1 (in this case `new_bus` will be `1`)
# or to busbar 2 (in this case `new_bus` will be `2`)
... # the way you do that depends on the `internal representation of the grid`
And of course you do the same for generators and both ends of each powerline.
Note
About powerline, grid2op adopts the following convention: a powerline cannot be connected on one side and disconnected on the other.
That being said, it’s still possible to connect the extremity of a powerline “alone” on a busbar, which will have the same effect of having it “disconnected at one ends only”.
Read back the results (flows, voltages etc.)¶
This last “technical” part concerns what can be refer to as “getters” from the backend. These functions allow to read back the state of the grid and expose its results to grid2op in a standardize manner.
The main functions are:
grid2op.Backend.Backend.generators_info()
: that allows to export the information related to generators (more detail in subsection Retrieving injections and flows)grid2op.Backend.Backend.loads_info()
: enabling to read the state of the loads (more detail in subsection Retrieving injections and flows)grid2op.Backend.Backend.lines_or_info()
: export information about the “origin” side of the powerlines (more detail in subsection Retrieving injections and flows)grid2op.Backend.Backend.lines_ex_info()
: export information about the “extremity” side of the powerlines (more detail in subsection Retrieving injections and flows)grid2op.Backend.Backend.get_topo_vect()
: represent information about the topology of the grid after the solver has run (for each element, it says on which busbar it’s connected) (more detail in subsection Retrieving the topology)
Retrieving injections and flows¶
In this subsection we detail how we retrieve the information about the flows (at each side of all the powerlines and transformers) and how to retrieve the value associated with each “element” of the grid.
The functions you need to implement are described in the table below:
Name |
Number of vectors |
Description |
---|---|---|
3 |
active setpoint, reactive absorption / production and voltage magnitude at the bus to which it’s connected |
|
3 |
active consumption, reactive consumption and voltage magnitude of the bus to which it’s connected |
|
4 |
active flow, reactive flow, voltage magnitude of the bus to which it’s connected and current flow |
|
4 |
active flow, reactive flow, voltage magnitude of the bus to which it’s connected and current flow |
It follows the same “representation” as anything else in grid2op : each of these values are represented as vectors.
Vector should have fixed size, and each element (eg origin side of powerline L0) should be always at the same place
in the vectors. For example, if you implement grid2op.Backend.Backend.lines_or_info()
, it should return
4 vectors:
p_or: represents the active flows on each powerline (origin side)
q_or: represents the ractive flows on each powerline (origin side)
v_or: represents the voltage magnitude of each powerline (origin side)
a_or: represents the current flows on each powerline (origin side)
The first element of p_or vector expose the active flow on origin side of powerline l0, first component of a_or represents the current flow on origin side of l0.
This is further illustrated in the figure below (that concerns the the loads, but it’s the same ideas for all the other elements):
Note
The values that should be retrieved here are what is considered as “if i had placed a (perfect) sensor on this element what would this sensor display.
This is what is considered as “real world data” by grid2op. If you want to model delay in the measurements or some lack of precision in the sensors (for example) we don’t advise to do it on the Backend side, but rather to code a different types of “Observation” that would add some noise to the values returned by grid2op.
Note
Depending on the capacity of the Backend and its precision in modeling “real systems” the values returned in these vector may, or may not be different from their setpoint.
For example, if the backend do not model that “real” generators have limit for the reactive power they can absorb / produce then it’s likely that the “prod_v” you get from the backend is the same as the “prod_v” that has been used when the “apply_action” has been called.
Grid2Op is independent from the “model of grid” you are using. So grid2op itself will not be affected (and nothing will check) whether “prod_v” after a powerflow is the same as “prod_v” before.
Retrieving the topology¶
The last information you need to provide for your backend is tha ability to retrieve the internal topology of the powergrid.
This function is especially important if your solver is able to simulate the behaviour of some complex automatons on the grid. In this case, the topology after the powerflow (and the possible trigger of these automatons) can be different from the initial topology of the grid as provided by the backend action (see section Grid description for more information).
Remember that to interface with grid2op, you needed to define orders of object in all the substation. To know the position of each element in the topology vector, grid2op simply concatenate all vectors of all substation.
Let’s start by substation 0:
Then you concatenate to it the vector representing substation 1:
And you do chat for all substations, giving:
So in this simple example, the first element of the topology vector will represent the origin of powerline 0, the second element will represent the load 0, the 7th element (id 6, remember python index are 0 based) represent first element of substation 1, so in this case extremity end of powerline 3, the 8th element the generator 1, etc. up to element with id 20 whith is the last element of the last substation, in this case extremity of powerline 7.
Once you know the order, the encoding is pretty straightforward:
-1 means “the element is disconnected”
1 means “the element is connected on busbar 1”
2 means “the element is connected on busbar 2”
etc.
So, for example, we have, in our grid, if generator 1 is disconnected and powerline l7 is disconnected:
And to represent a substation with multiple “nodes” (buses) you can encode it this way:
In this case, at substation 2 there are 2 connected busbars:
busbar 1 connects extremity of powerline 4 and origin of powerline 5
busbar 2 connects extremity of powerline 1 and origin of powerline 6
But there is no direct connection between busbar 1 and busbar 2.
Advanced usage and speed optimization¶
TODO this will be explained “soon”.
Detailed Documentation by class¶
If you still can’t find what you’re looking for, try in one of the following pages:
Still trouble finding the information ? Do not hesitate to send a github issue about the documentation at this link: Documentation issue template