Source code for edisgo.flex_opt.reinforce_grid

import pandas as pd
import copy
import datetime
from edisgo.flex_opt import check_tech_constraints as checks
from edisgo.flex_opt import reinforce_measures, exceptions
from edisgo.flex_opt.costs import grid_expansion_costs
from edisgo.tools import tools, pypsa_io
from edisgo.grid.tools import assign_mv_feeder_to_nodes
import logging

logger = logging.getLogger('edisgo')


[docs]def reinforce_grid(edisgo, timesteps_pfa=None, copy_graph=False, max_while_iterations=10, combined_analysis=False): """ Evaluates grid reinforcement needs and performs measures. This function is the parent function for all grid reinforcements. Parameters ---------- edisgo : :class:`~.grid.network.EDisGo` The eDisGo API object timesteps_pfa : :obj:`str` or :pandas:`pandas.DatetimeIndex<datetimeindex>` or :pandas:`pandas.Timestamp<timestamp>` timesteps_pfa specifies for which time steps power flow analysis is conducted and therefore which time steps to consider when checking for over-loading and over-voltage issues. It defaults to None in which case all timesteps in timeseries.timeindex (see :class:`~.grid.network.TimeSeries`) are used. Possible options are: * None Time steps in timeseries.timeindex (see :class:`~.grid.network.TimeSeries`) are used. * 'snapshot_analysis' Reinforcement is conducted for two worst-case snapshots. See :meth:`edisgo.tools.tools.select_worstcase_snapshots()` for further explanation on how worst-case snapshots are chosen. Note: If you have large time series choosing this option will save calculation time since power flow analysis is only conducted for two time steps. If your time series already represents the worst-case keep the default value of None because finding the worst-case snapshots takes some time. * :pandas:`pandas.DatetimeIndex<datetimeindex>` or :pandas:`pandas.Timestamp<timestamp>` Use this option to explicitly choose which time steps to consider. copy_graph : :obj:`Boolean` If True reinforcement is conducted on a copied graph and discarded. Default: False. max_while_iterations : :obj:`int` Maximum number of times each while loop is conducted. combined_analysis : :obj:`Boolean` If True allowed voltage deviations for combined analysis of MV and LV grid are used. If False different allowed voltage deviations for MV and LV are used. See also config section `grid_expansion_allowed_voltage_deviations`. Default: False. Returns ------- :class:`~.grid.network.Results` Returns the Results object holding grid expansion costs, equipment changes, etc. Notes ----- See :ref:`features-in-detail` for more information on how grid reinforcement is conducted. """ def _add_lines_changes_to_equipment_changes(): equipment, index, quantity = [], [], [] for line, number_of_lines in lines_changes.items(): equipment.append(line.type.name) index.append(line) quantity.append(number_of_lines) edisgo_reinforce.network.results.equipment_changes = \ edisgo_reinforce.network.results.equipment_changes.append( pd.DataFrame( {'iteration_step': [iteration_step] * len( lines_changes), 'change': ['changed'] * len(lines_changes), 'equipment': equipment, 'quantity': quantity}, index=index)) def _add_transformer_changes_to_equipment_changes(mode): for station, transformer_list in transformer_changes[mode].items(): edisgo_reinforce.network.results.equipment_changes = \ edisgo_reinforce.network.results.equipment_changes.append( pd.DataFrame( {'iteration_step': [iteration_step] * len( transformer_list), 'change': [mode] * len(transformer_list), 'equipment': transformer_list, 'quantity': [1] * len(transformer_list)}, index=[station] * len(transformer_list))) # assign MV feeder to every generator, LV station, load, and branch tee # to assign grid expansion costs to an MV feeder assign_mv_feeder_to_nodes(edisgo.network.mv_grid) # analyze for all time steps (advantage is that load and feed-in case can # be obtained more performant in case `timesteps_pfa` = 'snapshot_analysis' # plus edisgo and edisgo_reinforce will have pypsa representation in case # reinforcement needs to be conducted on a copied graph) edisgo.analyze() # in case reinforcement needs to be conducted on a copied graph the # edisgo object is deep copied if copy_graph is True: edisgo_reinforce = copy.deepcopy(edisgo) else: edisgo_reinforce = edisgo if timesteps_pfa is not None: # if timesteps_pfa = 'snapshot_analysis' get snapshots if (isinstance(timesteps_pfa, str) and timesteps_pfa == 'snapshot_analysis'): snapshots = tools.select_worstcase_snapshots( edisgo_reinforce.network) # drop None values in case any of the two snapshots does not exist timesteps_pfa = pd.DatetimeIndex(data=[ snapshots['load_case'], snapshots['feedin_case']]).dropna() # if timesteps_pfa is not of type datetime or does not contain # datetimes throw an error elif not isinstance(timesteps_pfa, datetime.datetime): if hasattr(timesteps_pfa, '__iter__'): if not all(isinstance(_, datetime.datetime) for _ in timesteps_pfa): raise ValueError( 'Input {} for timesteps_pfa is not valid.'.format( timesteps_pfa)) else: raise ValueError( 'Input {} for timesteps_pfa is not valid.'.format( timesteps_pfa)) iteration_step = 1 edisgo_reinforce.analyze(timesteps=timesteps_pfa) # REINFORCE OVERLOADED TRANSFORMERS AND LINES logger.debug('==> Check station load.') overloaded_mv_station = checks.hv_mv_station_load(edisgo_reinforce.network) overloaded_lv_stations = checks.mv_lv_station_load( edisgo_reinforce.network) logger.debug('==> Check line load.') crit_lines_lv = checks.lv_line_load(edisgo_reinforce.network) crit_lines_mv = checks.mv_line_load(edisgo_reinforce.network) crit_lines = crit_lines_lv.append(crit_lines_mv) while_counter = 0 while ((not overloaded_mv_station.empty or not overloaded_lv_stations.empty or not crit_lines.empty) and while_counter < max_while_iterations): if not overloaded_mv_station.empty: # reinforce substations transformer_changes = \ reinforce_measures.extend_substation_overloading( edisgo_reinforce.network, overloaded_mv_station) # write added and removed transformers to results.equipment_changes _add_transformer_changes_to_equipment_changes('added') _add_transformer_changes_to_equipment_changes('removed') if not overloaded_lv_stations.empty: # reinforce distribution substations transformer_changes = \ reinforce_measures.extend_distribution_substation_overloading( edisgo_reinforce.network, overloaded_lv_stations) # write added and removed transformers to results.equipment_changes _add_transformer_changes_to_equipment_changes('added') _add_transformer_changes_to_equipment_changes('removed') if not crit_lines.empty: # reinforce lines lines_changes = reinforce_measures.reinforce_branches_overloading( edisgo_reinforce.network, crit_lines) # write changed lines to results.equipment_changes _add_lines_changes_to_equipment_changes() # run power flow analysis again (after updating pypsa object) and check # if all over-loading problems were solved logger.debug('==> Run power flow analysis.') pypsa_io.update_pypsa_grid_reinforcement( edisgo_reinforce.network, edisgo_reinforce.network.results.equipment_changes[ edisgo_reinforce.network.results.equipment_changes. iteration_step==iteration_step]) edisgo_reinforce.analyze(timesteps=timesteps_pfa) logger.debug('==> Recheck station load.') overloaded_mv_station = checks.hv_mv_station_load( edisgo_reinforce.network) overloaded_lv_stations = checks.mv_lv_station_load( edisgo_reinforce.network) logger.debug('==> Recheck line load.') crit_lines_lv = checks.lv_line_load(edisgo_reinforce.network) crit_lines_mv = checks.mv_line_load(edisgo_reinforce.network) crit_lines = crit_lines_lv.append(crit_lines_mv) iteration_step += 1 while_counter += 1 # check if all load problems were solved after maximum number of # iterations allowed if (while_counter == max_while_iterations and (not crit_lines.empty or not overloaded_mv_station.empty or not overloaded_lv_stations.empty)): edisgo_reinforce.network.results.unresolved_issues.update(crit_lines) edisgo_reinforce.network.results.unresolved_issues.update( overloaded_lv_stations) edisgo_reinforce.network.results.unresolved_issues.update( overloaded_mv_station) raise exceptions.MaximumIterationError( "Overloading issues for the following lines could not be solved:" "{}".format(crit_lines)) else: logger.info('==> Load issues were solved in {} iteration ' 'step(s).'.format(while_counter)) # REINFORCE BRANCHES DUE TO VOLTAGE ISSUES iteration_step += 1 # solve voltage problems in MV grid logger.debug('==> Check voltage in MV grid.') if combined_analysis: voltage_levels = 'mv_lv' else: voltage_levels = 'mv' crit_nodes = checks.mv_voltage_deviation(edisgo_reinforce.network, voltage_levels=voltage_levels) while_counter = 0 while crit_nodes and while_counter < max_while_iterations: # reinforce lines lines_changes = reinforce_measures.reinforce_branches_overvoltage( edisgo_reinforce.network, edisgo_reinforce.network.mv_grid, crit_nodes[edisgo_reinforce.network.mv_grid]) # write changed lines to results.equipment_changes _add_lines_changes_to_equipment_changes() # run power flow analysis again (after updating pypsa object) and check # if all over-voltage problems were solved logger.debug('==> Run power flow analysis.') pypsa_io.update_pypsa_grid_reinforcement( edisgo_reinforce.network, edisgo_reinforce.network.results.equipment_changes[ edisgo_reinforce.network.results.equipment_changes. iteration_step == iteration_step]) edisgo_reinforce.analyze(timesteps=timesteps_pfa) logger.debug('==> Recheck voltage in MV grid.') crit_nodes = checks.mv_voltage_deviation(edisgo_reinforce.network, voltage_levels=voltage_levels) iteration_step += 1 while_counter += 1 # check if all voltage problems were solved after maximum number of # iterations allowed if while_counter == max_while_iterations and crit_nodes: for k, v in crit_nodes.items(): for node in v.index: edisgo_reinforce.network.results.unresolved_issues.update( {repr(node): v.loc[node, 'v_mag_pu']}) raise exceptions.MaximumIterationError( "Over-voltage issues for the following nodes in MV grid could " "not be solved: {}".format(crit_nodes)) else: logger.info('==> Voltage issues in MV grid were solved in {} ' 'iteration step(s).'.format(while_counter)) # solve voltage problems at secondary side of LV stations logger.debug('==> Check voltage at secondary side of LV stations.') if combined_analysis: voltage_levels = 'mv_lv' else: voltage_levels = 'lv' crit_stations = checks.lv_voltage_deviation(edisgo_reinforce.network, mode='stations', voltage_levels=voltage_levels) while_counter = 0 while crit_stations and while_counter < max_while_iterations: # reinforce distribution substations transformer_changes = \ reinforce_measures.extend_distribution_substation_overvoltage( edisgo_reinforce.network, crit_stations) # write added transformers to results.equipment_changes _add_transformer_changes_to_equipment_changes('added') # run power flow analysis again (after updating pypsa object) and check # if all over-voltage problems were solved logger.debug('==> Run power flow analysis.') pypsa_io.update_pypsa_grid_reinforcement( edisgo_reinforce.network, edisgo_reinforce.network.results.equipment_changes[ edisgo_reinforce.network.results.equipment_changes. iteration_step == iteration_step]) edisgo_reinforce.analyze(timesteps=timesteps_pfa) logger.debug('==> Recheck voltage at secondary side of LV stations.') crit_stations = checks.lv_voltage_deviation( edisgo_reinforce.network, mode='stations', voltage_levels=voltage_levels) iteration_step += 1 while_counter += 1 # check if all voltage problems were solved after maximum number of # iterations allowed if while_counter == max_while_iterations and crit_stations: for k, v in crit_stations.items(): for node in v.index: edisgo_reinforce.network.results.unresolved_issues.update( {repr(node): v.loc[node, 'v_mag_pu']}) raise exceptions.MaximumIterationError( "Over-voltage issues at busbar could not be solved for the " "following LV grids: {}".format(crit_stations)) else: logger.info('==> Voltage issues at busbars in LV grids were solved ' 'in {} iteration step(s).'.format(while_counter)) # solve voltage problems in LV grids logger.debug('==> Check voltage in LV grids.') crit_nodes = checks.lv_voltage_deviation(edisgo_reinforce.network, voltage_levels=voltage_levels) while_counter = 0 while crit_nodes and while_counter < max_while_iterations: # for every grid in crit_nodes do reinforcement for grid in crit_nodes: # reinforce lines lines_changes = reinforce_measures.reinforce_branches_overvoltage( edisgo_reinforce.network, grid, crit_nodes[grid]) # write changed lines to results.equipment_changes _add_lines_changes_to_equipment_changes() # run power flow analysis again (after updating pypsa object) and check # if all over-voltage problems were solved logger.debug('==> Run power flow analysis.') pypsa_io.update_pypsa_grid_reinforcement( edisgo_reinforce.network, edisgo_reinforce.network.results.equipment_changes[ edisgo_reinforce.network.results.equipment_changes. iteration_step == iteration_step]) edisgo_reinforce.analyze(timesteps=timesteps_pfa) logger.debug('==> Recheck voltage in LV grids.') crit_nodes = checks.lv_voltage_deviation(edisgo_reinforce.network, voltage_levels=voltage_levels) iteration_step += 1 while_counter += 1 # check if all voltage problems were solved after maximum number of # iterations allowed if while_counter == max_while_iterations and crit_nodes: for k, v in crit_nodes.items(): for node in v.index: edisgo_reinforce.network.results.unresolved_issues.update( {repr(node): v.loc[node, 'v_mag_pu']}) raise exceptions.MaximumIterationError( "Over-voltage issues for the following nodes in LV grids could " "not be solved: {}".format(crit_nodes)) else: logger.info( '==> Voltage issues in LV grids were solved ' 'in {} iteration step(s).'.format(while_counter)) # RECHECK FOR OVERLOADED TRANSFORMERS AND LINES logger.debug('==> Recheck station load.') overloaded_mv_station = checks.hv_mv_station_load(edisgo_reinforce.network) overloaded_lv_stations = checks.mv_lv_station_load( edisgo_reinforce.network) logger.debug('==> Recheck line load.') crit_lines_lv = checks.lv_line_load(edisgo_reinforce.network) crit_lines_mv = checks.mv_line_load(edisgo_reinforce.network) crit_lines = crit_lines_lv.append(crit_lines_mv) while_counter = 0 while ((not overloaded_mv_station.empty or not overloaded_lv_stations.empty or not crit_lines.empty) and while_counter < max_while_iterations): if not overloaded_mv_station.empty: # reinforce substations transformer_changes = \ reinforce_measures.extend_substation_overloading( edisgo_reinforce.network, overloaded_mv_station) # write added and removed transformers to results.equipment_changes _add_transformer_changes_to_equipment_changes('added') _add_transformer_changes_to_equipment_changes('removed') if not overloaded_lv_stations.empty: # reinforce substations transformer_changes = \ reinforce_measures.extend_distribution_substation_overloading( edisgo_reinforce.network, overloaded_lv_stations) # write added and removed transformers to results.equipment_changes _add_transformer_changes_to_equipment_changes('added') _add_transformer_changes_to_equipment_changes('removed') if not crit_lines.empty: # reinforce lines lines_changes = reinforce_measures.reinforce_branches_overloading( edisgo_reinforce.network, crit_lines) # write changed lines to results.equipment_changes _add_lines_changes_to_equipment_changes() # run power flow analysis again (after updating pypsa object) and check # if all over-loading problems were solved logger.debug('==> Run power flow analysis.') pypsa_io.update_pypsa_grid_reinforcement( edisgo_reinforce.network, edisgo_reinforce.network.results.equipment_changes[ edisgo_reinforce.network.results.equipment_changes. iteration_step == iteration_step]) edisgo_reinforce.analyze(timesteps=timesteps_pfa) logger.debug('==> Recheck station load.') overloaded_mv_station = checks.hv_mv_station_load( edisgo_reinforce.network) overloaded_lv_stations = checks.mv_lv_station_load( edisgo_reinforce.network) logger.debug('==> Recheck line load.') crit_lines_lv = checks.lv_line_load(edisgo_reinforce.network) crit_lines_mv = checks.mv_line_load(edisgo_reinforce.network) crit_lines = crit_lines_lv.append(crit_lines_mv) iteration_step += 1 while_counter += 1 # check if all load problems were solved after maximum number of # iterations allowed if (while_counter == max_while_iterations and (not crit_lines.empty or not overloaded_mv_station.empty or not overloaded_lv_stations.empty)): edisgo_reinforce.network.results.unresolved_issues.update(crit_lines) edisgo_reinforce.network.results.unresolved_issues.update( overloaded_lv_stations) edisgo_reinforce.network.results.unresolved_issues.update( overloaded_mv_station) raise exceptions.MaximumIterationError( "Overloading issues (after solving over-voltage issues) for the" "following lines could not be solved: {}".format(crit_lines)) else: logger.info( '==> Load issues were rechecked and solved ' 'in {} iteration step(s).'.format(while_counter)) # final check 10% criteria checks.check_ten_percent_voltage_deviation(edisgo_reinforce.network) # calculate grid expansion costs edisgo_reinforce.network.results.grid_expansion_costs = \ grid_expansion_costs(edisgo_reinforce.network) return edisgo_reinforce.network.results