1- # -*- coding: utf-8 -*-
2-
31# Copyright (c) 2016-2026 by University of Kassel and Fraunhofer Institute for Energy Economics
42# and Energy System Technology (IEE), Kassel. All rights reserved.
53
64import sys
75import copy
6+ from typing import TYPE_CHECKING , Iterable
87
98import geojson
9+ import networkx
1010import networkx as nx
1111import pandas as pd
1212import numpy as np
1313
14- from pandapower .auxiliary import soft_dependency_error
14+ from pandapower .auxiliary import pandapowerNet , soft_dependency_error
15+ from pandapower .create ._utils import add_column_to_df
1516from pandapower .topology .create_graph import create_nxgraph
1617from pandapower .topology .graph_searches import connected_components
1718
2324
2425import logging
2526
27+ if TYPE_CHECKING :
28+ import igraph
29+
2630logger = logging .getLogger (__name__ )
2731
2832
29- def build_igraph_from_pp (net , respect_switches = False , buses = None , trafo_length_km = 0.01 , switch_length_km = 0.001 ,
30- dcline_length_km = 1.0 ):
33+ def build_igraph_from_pp (
34+ net : pandapowerNet ,
35+ respect_switches : bool = False ,
36+ buses = None ,
37+ trafo_length_km = 0.01 ,
38+ switch_length_km = 0.001 ,
39+ dcline_length_km = 1.0
40+ ):
3141 """
3242 This function uses the igraph library to create an igraph graph for a given pandapower network.
3343 Lines, transformers and switches are respected.
3444 Performance vs. networkx: https://graph-tool.skewed.de/performance
3545
36- :param net: pandapower network
37- :type net: pandapowerNet
38- :param respect_switches: if True, exclude edges for open switches (also lines that are \
39- connected via line switches)
40- :type respect_switches: bool, default False
46+ Parameters:
47+ net: the pandapower network
48+ respect_switches: if True, exclude edges for open switches (also lines that are connected via line switches)
4149
42- : Example:
43- graph, meshed, roots = build_igraph_from_pp(net)
50+ Example:
51+ >>> graph, meshed, roots = build_igraph_from_pp(net)
4452 """
4553 if not IGRAPH_INSTALLED :
4654 soft_dependency_error (str (sys ._getframe ().f_code .co_name )+ "()" , "igraph" )
@@ -123,19 +131,23 @@ def _get_switch_mask(net, element, switch_element, open_switches):
123131 open_element_mask = np .isin (net [element ].index , open_elements , invert = True )
124132 return open_element_mask
125133
126- def coords_from_igraph (graph , roots , meshed = False , calculate_meshed = False ):
134+ def coords_from_igraph (
135+ graph : "igraph.Graph" ,
136+ roots : Iterable ,
137+ meshed : bool = False ,
138+ calculate_meshed : bool = False
139+ ) -> list [list [float ]]:
127140 """
128141 Create a list of generic coordinates from an igraph graph layout.
129142
130- :param graph: The igraph graph on which the coordinates shall be based
131- :type graph: igraph.Graph
132- :param roots: The root buses of the graph
133- :type roots: iterable
134- :param meshed: determines if the graph has any meshes
135- :type meshed: bool, default False
136- :param calculate_meshed: determines whether to calculate the meshed status
137- :type calculate_meshed: bool, default False
138- :return: coords - list of coordinates from the graph layout
143+ Parameters:
144+ graph: The igraph graph on which the coordinates shall be based
145+ roots: The root buses of the graph
146+ meshed: determines if the graph has any meshes
147+ calculate_meshed: determines whether to calculate the meshed status
148+
149+ Return:
150+ list of coordinates from the graph layout
139151 """
140152 if calculate_meshed :
141153 meshed = False
@@ -151,15 +163,19 @@ def coords_from_igraph(graph, roots, meshed=False, calculate_meshed=False):
151163 return list (zip (* layout .coords ))
152164
153165
154- def coords_from_nxgraph (mg = None , layout_engine = 'neato' ):
166+ def coords_from_nxgraph (
167+ mg : networkx .Graph = None ,
168+ layout_engine : str = 'neato'
169+ ) -> list [list [float ]]:
155170 """
156171 Create a list of generic coordinates from a networkx graph layout.
157172
158- :param mg: The networkx graph on which the coordinates shall be based
159- :type mg: networkx.Graph
160- :param layout_engine: GraphViz Layout Engine for layouting a network. See https://graphviz.org/docs/layouts/
161- :type layout_engine: str
162- :return: coords - list of coordinates from the graph layout
173+ Parameters:
174+ mg: The networkx graph on which the coordinates shall be based
175+ layout_engine: GraphViz Layout Engine for layouting a network. See https://graphviz.org/docs/layouts/
176+
177+ Return:
178+ list of coordinates from the graph layout
163179 """
164180 # workaround for bug in agraph
165181 for u , v in mg .edges (data = False ):
@@ -171,42 +187,43 @@ def coords_from_nxgraph(mg=None, layout_engine='neato'):
171187 return list (zip (* (list (nx .drawing .nx_agraph .graphviz_layout (mg , prog = layout_engine ).values ()))))
172188
173189
174- def create_generic_coordinates (net , mg = None , library = "igraph" ,
175- respect_switches = False ,
176- geodata_table = "bus" ,
177- buses = None ,
178- overwrite = False ,
179- layout_engine = 'neato' ,
180- trafo_length_km = 0.01 ,
181- switch_length_km = 0.001 ):
190+ def create_generic_coordinates (
191+ net : pandapowerNet ,
192+ mg : networkx .Graph = None ,
193+ library : str = "igraph" ,
194+ respect_switches : bool = False ,
195+ geodata_table : str = "bus" ,
196+ buses : Iterable [int ] = None ,
197+ overwrite : bool = False ,
198+ layout_engine : str = 'neato' ,
199+ trafo_length_km : float = 0.01 ,
200+ switch_length_km : float = 0.001
201+ ) -> pandapowerNet :
182202 """
183203 This function will add arbitrary geo-coordinates for all buses based on an analysis of branches
184204 and rings. It will remove out of service buses/lines from the net. The coordinates will be
185205 created either by igraph or by using networkx library.
186206
187- :param net: pandapower network
188- :type net: pandapowerNet
189- :param mg: Existing networkx multigraph, if available. Convenience to save computation time.
190- :type mg: networkx.Graph
191- :param respect_switches: respect switches in a network for generic coordinates
192- :type respect_switches: bool
193- :param library: "igraph" to use igraph package or "networkx" to use networkx package
194- :type library: str
195- :param geodata_table: table to write the generic geodatas to
196- :type geodata_table: str
197- :param buses: buses for which generic geodata are created, all buses will be used by default
198- :type buses: list
199- :param overwrite: overwrite existing geodata
200- :type overwrite: bool
201- :param layout_engine: GraphViz Layout Engine for layouting a network. See https://graphviz.org/docs/layouts/
202- :type layout_engine: str
203- :return: net - pandapower network with added geo coordinates for the buses
204-
205- :Example:
207+ Parameters:
208+ net: pandapower network
209+ mg: Existing networkx multigraph, if available. Convenience to save computation time.
210+ respect_switches: respect switches in a network for generic coordinates
211+ library: "igraph" to use igraph package or "networkx" to use networkx package
212+ geodata_table: table to write the generic geodatas to
213+ buses: buses for which generic geodata are created, all buses will be used by default
214+ overwrite: overwrite existing geodata
215+ layout_engine: GraphViz Layout Engine for layouting a network. See https://graphviz.org/docs/layouts/
216+
217+ Return:
218+ the pandapower network with added geo coordinates for the buses.
219+ Does not copy the network, so the original network will be modified!
220+
221+ Example:
206222 >>> net = create_generic_coordinates(net)
207223 """
208-
209- _prepare_geodata_table (net , geodata_table , overwrite )
224+ if buses is None :
225+ buses = net [geodata_table ].index .tolist ()
226+ _prepare_geodata_table (net , geodata_table , overwrite , buses )
210227 if library == "igraph" :
211228 if not IGRAPH_INSTALLED :
212229 soft_dependency_error ("build_igraph_from_pp()" , "igraph" )
@@ -224,23 +241,38 @@ def create_generic_coordinates(net, mg=None, library="igraph",
224241 else :
225242 raise ValueError ("Unknown library %s - chose 'igraph' or 'networkx'" % library )
226243 if len (coords ):
227- net [geodata_table ]["geo" ] = pd .Series (
228- map (lambda x : geojson .dumps (geojson .Point ((x [1 ], x [0 ])), sort_keys = True ), zip (* coords )),
229- index = net [ geodata_table ]. index if buses is None else buses ,
244+ net [geodata_table ]["geo" ][ buses ] = pd .Series (
245+ data = map (lambda x : geojson .dumps (geojson .Point ((x [1 ], x [0 ])), sort_keys = True ), zip (* coords )),
246+ index = buses ,
230247 )
231248 return net
232249
233250
234- def _prepare_geodata_table (net , geodata_table , overwrite ):
235- if geodata_table in net and "geo" in net [geodata_table ] and net [geodata_table ]["geo" ].dropna ().shape [0 ]:
251+ def _prepare_geodata_table (
252+ net : pandapowerNet , geodata_table : str , overwrite : bool , elements : Iterable [int ] | None
253+ ) -> None :
254+ if geodata_table not in net or "geo" not in net [geodata_table ]:
255+ try :
256+ add_column_to_df (net , geodata_table , "geo" )
257+ except KeyError as e :
258+ logger .warning ("Creating geodata for a unknown table" )
259+ if geodata_table not in net :
260+ net [geodata_table ] = pd .DataFrame (columns = ["geo" ], index = elements , dtype = pd .StringDtype ())
261+ else :
262+ net [geodata_table ]["geo" ] = pd .NA
263+ if elements is None :
264+ elements = net [geodata_table ].index .tolist ()
265+ try :
266+ net [geodata_table ].loc [elements ]
267+ except KeyError as e :
268+ logger .error (f"While preparing geodata table for { geodata_table } a nonexistent bus was passed!" )
269+ raise e
270+ if net [geodata_table ].loc [elements , "geo" ].dropna ().shape [0 ]:
236271 if overwrite :
237- net [geodata_table ] = net [geodata_table ].drop ("geo" , axis = 1 )
238- net [geodata_table ] = net [geodata_table ].dropna (how = 'all' )
272+ net [geodata_table ].loc [elements , "geo" ] = pd .NA
239273 else :
240274 raise UserWarning (f"Table { geodata_table } is not empty - use overwrite=True to overwrite existing geodata" )
241275
242- if geodata_table not in net :
243- net [geodata_table ] = pd .DataFrame (columns = ["geo" ])
244276
245277def fuse_geodata (net ):
246278 mg = create_nxgraph (net , include_lines = False , include_impedances = False , respect_switches = False )
0 commit comments