Source code for ding0.grid.mv_grid.tools

"""This file is part of DING0, the DIstribution Network GeneratOr.
DING0 is a tool to generate synthetic medium and low voltage power
distribution grids based on open data.

It is developed in the project open_eGo: https://openegoproject.wordpress.com

DING0 lives at github: https://github.com/openego/ding0/
The documentation is available on RTD: http://ding0.readthedocs.io"""

__copyright__  = "Reiner Lemoine Institut gGmbH"
__license__    = "GNU Affero General Public License Version 3 (AGPL-3.0)"
__url__        = "https://github.com/openego/ding0/blob/master/LICENSE"
__author__     = "nesnoj, gplssm"


from ding0.core.network.stations import LVStationDing0
from ding0.core.network import CableDistributorDing0, GeneratorDing0, LoadDing0
from ding0.tools.geo import calc_geo_centre_point
from ding0.tools import config as cfg_ding0
import logging


logger = logging.getLogger('ding0')


[docs]def set_circuit_breakers(mv_grid, mode='load', debug=False): """ Calculates the optimal position of a circuit breaker at lv stations (if existing) on all routes of mv_grid, adds and connects them to graph. Args ---- mv_grid: MVGridDing0 MV grid instance debug: bool, defaults to False If True, information is printed during process Note ----- According to planning principles of MV grids, a MV ring is run as two strings (half-rings) separated by a circuit breaker which is open at normal operation [#]_, [#]_. Assuming a ring (route which is connected to the root node at either sides), the optimal position of a circuit breaker is defined as the position (virtual cable) between two nodes where the conveyed current is minimal on the route. Instead of the peak current, the peak load is used here (assuming a constant voltage). The circuit breaker will be installed to a LV station, unless none exists in a ring. In this case, a node of arbitrary type is chosen for the location of the switch disconnecter. If a ring is dominated by loads (peak load > peak capacity of generators), only loads are used for determining the location of circuit breaker. If generators are prevailing (peak load < peak capacity of generators), only generator capacities are considered for relocation. The core of this function (calculation of the optimal circuit breaker position) is the same as in ding0.grid.mv_grid.models.Route.calc_circuit_breaker_position but here it is 1. applied to a different data type (NetworkX Graph) and it 2. adds circuit breakers to all rings. The re-location of circuit breakers is necessary because the original position (calculated during routing with method mentioned above) shifts during the connection of satellites and therefore it is no longer valid. References ---------- .. [#] X. Tao, "Automatisierte Grundsatzplanung von Mittelspannungsnetzen", Dissertation, 2006 .. [#] FGH e.V.: "Technischer Bericht 302: Ein Werkzeug zur Optimierung der Störungsbeseitigung für Planung und Betrieb von Mittelspannungsnetzen", Tech. rep., 2008 """ def relocate_circuit_breaker(): """ Moves circuit breaker to different position in ring. Note ----- Branch of circuit breaker should be set to None in advance. So far only useful to relocate all circuit breakers in a grid as the position of the inserted circuit breaker is not checked beforehand. If used for single circuit breakers make sure to insert matching ring and circuit breaker. """ node_cb = ring[position] # check if node is last node of ring if position < len(ring): # check which branch to disconnect by determining load difference # of neighboring nodes diff2 = abs(sum(node_peak_data[0:position+1]) - sum(node_peak_data[position+1:len(node_peak_data)])) if diff2 < diff_min: node2 = ring[position+1] else: node2 = ring[position-1] else: node2 = ring[position-1] circ_breaker.branch = mv_grid.graph.adj[node_cb][node2]['branch'] circ_breaker.branch_nodes = (node_cb, node2) circ_breaker.switch_node = node_cb circ_breaker.branch.circuit_breaker = circ_breaker circ_breaker.geo_data = calc_geo_centre_point(node_cb, node2) # get power factor for loads and generators cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen') # "disconnect" circuit breakers from branches for cb in mv_grid.circuit_breakers(): cb.branch.circuit_breaker = None # iterate over all rings and circuit breakers for ring, circ_breaker in zip(mv_grid.rings_nodes(include_root_node=False), mv_grid.circuit_breakers()): nodes_peak_load = [] nodes_peak_generation = [] # iterate over all nodes of ring for node in ring: # node is LV station -> get peak load and peak generation if isinstance(node, LVStationDing0): nodes_peak_load.append(node.peak_load / cos_phi_load) nodes_peak_generation.append( node.peak_generation / cos_phi_feedin) # node is cable distributor -> get all connected nodes of subtree using graph_nodes_from_subtree() elif isinstance(node, CableDistributorDing0): nodes_subtree = mv_grid.graph_nodes_from_subtree(node) nodes_subtree_peak_load = 0 nodes_subtree_peak_generation = 0 for node_subtree in nodes_subtree: # node is LV station -> get peak load and peak generation if isinstance(node_subtree, LVStationDing0): nodes_subtree_peak_load += node_subtree.peak_load / \ cos_phi_load nodes_subtree_peak_generation += node_subtree.peak_generation / \ cos_phi_feedin # node is LV station -> get peak load and peak generation if isinstance(node_subtree, GeneratorDing0): nodes_subtree_peak_generation += node_subtree.capacity / \ cos_phi_feedin nodes_peak_load.append(nodes_subtree_peak_load) nodes_peak_generation.append(nodes_subtree_peak_generation) else: raise ValueError('Ring node has got invalid type.') if mode == 'load': node_peak_data = nodes_peak_load elif mode == 'generation': node_peak_data = nodes_peak_generation elif mode == 'loadgen': # is ring dominated by load or generation? # (check if there's more load than generation in ring or vice versa) if sum(nodes_peak_load) > sum(nodes_peak_generation): node_peak_data = nodes_peak_load else: node_peak_data = nodes_peak_generation else: raise ValueError('parameter \'mode\' is invalid!') # calc optimal circuit breaker position # Set start value for difference in ring halfs diff_min = 10e9 # if none of the nodes is of the type LVStation, a switch # disconnecter will be installed anyways. if any([isinstance(n, LVStationDing0) for n in ring]): has_lv_station = True else: has_lv_station = False logging.debug("Ring {} does not have a LV station. " "Switch disconnecter is installed at arbitrary " "node.".format(ring)) # check where difference of demand/generation in two half-rings is minimal for ctr in range(len(node_peak_data)): # check if node that owns the switch disconnector is of type # LVStation if isinstance(ring[ctr], LVStationDing0) or not has_lv_station: # split route and calc demand difference route_data_part1 = sum(node_peak_data[0:ctr]) route_data_part2 = sum(node_peak_data[ctr:len(node_peak_data)]) diff = abs(route_data_part1 - route_data_part2) # equality has to be respected, otherwise comparison stops when demand/generation=0 if diff <= diff_min: diff_min = diff position = ctr else: break # relocate circuit breaker relocate_circuit_breaker() if debug: logger.debug('Ring: {}'.format(ring)) logger.debug('Circuit breaker {0} was relocated to edge {1} ' '(position on route={2})'.format( circ_breaker, repr(circ_breaker.branch), position) ) logger.debug('Peak load sum: {}'.format(sum(nodes_peak_load))) logger.debug('Peak loads: {}'.format(nodes_peak_load))