Source code for hynet.reduction.large_scale.topology

"""
Topology-based subgrid selection and reduction.
"""

import logging

import numpy as np

import hynet
from hynet.utilities.base import Timer

from hynet.reduction.large_scale.subgrid import reduce_subgrid
from hynet.reduction.large_scale.utilities import (add_parallel_branch_info,
                                                   remove_parallel_branch_info,
                                                   add_adjacent_bus_info,
                                                   remove_adjacent_bus_info)

_log = logging.getLogger(__name__)


[docs]def reduce_single_buses(scenario): """ Reduce single buses within the scenario. This function reduces buses that only exhibit one adjacent bus, see Section IV-B in [1]_. This function may be called repeatedly to reduce "lines of buses" as considered in [1]_. Parameters ---------- scenario : Scenario Scenario that shall be processed. Returns ------- int Number of buses that were reduced. References ---------- .. [1] J. Sistermanns, M. Hotz, D. Hewes, R. Witzmann, and W. Utschick, "Feature- and Structure-Preserving Network Reduction for Large-Scale Transmission Grids," 13th IEEE PES PowerTech Conference, Milano, Italy, Jun. 2019. """ timer = Timer() num_buses_before = scenario.num_buses add_adjacent_bus_info(scenario) single_buses = scenario.bus.index[scenario.bus['num_adjacent'] == 1] remove_adjacent_bus_info(scenario) for bus_id in single_buses: adjacent = np.union1d( scenario.branch.loc[scenario.branch['dst'] == bus_id, 'src'].to_numpy(), scenario.branch.loc[scenario.branch['src'] == bus_id, 'dst'].to_numpy() ) if not adjacent.size: # There was a subgrid comprising only two buses, where one of them # was already reduced. We skip the second one... continue elif adjacent.size != 1: raise RuntimeError("The candidate's adjacent bus is not unique.") reduce_subgrid(scenario, representative_bus=adjacent[0], subgrid=[bus_id]) _log.info("Reduction of single buses ~ Reduced {:d} buses ({:.3f} sec.)" .format(num_buses_before - scenario.num_buses, timer.total())) return num_buses_before - scenario.num_buses
[docs]def identify_islands(scenario, max_island_size): """ Identify "islands" at the boundary of the grid. In the context of the topology-based network reduction, "islands" are clusters of buses that are connected to the main grid via a single corridor. This function identifies these "islands" by testing every corridor, i.e., the branches in the corridor are removed, islanding of a subgrid is detected and, if its size does not exceed the defined maximum island size, it is considered an "island". Parameters ---------- scenario : Scenario Scenario that shall be examined. max_island_size : int Maximum size of an "island" in terms of its number of buses. Returns ------- island_info : list[tuple(.hynet_id_, pandas.Index[.hynet_id_])] Information on the "islands" within the system. The returned list contains an item for every island, which is a tuple comprising the island's representative bus (i.e., the bus of the main grid to which the island is connected) and a pandas index with the bus ID of all buses of the island. """ timer = Timer() island_info = [] if max_island_size < 1: return island_info _log.info("Identification of islands ~ Starting (max. 'island' size {:d})" .format(max_island_size)) if len(scenario.get_islands()) != 1: raise ValueError("This function only supports connected grids.") add_parallel_branch_info(scenario) for branch_id in scenario.branch.index: branch = scenario.branch.loc[branch_id] # Identify the branches in this corridor if branch['parallel']: if branch['parallel_main_id'] != branch_id: continue remove_ids = scenario.branch.index[ scenario.branch['parallel_main_id'] == branch_id] else: remove_ids = branch_id # Remove all branches in the corridor, identify the islands, and # restore the original data branch_bkp = scenario.branch scenario.branch = scenario.branch.drop(remove_ids, axis='index') islands = scenario.get_islands() scenario.branch = branch_bkp # Identify the smaller island check if it qualifies for reduction if len(islands) == 1: continue elif len(islands) != 2: raise RuntimeError("Unexpected program state.") if len(islands[0]) < len(islands[1]): island = islands[0] else: island = islands[1] if island.size > max_island_size: continue # Identify the representative bus and initiate the reduction if branch['src'] in island: representative_bus = branch['dst'] elif branch['dst'] in island: representative_bus = branch['src'] else: raise RuntimeError("Unexpected program state.") island_info.append((representative_bus, island)) remove_parallel_branch_info(scenario) _log.info("Identification of islands ~ Identified {:d} islands ({:.3f} sec.)" .format(len(island_info), timer.total())) return island_info
[docs]def reduce_islands(scenario, max_island_size): """ Reduce "islands" within the scenario. This function reduces "islands" at the boundary of the grid, see Section IV-B in [1]_. Parameters ---------- scenario : Scenario Scenario that shall be processed. max_island_size : int Maximum size of an "island" in terms of its number of buses. Returns ------- int Number of buses that were reduced. References ---------- .. [1] J. Sistermanns, M. Hotz, D. Hewes, R. Witzmann, and W. Utschick, "Feature- and Structure-Preserving Network Reduction for Large-Scale Transmission Grids," 13th IEEE PES PowerTech Conference, Milano, Italy, Jun. 2019. """ timer = Timer() num_buses_before = scenario.num_buses island_info = identify_islands(scenario, max_island_size) if not island_info: _log.info("Reduction of islands ~ No islands are present ({:.3f} sec.)" .format(timer.total())) return 0 idx_sort = \ np.argsort([len(island) for representative_bus, island in island_info]) island_info = np.array(island_info, dtype=object)[idx_sort[::-1]] for representative_bus, island in island_info: was_removed = ~island.isin(scenario.bus.index) if np.all(was_removed): # We already reduced a larger island that contained this one... continue if np.any(was_removed): # Due to the sorting, a subgrid must be a subset of a larger one... raise RuntimeError("Unexpected program state.") reduce_subgrid(scenario, representative_bus, subgrid=island) _log.info("Reduction of islands ~ Reduced {:d} buses ({:.3f} sec.)" .format(num_buses_before - scenario.num_buses, timer.total())) return num_buses_before - scenario.num_buses
[docs]def reduce_by_topology(scenario, max_island_size=None): """ Apply a topology-based network reduction to the scenario. This function performs the topology-based network reduction described in Section IV-B in [1]_. Parameters ---------- scenario : Scenario Scenario that shall be processed. max_island_size : int, optional Maximum size of an "island" in terms of its number of buses. By default, it is set to approximately 1% of the total number of buses. Set this parameter to ``0`` to disable the reduction of "islands". Returns ------- int Number of buses that were reduced. References ---------- .. [1] J. Sistermanns, M. Hotz, D. Hewes, R. Witzmann, and W. Utschick, "Feature- and Structure-Preserving Network Reduction for Large-Scale Transmission Grids," 13th IEEE PES PowerTech Conference, Milano, Italy, Jun. 2019. """ timer = Timer() num_buses_before = scenario.num_buses if max_island_size is None: max_island_size = int(np.ceil(0.01 * scenario.num_buses)) _log.info("Topology-based network reduction ~ Starting (max. 'island' " "size {:d})".format(max_island_size)) while reduce_single_buses(scenario) > 0: pass if max_island_size is None or max_island_size > 0: reduce_islands(scenario, max_island_size) _log.info("Topology-based network reduction ~ " "Reduced {:d} buses ({:.3f} sec.)" .format(num_buses_before - scenario.num_buses, timer.total())) return num_buses_before - scenario.num_buses