e2nIEE / pandapower
 1 ```# -*- coding: utf-8 -*- ``` 2 3 ```# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics ``` 4 ```# and Energy System Technology (IEE), Kassel. All rights reserved. ``` 5 6 7 1 ```import networkx as nx ``` 8 1 ```import pandas as pd ``` 9 1 ```from collections import deque ``` 10 1 ```from itertools import combinations ``` 11 12 1 ```from pandapower.topology.create_graph import create_nxgraph ``` 13 14 15 1 ```def connected_component(mg, bus, notravbuses=[]): ``` 16 ``` """ ``` 17 ``` Finds all buses in a NetworkX graph that are connected to a certain bus. ``` 18 19 ``` INPUT: ``` 20 ``` **mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network. ``` 21 22 ``` **bus** (integer) - Index of the bus at which the search for connected components originates ``` 23 24 25 ``` OPTIONAL: ``` 26 ``` **notravbuses** (list/set) - indices of notravbuses: lines connected to these buses are ``` 27 ``` not being considered in the graph ``` 28 29 ``` OUTPUT: ``` 30 ``` **cc** (generator) - Returns a generator that yields all buses connected to the input bus ``` 31 32 ``` EXAMPLE: ``` 33 ``` import pandapower.topology as top ``` 34 35 ``` mg = top.create_nxgraph(net) ``` 36 37 ``` cc = top.connected_component(mg, 5) ``` 38 39 ``` """ ``` 40 1 ``` yield bus ``` 41 1 ``` visited = {bus} ``` 42 1 ``` stack = deque([iter(mg[bus])]) ``` 43 1 ``` while stack: ``` 44 1 ``` for child in stack.pop(): ``` 45 1 ``` if child not in visited: ``` 46 1 ``` yield child ``` 47 1 ``` visited.add(child) ``` 48 1 ``` if child not in notravbuses: ``` 49 1 ``` stack.append(iter(mg[child])) ``` 50 51 52 1 ```def connected_components(mg, notravbuses=set()): ``` 53 ``` """ ``` 54 ``` Clusters all buses in a NetworkX graph that are connected to each other. ``` 55 56 ``` INPUT: ``` 57 ``` **mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network. ``` 58 59 60 ``` OPTIONAL: ``` 61 ``` **notravbuses** (set) - Indices of notravbuses: lines connected to these buses are ``` 62 ``` not being considered in the graph ``` 63 64 ``` OUTPUT: ``` 65 ``` **cc** (generator) - Returns a generator that yields all clusters of buses connected ``` 66 ``` to each other. ``` 67 68 ``` EXAMPLE: ``` 69 ``` import pandapower.topology as top ``` 70 71 ``` mg = top.create_nxgraph(net) ``` 72 73 ``` cc = top.connected_components(net, 5) ``` 74 75 ``` """ ``` 76 77 1 ``` nodes = set(mg.nodes()) - notravbuses ``` 78 1 ``` while nodes: ``` 79 1 ``` cc = set(connected_component(mg, nodes.pop(), notravbuses=notravbuses)) ``` 80 1 ``` yield cc ``` 81 1 ``` nodes -= cc ``` 82 ``` # the above does not work if two notravbuses are directly connected ``` 83 1 ``` if len(notravbuses) > 0: ``` 84 1 ``` for f, t in mg.edges(notravbuses): ``` 85 1 ``` if f in notravbuses and t in notravbuses: ``` 86 0 ``` yield set([f, t]) ``` 87 88 89 1 ```def calc_distance_to_bus(net, bus, respect_switches=True, nogobuses=None, ``` 90 ``` notravbuses=None, weight='weight'): ``` 91 ``` """ ``` 92 ``` Calculates the shortest distance between a source bus and all buses connected to it. ``` 93 94 ``` INPUT: ``` 95 ``` **net** (pandapowerNet) - Variable that contains a pandapower network. ``` 96 97 ``` **bus** (integer) - Index of the source bus. ``` 98 99 100 ``` OPTIONAL: ``` 101 ``` **respect_switches** (boolean, True) - True: open line switches are being considered ``` 102 ``` (no edge between nodes) ``` 103 ``` False: open line switches are being ignored ``` 104 105 ``` **nogobuses** (integer/list, None) - nogobuses are not being considered ``` 106 107 ``` **notravbuses** (integer/list, None) - lines connected to these buses are not being ``` 108 ``` considered ``` 109 ``` **weight** (string, None) – Edge data key corresponding to the edge weight ``` 110 111 ``` OUTPUT: ``` 112 ``` **dist** - Returns a pandas series with containing all distances to the source bus ``` 113 ``` in km. If weight=None dist is the topological distance (int). ``` 114 115 ``` EXAMPLE: ``` 116 ``` import pandapower.topology as top ``` 117 118 ``` dist = top.calc_distance_to_bus(net, 5) ``` 119 120 ``` """ ``` 121 1 ``` g = create_nxgraph(net, respect_switches=respect_switches, ``` 122 ``` nogobuses=nogobuses, notravbuses=notravbuses) ``` 123 1 ``` return pd.Series(nx.single_source_dijkstra_path_length(g, bus, weight=weight)) ``` 124 125 126 1 ```def unsupplied_buses(net, mg=None, slacks=None, respect_switches=True): ``` 127 ``` """ ``` 128 ``` Finds buses, that are not connected to an external grid. ``` 129 130 ``` INPUT: ``` 131 ``` **net** (pandapowerNet) - variable that contains a pandapower network ``` 132 133 ``` OPTIONAL: ``` 134 ``` **mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network. ``` 135 136 ``` **in_service_only** (boolean, False) - Defines whether only in service buses should be ``` 137 ``` included in unsupplied_buses. ``` 138 139 ``` **slacks** (set, None) - buses which are considered as root / slack buses. If None, all ``` 140 ``` existing slack buses are considered. ``` 141 142 ``` **respect_switches** (boolean, True) - Fixes how to consider switches - only in case of no ``` 143 ``` given mg. ``` 144 145 ``` OUTPUT: ``` 146 ``` **ub** (set) - unsupplied buses ``` 147 148 ``` EXAMPLE: ``` 149 ``` import pandapower.topology as top ``` 150 151 ``` top.unsupplied_buses(net) ``` 152 ``` """ ``` 153 154 1 ``` mg = mg or create_nxgraph(net, respect_switches=respect_switches) ``` 155 1 ``` if slacks is None: ``` 156 1 ``` slacks = set(net.ext_grid[net.ext_grid.in_service].bus.values) | set( ``` 157 ``` net.gen[net.gen.in_service & net.gen.slack].bus.values) ``` 158 1 ``` not_supplied = set() ``` 159 1 ``` for cc in nx.connected_components(mg): ``` 160 1 ``` if not set(cc) & slacks: ``` 161 1 ``` not_supplied.update(set(cc)) ``` 162 163 1 ``` return not_supplied ``` 164 165 166 1 ```def find_basic_graph_characteristics(g, roots, characteristics): ``` 167 ``` """ ``` 168 ``` Determines basic characteristics of the given graph like connected buses, stubs, bridges, ``` 169 ``` and articulation points. ``` 170 171 ``` .. note:: ``` 172 173 ``` This is the base function for find_graph_characteristics. Please use the latter ``` 174 ``` function instead! ``` 175 ``` """ ``` 176 1 ``` connected = 'connected' in characteristics ``` 177 1 ``` stub_buses = 'stub_buses' in characteristics ``` 178 1 ``` bridges = {'bridges', 'required_bridges'} & set(characteristics) ``` 179 1 ``` articulation_points = {'articulation_points', 'notn1_areas'} & set(characteristics) ``` 180 1 ``` notn1_starts = 'notn1_areas' in characteristics ``` 181 182 1 ``` char_dict = {'connected': set(), 'stub_buses': set(), 'bridges': set(), ``` 183 ``` 'articulation_points': set(), 'notn1_starts': set()} ``` 184 185 1 ``` discovery = {root: 0 for root in roots} # "time" of first discovery of node during search ``` 186 1 ``` low = {root: 0 for root in roots} ``` 187 1 ``` visited = set(roots) ``` 188 1 ``` path = [] ``` 189 1 ``` stack = [(root, root, iter(g[root])) for root in roots] ``` 190 1 ``` while stack: ``` 191 1 ``` grandparent, parent, children = stack[-1] ``` 192 1 ``` try: ``` 193 1 ``` child = next(children) ``` 194 1 ``` if stub_buses: ``` 195 1 ``` if child not in visited: ``` 196 1 ``` path.append(child) # keep track of movement through the graph ``` 197 1 ``` if grandparent == child: ``` 198 1 ``` continue ``` 199 1 ``` if child in visited: ``` 200 1 ``` if discovery[child] <= discovery[parent]: # back edge ``` 201 1 ``` low[parent] = min(low[parent], discovery[child]) ``` 202 ``` else: ``` 203 1 ``` low[child] = discovery[child] = len(discovery) ``` 204 1 ``` visited.add(child) ``` 205 1 ``` stack.append((parent, child, iter(g[child]))) ``` 206 1 ``` except StopIteration: ``` 207 1 ``` back = stack.pop() ``` 208 1 ``` path.append(back[0]) ``` 209 1 ``` if low[parent] >= discovery[grandparent]: ``` 210 ``` # Articulation points and start of not n-1 safe buses ``` 211 1 ``` if grandparent not in roots: ``` 212 1 ``` if articulation_points: ``` 213 1 ``` char_dict['articulation_points'].add(grandparent) ``` 214 1 ``` if notn1_starts: ``` 215 1 ``` char_dict['notn1_starts'].add(parent) ``` 216 1 ``` if low[parent] > discovery[grandparent]: ``` 217 ``` # Bridges ``` 218 1 ``` if bridges: ``` 219 1 ``` char_dict['bridges'].add((grandparent, parent)) ``` 220 221 ``` # Stub buses ``` 222 1 ``` if stub_buses: ``` 223 1 ``` stub = path.pop() ``` 224 1 ``` if stub != grandparent: ``` 225 0 ``` char_dict['stub_buses'].add(stub) ``` 226 1 ``` while path and path[-1] != grandparent and path[-1] not in roots: ``` 227 1 ``` stub = path.pop() ``` 228 1 ``` char_dict['stub_buses'].add(stub) ``` 229 1 ``` low[grandparent] = min(low[parent], low[grandparent]) ``` 230 231 1 ``` if connected: ``` 232 1 ``` char_dict['connected'] = visited ``` 233 1 ``` return char_dict ``` 234 235 236 1 ```def find_graph_characteristics(g, roots, characteristics): ``` 237 ``` """ ``` 238 ``` Finds and returns different characteristics of the given graph which can be specified. ``` 239 240 ``` INPUT: ``` 241 ``` **g** (NetworkX graph) - Graph of the network ``` 242 243 ``` **roots** (list) - Root buses of the graphsearch ``` 244 245 ``` **characteristics** (list) - List of characteristics this function determines and returns ``` 246 247 ``` .. note:: ``` 248 249 ``` Possible characteristics: ``` 250 251 ``` - 'connected' - All buses which have a connection to at least one of the root buses ``` 252 ``` - 'articulation_points' - Buses which lead to disconnected areas if they get removed ``` 253 ``` - 'bridges' - Edges which lead to disconnected areas if they get removed ``` 254 ``` - 'stub_buses' - Buses which arent't connected if one specific edge gets removed ``` 255 ``` - 'required_bridges' - Bridges which are strictly needed to connect a specific bus ``` 256 ``` - 'notn1_areas' - Areas which aren't connected if one specific bus gets removed ``` 257 258 ``` OUTPUT: ``` 259 260 ``` **char_dict** (dict) - dictionary which contains the wanted characteristics ``` 261 262 ``` ======================= ================================================================ ``` 263 ``` key dict value ``` 264 ``` ======================= ================================================================ ``` 265 ``` 'connected' set of all connected buses ``` 266 ``` 'articulation_points' set of all articulation points ``` 267 ``` 'bridges' set of tuples which represent start and end bus of each bridge ``` 268 ``` 'stub_buses' set of all buses which lie on a stub ``` 269 ``` 'required_bridges' dict of all buses which are connected via at least one bridge. ``` 270 ``` The dict values contain a set of bridges which are needed to ``` 271 ``` connect the key buses ``` 272 ``` 'notn1_areas' dict of not n-1 safe areas. The dict values contain a set of ``` 273 ``` not n-1 safe buses which aren't connected if the key bus gets ``` 274 ``` removed ``` 275 ``` ======================= ================================================================ ``` 276 277 ``` EXAMPLE:: ``` 278 279 ``` import topology as top ``` 280 ``` g = top.create_nxgraph(net, respect_switches=False) ``` 281 ``` char_dict = top.find_graph_characteristics(g, roots=[0, 3], characteristics=['connected', 'stub_buses']) ``` 282 ``` """ ``` 283 1 ``` char_dict = find_basic_graph_characteristics(g, roots, characteristics) ``` 284 285 1 ``` required_bridges = 'required_bridges' in characteristics ``` 286 1 ``` notn1_areas = 'notn1_areas' in characteristics ``` 287 288 1 ``` if not required_bridges and not notn1_areas: ``` 289 1 ``` return {key: char_dict[key] for key in characteristics} ``` 290 291 1 ``` char_dict.update({'required_bridges': dict(), 'notn1_areas': dict()}) ``` 292 293 1 ``` visited = set(roots) ``` 294 1 ``` visited_bridges = [] ``` 295 1 ``` notn1_area_start = None ``` 296 1 ``` curr_notn1_area = [] ``` 297 298 1 ``` stack = [(root, root, iter(g[root])) for root in roots] ``` 299 1 ``` while stack: ``` 300 1 ``` grandparent, parent, children = stack[-1] ``` 301 1 ``` try: ``` 302 1 ``` child = next(children) ``` 303 1 ``` if child == grandparent: ``` 304 1 ``` continue ``` 305 1 ``` if child not in visited: ``` 306 1 ``` visited.add(child) ``` 307 1 ``` stack.append((parent, child, iter(g[child]))) ``` 308 1 ``` if required_bridges and ((parent, child) in char_dict['bridges'] or ``` 309 ``` (child, parent) in char_dict['bridges']): ``` 310 1 ``` visited_bridges.append((parent, child)) ``` 311 312 1 ``` if notn1_areas: ``` 313 1 ``` if child in char_dict['notn1_starts'] and not notn1_area_start: ``` 314 1 ``` notn1_area_start = parent ``` 315 1 ``` if notn1_area_start: ``` 316 1 ``` curr_notn1_area.append(child) ``` 317 318 1 ``` except StopIteration: ``` 319 1 ``` stack.pop() ``` 320 1 ``` if required_bridges: ``` 321 1 ``` if len(visited_bridges) > 0: ``` 322 1 ``` char_dict['required_bridges'][parent] = visited_bridges[:] ``` 323 1 ``` if ((parent, grandparent) in char_dict['bridges'] or ``` 324 ``` (grandparent, parent) in char_dict['bridges']): ``` 325 1 ``` visited_bridges.pop() ``` 326 327 1 ``` if notn1_areas and grandparent == notn1_area_start: ``` 328 1 ``` if grandparent in char_dict["notn1_areas"]: ``` 329 0 ``` char_dict["notn1_areas"][grandparent].update(set(curr_notn1_area[:])) ``` 330 ``` else: ``` 331 1 ``` char_dict["notn1_areas"][grandparent] = set(curr_notn1_area[:]) ``` 332 1 ``` del curr_notn1_area[:] ``` 333 1 ``` notn1_area_start = None ``` 334 335 1 ``` return {key: char_dict[key] for key in characteristics} ``` 336 337 338 1 ```def get_2connected_buses(g, roots): ``` 339 ``` """ ``` 340 ``` Get all buses which have at least two connections to the roots ``` 341 342 ``` INPUT: ``` 343 ``` **g** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network ``` 344 345 ``` **roots** - Roots of the graphsearch ``` 346 ``` """ ``` 347 1 ``` char_dict = find_graph_characteristics(g, roots, characteristics=['connected', 'stub_buses']) ``` 348 1 ``` connected, stub_buses = char_dict['connected'], char_dict['stub_buses'] ``` 349 1 ``` two_connected = connected - stub_buses ``` 350 1 ``` return connected, two_connected ``` 351 352 353 1 ```def determine_stubs(net, roots=None, mg=None, respect_switches=False): ``` 354 ``` """ ``` 355 ``` Finds stubs in a network. Open switches are being ignored. Results are being written in a new ``` 356 ``` column in the bus table ("on_stub") and line table ("is_stub") as True/False value. ``` 357 358 359 ``` INPUT: ``` 360 ``` **net** (pandapowerNet) - Variable that contains a pandapower network. ``` 361 362 ``` OPTIONAL: ``` 363 ``` **roots** (integer/list, None) - indices of buses that should be excluded (by default, the ``` 364 ``` ext_grid buses will be set as roots) ``` 365 366 ``` EXAMPLE: ``` 367 ``` import pandapower.topology as top ``` 368 369 ``` top.determine_stubs(net, roots = [0, 1]) ``` 370 371 372 ``` """ ``` 373 1 ``` if mg is None: ``` 374 1 ``` mg = create_nxgraph(net, respect_switches=respect_switches) ``` 375 ``` # remove buses with degree lower 2 until none left ``` 376 1 ``` if roots is None: ``` 377 1 ``` roots = set(net.ext_grid.bus) ``` 378 ``` # mg.add_edges_from((a, b) for a, b in zip(list(roots)[:-1], list(roots)[1:])) ``` 379 ``` # while True: ``` 380 ``` # dgo = {g for g, d in list(mg.degree().items()) if d < 2} #- roots ``` 381 ``` # if not dgo: ``` 382 ``` # break ``` 383 ``` # mg.remove_nodes_from(dgo) ``` 384 ``` # n1_buses = mg.nodes() ``` 385 1 ``` _, n1_buses = get_2connected_buses(mg, roots) ``` 386 1 ``` net.bus["on_stub"] = True ``` 387 1 ``` net.bus.loc[n1_buses, "on_stub"] = False ``` 388 1 ``` net.line["is_stub"] = ~((net.line.from_bus.isin(n1_buses)) & (net.line.to_bus.isin(n1_buses))) ``` 389 1 ``` stubs = set(net.bus.index) - set(n1_buses) ``` 390 1 ``` return stubs ``` 391 392 393 1 ```def lines_on_path(mg, path): ``` 394 ``` """ ``` 395 ``` Finds all lines that connect a given path of buses. ``` 396 397 ``` INPUT: ``` 398 ``` **mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network. ``` 399 400 ``` **path** (list) - List of connected buses. ``` 401 402 ``` OUTPUT: ``` 403 ``` **lines** (list) - Returns a list of all lines on the path. ``` 404 405 ``` EXAMPLE: ``` 406 ``` import topology as top ``` 407 408 ``` mg = top.create_nxgraph(net) ``` 409 ``` lines = top.lines_on_path(mg, [4, 5, 6]) ``` 410 411 ``` """ ``` 412 413 1 ``` return elements_on_path(mg, path, "line") ``` 414 415 416 1 ```def elements_on_path(mg, path, element="line"): ``` 417 ``` """ ``` 418 ``` Finds all elements that connect a given path of buses. ``` 419 420 ``` INPUT: ``` 421 ``` **mg** (NetworkX graph) - NetworkX Graph or MultiGraph that represents a pandapower network. ``` 422 423 ``` **path** (list) - List of connected buses. ``` 424 425 ``` **element** (string, "l") - element type ``` 426 427 ``` **multi** (boolean, True) - True: Applied on a NetworkX MultiGraph ``` 428 ``` False: Applied on a NetworkX Graph ``` 429 430 ``` OUTPUT: ``` 431 ``` **elements** (list) - Returns a list of all lines on the path. ``` 432 433 ``` EXAMPLE: ``` 434 ``` import topology as top ``` 435 436 ``` mg = top.create_nxgraph(net) ``` 437 ``` elements = top.elements_on_path(mg, [4, 5, 6]) ``` 438 439 ``` """ ``` 440 1 ``` if element not in ["line", "switch", "trafo", "trafo3w"]: ``` 441 1 ``` raise ValueError("Invalid element type %s"%element) ``` 442 1 ``` if isinstance(mg, nx.MultiGraph): ``` 443 1 ``` return [edge[1] for b1, b2 in zip(path, path[1:]) for edge in mg.get_edge_data(b1, b2).keys() ``` 444 ``` if edge[0]==element] ``` 445 ``` else: ``` 446 1 ``` return [mg.get_edge_data(b1, b2)["key"][1] for b1, b2 in zip(path, path[1:]) ``` 447 ``` if mg.get_edge_data(b1, b2)["key"][0]==element] ``` 448 449 450 1 ```def get_end_points_of_continuously_connected_lines(net, lines): ``` 451 1 ``` mg = nx.MultiGraph() ``` 452 1 ``` line_buses = net.line.loc[lines, ["from_bus", "to_bus"]].values ``` 453 1 ``` mg.add_edges_from(line_buses) ``` 454 1 ``` switch_buses = net.switch[["bus", "element"]].values[net.switch.et.values=="b"] ``` 455 1 ``` mg.add_edges_from(switch_buses) ``` 456 457 1 ``` all_buses = set(line_buses.flatten()) ``` 458 1 ``` longest_path = [] ``` 459 1 ``` for b1, b2 in combinations(all_buses, 2): ``` 460 1 ``` try: ``` 461 1 ``` path = nx.shortest_path(mg, b1, b2) ``` 462 1 ``` except nx.NetworkXNoPath: ``` 463 1 ``` raise UserWarning("Lines not continuously connected") ``` 464 1 ``` if len(path) > len(longest_path): ``` 465 1 ``` longest_path = path ``` 466 1 ``` if all_buses - set(longest_path): ``` 467 1 ``` raise UserWarning("Lines have branching points") ``` 468 1 ``` return longest_path[0], longest_path[-1] ```

Read our documentation on viewing source code .