Source code for powergrid_synth.core.input_extractor

r"""
This module extracts the topology parameters from realistic grids topology, acting as inputs for generate synthetic grids topology, namely

- node degrees per voltage level 
- node diameter per voltage level
- transformer degrees between different-voltage levels
"""

import networkx as nx
from typing import Dict, List, Tuple

[docs] def extract_topology_params_from_graph(G: nx.Graph) -> Dict: r""" Extract CLC model inputs from an existing power grid graph. This is "operation mode I", where the generator is configured to mimic an existing reference grid. The function extracts per-level degree sequences and diameters (Phase 1 inputs), as well as pairwise transformer degree sequences (Phase 2 inputs). Parameters ---------- G : networkx.Graph Power grid graph with a ``'voltage_level'`` attribute on every node. Returns ------- dict ``'degrees_by_level'`` : list of list of int Intra-level degree sequences (one per voltage level, ordered by ascending voltage label). ``'diameters_by_level'`` : list of int Diameter of the largest connected component of each same-voltage subgraph. ``'transformer_degrees'`` : dict ``{(i, j): (deg_i_to_j, deg_j_to_i)}`` for each pair of voltage levels that has at least one transformer edge. """ print("Extracting topology parameters...") # Get sorted unique voltage levels present in the graph # We use a set comprehension to handle missing attributes gracefully if needed levels = sorted(list(set(d.get('voltage_level') for n, d in G.nodes(data=True) if 'voltage_level' in d))) degrees_by_level = [] diameters_by_level = [] transformer_degrees = {} # 1. Per Level Stats for lvl in levels: nodes = [n for n, d in G.nodes(data=True) if d.get('voltage_level') == lvl] subG = G.subgraph(nodes) # Degree sequence (within the level) degs = [d for n, d in subG.degree()] degrees_by_level.append(degs) # Diameter (LCC) if len(nodes) > 0: if nx.is_connected(subG): diam = nx.diameter(subG) else: largest_cc = max(nx.connected_components(subG), key=len) diam = nx.diameter(subG.subgraph(largest_cc)) else: diam = 0 diameters_by_level.append(diam) # 2. Transformer Stats (Inter-level connections) # We iterate by index to map to the 'degrees_by_level' indices (0, 1, 2...) # This assumes the generator expects level indices 0..K-1 for i in range(len(levels)): for j in range(i + 1, len(levels)): lvl_i = levels[i] lvl_j = levels[j] nodes_i = [n for n, d in G.nodes(data=True) if d.get('voltage_level') == lvl_i] nodes_j = [n for n, d in G.nodes(data=True) if d.get('voltage_level') == lvl_j] if not nodes_i or not nodes_j: continue # Degrees in Level i towards Level j deg_i_to_j = [] for n in nodes_i: count = sum(1 for neighbor in G.neighbors(n) if neighbor in nodes_j) deg_i_to_j.append(count) # Degrees in Level j towards Level i deg_j_to_i = [] for n in nodes_j: count = sum(1 for neighbor in G.neighbors(n) if neighbor in nodes_i) deg_j_to_i.append(count) # Only record if there are actual connections if sum(deg_i_to_j) > 0: transformer_degrees[(i, j)] = (deg_i_to_j, deg_j_to_i) return { 'degrees_by_level': degrees_by_level, 'diameters_by_level': diameters_by_level, 'transformer_degrees': transformer_degrees }