You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While this seems to be complete, in certain cases it would be convenient to have a Networkx Graph that does NOT encode power flows, but only topology. I will refer to this as a topology graph. The advantage of this is that it should be quicker/easier to compute than the energy/elements graphs, but is still in a networkx format (so we can more conveniently do graph operations / algorithms on it).
Describe the solution you'd like
The following code recreates the energy graph without any power flow / extra attributes, only topological ones (connected / disconnected). This is a standalone class, but I would NOT recommend using this implementation directly. Instead there could be an optional argument when generating the energy graph that skips all power flow-related steps? Note the add_act method is identical to the one suggested in Issue 681
classTopoGraphConverter():
def__init__(self, env:BaseEnv):
self.n_busbar_per_sub=env.n_busbar_per_subself.name_sub=env.name_subself.n_sub=env.n_subself._topo_vect_to_sub=env._topo_vect_to_subself.local_bus_to_global=env.local_bus_to_globalself.detachment_is_allowed=env.detachment_is_allowedself.name_load=env.name_loadself.n_load=env.n_loadself.load_to_subid=env.load_to_subidself.name_gen=env.name_genself.n_gen=env.n_genself.gen_to_subid=env.gen_to_subidself.name_line=env.name_lineself.n_line=env.n_lineself.line_or_to_subid=env.line_or_to_subidself.line_ex_to_subid=env.line_ex_to_subidself.name_storage=env.name_storageself.n_storage=env.n_storageself.storage_to_subid=env.storage_to_subidself.name_shunt=env.name_shuntself.n_shunt=env.n_shuntself.shunt_to_subid=env.shunt_to_subidself.NB_TIMESTEP_OVERFLOW_ALLOWED=env.parameters.NB_TIMESTEP_OVERFLOW_ALLOWEDdefadd_act(self, obs:BaseObservation, act:BaseAction, issue_warn=True):
cls=type(obs)
cls_act=type(act)
act=copy.deepcopy(act)
res=cls()
res.set_game_over(env=None)
res.topo_vect[:] =obs.topo_vectres.line_status[:] =obs.line_statusres.timestep_overflow[:] =obs.timestep_overflowres.timestep_overflow[obs.rho>1.0] +=1overflow_mask=res.timestep_overflow>self.NB_TIMESTEP_OVERFLOW_ALLOWEDres.line_status[overflow_mask] =-1line_or_topo_vect=res.topo_vect[obs.line_or_pos_topo_vect]
line_or_topo_vect[overflow_mask] =-1line_ex_topo_vect=res.topo_vect[obs.line_ex_pos_topo_vect]
line_ex_topo_vect[overflow_mask] =-1res.topo_vect[obs.line_or_pos_topo_vect] =line_or_topo_vectres.topo_vect[obs.line_ex_pos_topo_vect] =line_ex_topo_vect# If a powerline has been reconnected without specific bus, issue a warningif"set_line_status"incls_act.authorized_keys:
obs._aux_add_act_set_line_status(cls, cls_act, act, res, issue_warn)
# topo vectif"set_bus"incls_act.authorized_keys:
res.topo_vect[act.set_bus!=0] =act.set_bus[act.set_bus!=0]
if"change_bus"incls_act.authorized_keys:
do_change_bus_on=act.change_bus& (
res.topo_vect>0
) # change bus of elements that were onres.topo_vect[do_change_bus_on] =3-res.topo_vect[do_change_bus_on]
# topo vect: reco of powerline that should beres.line_status= (res.topo_vect[cls.line_or_pos_topo_vect] >=1) & (
res.topo_vect[cls.line_ex_pos_topo_vect] >=1
)
# powerline statusif"set_line_status"incls_act.authorized_keys:
obs._aux_add_act_set_line_status2(cls, cls_act, act, res, issue_warn)
if"change_line_status"incls_act.authorized_keys:
obs._aux_add_act_change_line_status2(cls, cls_act, act, res, issue_warn)
returnresdef_aux_add_edges(self, el_ids, el_global_bus,
nb_el,el_connected,el_name,
edges_prop, graph):
edges_el= [(el_ids[el_id], self.n_sub+el_global_bus[el_id]) ifel_connected[el_id] elseNoneforel_idinrange(nb_el)
]
li_el_edges= [(*edges_el[el_id],
{"id": el_id,
"type": f"{el_name}_to_bus"})
forel_idinrange(nb_el)
ifel_connected[el_id]]
ifedges_propisnotNone:
ed_num=0# edge numberforel_idinrange(nb_el):
ifnotel_connected[el_id]:
continueforprop_nm, prop_vectinedges_prop:
li_el_edges[ed_num][-1][prop_nm] =prop_vect[el_id]
ed_num+=1graph.add_edges_from(li_el_edges)
returnli_el_edgesdef_aux_add_el_to_comp_graph(self, graph,
first_id,el_names_vect,
el_name, nb_el,
el_bus=None, el_to_sub_id=None,
nodes_prop=None, edges_prop=None):
ifel_busisNoneandel_to_sub_idisnotNone:
raiseGrid2OpException("el_bus is None and el_to_sub_id is not None")
ifel_busisnotNoneandel_to_sub_idisNone:
raiseGrid2OpException("el_bus is not None and el_to_sub_id is None")
# add the nodes for the elements of this typesel_ids=first_id+np.arange(nb_el)
# add the properties for these nodesli_el_node= [(el_ids[el_id],
{"id": el_id,
"type": f"{el_name}",
"name": el_names_vect[el_id]
})
forel_idinrange(nb_el)]
ifel_busisnotNone:
el_global_bus=self.local_bus_to_global(el_bus,
el_to_sub_id)
el_connected=np.array(el_global_bus) >=0forel_idinrange(nb_el):
li_el_node[el_id][-1]["connected"] =el_connected[el_id]
ifnodes_propisnotNone:
forel_idinrange(nb_el):
forprop_nm, prop_vectinnodes_prop:
li_el_node[el_id][-1][prop_nm] =prop_vect[el_id]
ifel_busisNoneandel_to_sub_idisNone:
graph.add_nodes_from(li_el_node)
returnel_ids# Add the edgesself._aux_add_edges(
el_ids, el_global_bus, nb_el,
el_connected, el_name, edges_prop,graph)
graph.add_nodes_from(li_el_node)
returnel_idsdef_aux_get_connected_buses(self, obs:BaseObservation):
res=np.full(self.n_busbar_per_sub*self.n_sub, fill_value=False)
global_bus=self.local_bus_to_global(obs.topo_vect,
self._topo_vect_to_sub)
res[global_bus[global_bus!=-1]] =Truereturnresdef_aux_add_buses(self, obs:BaseObservation, graph, first_id):
bus_ids=first_id+np.arange(self.n_busbar_per_sub*self.n_sub)
conn_bus=self._aux_get_connected_buses(obs)
bus_li= [
(bus_ids[bus_id],
{"id": bus_id,
"connected": conn_bus[bus_id]})
forbus_idinrange(self.n_busbar_per_sub*self.n_sub)
]
graph.add_nodes_from(bus_li)
edge_bus_li= [(bus_id,
bus_id%self.n_sub,
{"type": "bus_to_substation"})
forbus_idinbus_ids]
graph.add_edges_from(edge_bus_li)
returnbus_idsdef_aux_add_loads(self, obs:BaseObservation, graph, first_id):
load_ids=self._aux_add_el_to_comp_graph(
graph, first_id, self.name_load,
"load", self.n_load, obs.load_bus, self.load_to_subid)
returnload_idsdef_aux_add_gens(self, obs:BaseObservation, graph, first_id):
gen_ids=self._aux_add_el_to_comp_graph(
graph, first_id, self.name_gen,
"gen", self.n_gen, obs.gen_bus, self.gen_to_subid)
returngen_idsdef_aux_add_edge_line_side(self, graph, bus,
sub_id, line_node_ids,
side):
global_bus=self.local_bus_to_global(bus, sub_id)
conn_=np.array(global_bus) >=0res=self._aux_add_edges(
line_node_ids, global_bus, self.n_line,
conn_, "line", edges_prop=None, graph=graph)
returnresdef_aux_add_lines(self, obs:BaseObservation, graph, first_id):
nodes_prop= [("connected", obs.line_status),
("timestep_overflow", obs.timestep_overflow)]
lin_ids=self._aux_add_el_to_comp_graph(
graph, first_id, self.name_line,
"line", self.n_line, el_bus=None,
el_to_sub_id=None, nodes_prop=nodes_prop)
# Add "or" edgesself._aux_add_edge_line_side(graph, obs.line_or_bus,
self.line_or_to_subid, lin_ids, "or")
# Add "ex" edgesself._aux_add_edge_line_side(graph, obs.line_ex_bus,
self.line_ex_to_subid, lin_ids, "ex")
returnlin_idsdef_aux_add_storages(self, obs:BaseObservation, graph, first_id):
sto_ids=self._aux_add_el_to_comp_graph(
graph, first_id, self.name_storage,
"storage", self.n_storage, obs.storage_bus,
el_to_sub_id=self.storage_to_subid)
returnsto_idsdef_aux_add_shunts(self, obs:BaseObservation, graph, first_id):
sto_ids=self._aux_add_el_to_comp_graph(
graph, first_id, self.name_shunt,
"shunt", self.n_shunt, obs._shunt_bus,
el_to_sub_id=self.shunt_to_subid)
returnsto_idsdefconvert(self, obs:BaseObservation):
# Initialize the graph with "grid level" attributesgraph=networkx.DiGraph()
# Add the substationssub_li= [(sub_id,
{"id": sub_id,
"type": "substation",
"name": self.name_sub[sub_id]})
forsub_idinrange(self.n_sub)]
graph.add_nodes_from(sub_li)
# Handle Busesbus_ids=self._aux_add_buses(obs, graph, env.n_sub)
# Handle Loadsload_ids=self._aux_add_loads(obs, graph, bus_ids[-1] +1)
# Handle Gensgen_ids=self._aux_add_gens(obs, graph, load_ids[-1] +1)
# Handle Linesline_ids=self._aux_add_lines(obs, graph, gen_ids[-1] +1)
# Handle Energy Storagesto_ids=Noneif (has_storage:=self.n_storage>0):
sto_ids=self._aux_add_storages(obs, graph, line_ids[-1] +1)
# Handle shuntsiftype(obs).shunts_data_available:
_=self._aux_add_shunts(obs, graph,
sto_ids[-1] +1ifhas_storageelseline_ids[-1] +1)
# And now we use the data above to put the right properties to the nodes for the busesbus_v_theta= {}
forbus_idinbus_ids:
li_pred=list(graph.predecessors(n=bus_id))
ifli_pred:
bus_v_theta[bus_id] = {"connected": True}
else:
bus_v_theta[bus_id] = {"connected": False}
networkx.set_node_attributes(graph, bus_v_theta)
# Extra layer of security: prevent accidental modification of this graphnetworkx.freeze(graph)
returngraph
The text was updated successfully, but these errors were encountered:
Is your feature request related to a problem? Please describe.
In the documentation "A grid, a graph: grid2op representation of the powergrid", there are currently 5 ways of graphs/matrix representations of an observation in Grid2Op:
Observation
Describe the solution you'd like
The following code recreates the energy graph without any power flow / extra attributes, only topological ones (connected / disconnected). This is a standalone class, but I would NOT recommend using this implementation directly. Instead there could be an optional argument when generating the energy graph that skips all power flow-related steps? Note the add_act method is identical to the one suggested in Issue 681
The text was updated successfully, but these errors were encountered: