Source code for etrago.execute.sclopf

# -*- coding: utf-8 -*-
# Copyright 2016-2023  Flensburg University of Applied Sciences,
# Europa-Universität Flensburg,
# Centre for Sustainable Energy Systems,
# DLR-Institute for Networked Energy Systems
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# File description
"""
sclopf.py defines functions for contingency analysis. 
"""
import os

import numpy as np
import pandas as pd
import time
import datetime
import logging

logger = logging.getLogger(__name__)

from pypsa.opt import l_constraint
from pypsa.opf import (
    define_passive_branch_flows_with_kirchhoff,
    network_lopf_solve,
    define_passive_branch_flows,
    network_lopf_build_model,
    network_lopf_prepare_solver,
)
from pypsa.pf import sub_network_lpf
import multiprocessing as mp
import csv

from etrago.execute import (
    import_gen_from_links,
    update_electrical_parameters,
)

from etrago.tools.constraints import Constraints


logger = logging.getLogger(__name__)

__copyright__ = (
    "Flensburg University of Applied Sciences, "
    "Europa-Universität Flensburg, "
    "Centre for Sustainable Energy Systems, "
    "DLR-Institute for Networked Energy Systems"
)
__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)"
__author__ = "ClaraBuettner"


[docs] def post_contingency_analysis_lopf(etrago, branch_outages, n_process=4): network = etrago.network.copy() import_gen_from_links(network, drop_small_capacities=False) # Drop not-AC subnetwork from network n = network.copy() main_subnet = str(network.buses.sub_network.value_counts().argmax()) n.mremove( "Bus", n.buses[ ~n.buses.index.isin( network.sub_networks["obj"][main_subnet].buses().index ) ].index, ) for one_port in n.iterate_components( ["Load", "Generator", "Store", "StorageUnit"] ): n.mremove( one_port.name, one_port.df[~one_port.df.bus.isin(n.buses.index)].index, ) for two_port in n.iterate_components(["Line", "Link", "Transformer"]): n.mremove( two_port.name, two_port.df[~two_port.df.bus0.isin(n.buses.index)].index, ) n.mremove( two_port.name, two_port.df[~two_port.df.bus1.isin(n.buses.index)].index, ) n.lines.s_nom = n.lines.s_nom_opt.copy() b_x = 1.0 / n.lines.x_pu # Consider outage of each line branch_outages = n.lines.index # Copy result from LOPF n.generators_t.p_set = n.generators_t.p_set.reindex( columns=n.generators.index ) n.generators_t.p_set = n.generators_t.p n.storage_units_t.p_set = n.storage_units_t.p_set.reindex( columns=n.storage_units.index ) n.storage_units_t.p_set = n.storage_units_t.p n.links_t.p_set = n.links_t.p_set.reindex(columns=n.links.index) n.links_t.p_set = n.links_t.p0 snapshots_set = {} length = int(n.snapshots.size / n_process) for i in range(n_process): snapshots_set[str(i + 1)] = n.snapshots[i * length : (i + 1) * length] manager = mp.Manager() d = manager.dict() snapshots_set[str(n_process)] = n.snapshots[i * length :] def multi_con(n, snapshots, d): for sn in snapshots: # Check no lines are overloaded with the linear contingency analysis # rows: branch outage, index = monitorred line p0_test = n.lpf_contingency( branch_outages=branch_outages, snapshots=sn ) # check loading as per unit of s_nom in each contingency # columns: branch_outages load = abs( p0_test.divide(n.passive_branches().s_nom_opt, axis=0) ).drop(["base"], axis=1) # schon im base_case leitungen teilweise überlastet. Liegt das an x Anpassung? overload = load - 1 # columns: branch_outages overload[overload < 0] = 0 if not len(overload) == 0: d[sn] = overload processes = [ mp.Process(target=multi_con, args=(n, snapshots_set[i], d)) for i in snapshots_set ] # Run processes for p in processes: p.start() # Exit the completed processes for p in processes: p.join() for p in processes: p.terminate() if etrago.args["csv_export"] != False: if not os.path.exists( etrago.args["csv_export"] + "/post_contingency_analysis/" ): os.mkdir(etrago.args["csv_export"] + "/post_contingency_analysis/") for s in d.keys(): d[s].to_csv( etrago.args["csv_export"] + "/post_contingency_analysis/" + str(s) + ".csv" ) return d
[docs] def post_contingency_analysis(network, branch_outages, delta=0.05): import_gen_from_links(network, drop_small_capacities=False) # Drop not-AC subnetwork from network n = network.copy() main_subnet = str(network.buses.sub_network.value_counts().argmax()) n.mremove( "Bus", n.buses[ ~n.buses.index.isin( network.sub_networks["obj"][main_subnet].buses().index ) ].index, ) for one_port in n.iterate_components( ["Load", "Generator", "Store", "StorageUnit"] ): n.mremove( one_port.name, one_port.df[~one_port.df.bus.isin(n.buses.index)].index, ) for two_port in n.iterate_components(["Line", "Link", "Transformer"]): n.mremove( two_port.name, two_port.df[~two_port.df.bus0.isin(n.buses.index)].index, ) n.mremove( two_port.name, two_port.df[~two_port.df.bus1.isin(n.buses.index)].index, ) n.lines.s_nom = n.lines.s_nom_opt.copy() b_x = 1.0 / n.lines.x_pu if np.isnan(b_x).any(): import pdb pdb.set_trace() branch_outages = ( n.lines.index ) # branch_outages[branch_outages.isin(n.lines.index)] n.generators_t.p_set = n.generators_t.p_set.reindex( columns=n.generators.index ) n.generators_t.p_set = n.generators_t.p n.storage_units_t.p_set = n.storage_units_t.p_set.reindex( columns=n.storage_units.index ) n.storage_units_t.p_set = n.storage_units_t.p n.links_t.p_set = n.links_t.p_set.reindex(columns=n.links.index) n.links_t.p_set = n.links_t.p0 d = {} for sn in network.snapshots: p0_test = n.lpf_contingency( branch_outages=branch_outages, snapshots=sn ) # rows: branch outage, index = monitorred line # check loading as per unit of s_nom in each contingency load_signed = ( p0_test.divide(n.passive_branches().s_nom_opt, axis=0) ).drop(["base"], axis=1) load = abs( p0_test.divide(n.passive_branches().s_nom_opt, axis=0) ).drop( ["base"], axis=1 ) # columns: branch_outages load_per_outage_over = load_signed.transpose()[ load_signed.abs().max() > (1 + delta) ].transpose() out = load_per_outage_over.columns.values mon = load_per_outage_over.abs().idxmax().values sign = [] for i in range(len(out)): sign.append( np.sign(load_per_outage_over[out[i]][mon[i]]).astype(int) ) combinations = [out, mon, sign] # else: # overloaded = (load>(1 + delta))# columns: branch_outages # array_mon = [] # array_out = [] # array_sign = [] # for col in overloaded: # mon = overloaded.index[overloaded[col]].tolist() # out = [col]*len(mon) # sign = np.sign(load_signed[overloaded[col]][col]).values # if mon != []: # array_mon.extend(mon) # array_out.extend(out) # array_sign.extend(sign) # combinations = [array_out, array_mon, array_sign] if not len(combinations[0]) == 0: d[sn] = combinations return d
[docs] def network_lpf_contingency_subnetwork( network, snapshot=None, branch_outages=None ): """ Computes linear power flow for a selection of branch outages within the subnetwork with the highest number of buses Parameters ---------- snapshots : list-like|single snapshot A subset or an elements of network.snapshots on which to run the power flow, defaults to network.snapshots NB: currently this only works for a single snapshot branch_outages : list-like A list of passive branches which are to be tested for outages. If None, it's take as all network.passive_branches_i() Returns ------- p0 : pandas.DataFrame num_passive_branch x num_branch_outages DataFrame of new power flows """ main_subnet = str(network.buses.sub_network.value_counts().argmax()) sn = network.sub_networks.obj[main_subnet] sub_network_lpf(sn, snapshot) # Store the flows from the base case passive_branches = sn.branches() if branch_outages is None: branch_outages = passive_branches.index p0_base = pd.concat( { c: network.pnl(c).p0.loc[snapshot] for c in network.passive_branch_components } ) p0 = p0_base.to_frame("base") sn._branches = sn.branches() sn.calculate_BODF() if not isinstance(branch_outages[0], tuple): logger.warning( f"No type given for {branch_outages}, assuming it is a line" ) for branch in branch_outages: if not isinstance(branch, tuple): branch = ("Line", branch) sn = network.sub_networks.obj[passive_branches.sub_network[branch]] branch_i = sn._branches.index.get_loc(branch) p0_new = p0_base + pd.Series( sn.BODF[:, branch_i] * p0_base[branch], sn._branches.index ) p0[branch] = p0_new return p0
[docs] def post_contingency_analysis_per_line( network, branch_outages, n_process=4, delta=0.01 ): x = time.time() # import_gen_from_links(network, drop_small_capacities=False) # # Drop not-AC subnetwork from network n = network.copy() # main_subnet = str(network.buses.sub_network.value_counts().argmax()) # Import other sectors into main subnetwork from_AC = n.buses.carrier[n.links.bus0] == "AC" to_AC = n.buses.carrier[n.links.bus1] == "AC" links_from_ac = n.links[(from_AC.values) & (n.links.carrier != "DC")] links_to_ac = n.links[(to_AC.values) & (n.links.carrier != "DC")] links_from_ac.drop( links_from_ac[links_from_ac.index.isin(links_to_ac.index)].index, inplace=True, ) ts_from = n.links_t.p0[links_from_ac.index] ts_from.columns = "link_from_" + ts_from.columns ts_to = n.links_t.p1[links_to_ac.index] ts_to.columns = "link_to_" + ts_to.columns n.madd( "Load", "link_from_" + links_from_ac.index.values, bus=links_from_ac.bus0.values, p_set=ts_from, ) n.madd( "Load", "link_to_" + links_to_ac.index.values, bus=links_to_ac.bus0.values, p_set=ts_to, ) buses_to_drop = ( links_from_ac.bus1.values.tolist() + links_to_ac.bus0.values.tolist() + n.buses[ n.buses.carrier.str.contains("heat_store") ].index.values.tolist() ) for one_port in n.iterate_components( ["Load", "Generator", "Store", "StorageUnit"] ): n.mremove( one_port.name, one_port.df[one_port.df.bus.isin(buses_to_drop)].index, ) for two_port in n.iterate_components(["Link", "Transformer"]): n.mremove( two_port.name, two_port.df[two_port.df.bus0.isin(buses_to_drop)].index, ) n.mremove( two_port.name, two_port.df[two_port.df.bus1.isin(buses_to_drop)].index, ) n.mremove("Bus", buses_to_drop) n.lines.s_nom = n.lines.s_nom_opt.copy() b_x = 1.0 / n.lines.x_pu if np.isnan(b_x).any(): import pdb pdb.set_trace() n.generators_t.p_set = n.generators_t.p_set.reindex( columns=n.generators.index ) n.generators_t.p_set = n.generators_t.p n.storage_units_t.p_set = n.storage_units_t.p_set.reindex( columns=n.storage_units.index ) n.storage_units_t.p_set = n.storage_units_t.p n.links_t.p_set = n.links_t.p_set.reindex(columns=n.links.index) n.links_t.p_set = n.links_t.p0 snapshots_set = {} length = int(n.snapshots.size / n_process) for i in range(n_process): snapshots_set[str(i + 1)] = n.snapshots[i * length : (i + 1) * length] snapshots_set[str(n_process)] = n.snapshots[i * length :] manager = mp.Manager() d = manager.dict() def multi_con(n, snapshots, d): for sn in snapshots: # Check no lines are overloaded with the linear contingency analysis p0_test = network_lpf_contingency_subnetwork( network=n, branch_outages=branch_outages, snapshot=sn ) # rows: branch outage, index = monitorred line # check loading as per unit of s_nom in each contingency load_signed = ( p0_test.divide(n.passive_branches().s_nom_opt, axis=0) ).drop(["base"], axis=1) load = abs( p0_test.divide(n.passive_branches().s_nom_opt, axis=0) ).drop( ["base"], axis=1 ) # columns: branch_outages if True: load_per_outage_over = load_signed.transpose()[ load_signed.abs().max() > (1 + delta) ].transpose() out = load_per_outage_over.columns.values mon = load_per_outage_over.abs().idxmax().values sign = [] for i in range(len(out)): sign.append( np.sign(load_per_outage_over[out[i]][mon[i]]).astype( int ) ) combinations = [out, mon, sign] else: overloaded = load > (1 + delta) # columns: branch_outages array_mon = [] array_out = [] array_sign = [] for col in overloaded: mon = overloaded.index[overloaded[col]].tolist() out = [col] * len(mon) sign = np.sign(load_signed[overloaded[col]][col]).values if mon != []: array_mon.extend(mon) array_out.extend(out) array_sign.extend(sign) combinations = [array_out, array_mon, array_sign] if not len(combinations[0]) == 0: d[sn] = combinations processes = [ mp.Process(target=multi_con, args=(n, snapshots_set[i], d)) for i in snapshots_set ] for p in processes: p.start() for p in processes: p.join() for p in processes: p.terminate() y = (time.time() - x) / 60 logger.info( "Post contingengy check finished in " + str(round(y, 2)) + " minutes." ) return d
[docs] def iterate_lopf_calc(network, args, l_snom_pre, t_snom_pre): """ Function that runs iterations of lopf without building new models. Currently only working with model_formulation = 'kirchhoff' Parameters ---------- network : :class:`pypsa.Network Overall container of PyPSA l_snom_pre: pandas.Series s_nom of ac-lines in previous iteration t_snom_pre: pandas.Series s_nom of transformers in previous iteration """ # Delete flow constraints for each possible model formulation x = time.time() network.model.del_component("cycle_constraints") network.model.del_component("cycle_constraints_index") network.model.del_component("cycle_constraints_index_0") network.model.del_component("cycle_constraints_index_1") if args["model_formulation"] == "kirchhoff": define_passive_branch_flows_with_kirchhoff( network, network.snapshots, skip_vars=True ) else: logger.error("Currently only implemented for kirchhoff-formulation.") y = time.time() logger.info("Flow constraints updated in [min] " + str((y - x) / 60)) network_lopf_solve( network, network.snapshots, formulation=args["model_formulation"], free_memory={"pypsa"}, solver_options=args["solver_options"], ) return network
[docs] def add_all_contingency_constraints(network, combinations, track_time): x = time.time() branch_outage_keys = [] contingency_flow = {} n_buses = 0 # choose biggest sub_network if len(network.sub_networks.obj.index) > 1: for s in network.sub_networks.obj.index: n = len(network.sub_networks.obj[s].buses()) if n > n_buses: n_buses = n sub = network.sub_networks.obj[s] else: sub = network.sub_networks.obj[0] sub._branches = sub.branches() sub.calculate_BODF() bodf = pd.DataFrame( columns=sub.branches().index.get_level_values(1), index=sub.branches().index.get_level_values(1), data=sub.BODF, ) sub._branches["_i"] = range(sub._branches.shape[0]) sub._extendable_branches = sub._branches[sub._branches.s_nom_extendable] sub._fixed_branches = sub._branches[~sub._branches.s_nom_extendable] # set dict with s_nom containing fixed or extendable s_nom s_nom = sub._branches.s_nom.to_dict() for idx in sub._extendable_branches.index.values: s_nom[idx] = network.model.passive_branch_s_nom[idx] BODF_FACTOR = 1 # avoid numerical trouble caused by small BODFs for sn in combinations.keys(): if len(combinations[sn][0]) > 0: out = combinations[sn][0] mon = combinations[sn][1] sign = combinations[sn][2] branch_outage_keys.extend( [ (out[i][0], out[i][1], mon[i][0], mon[i][1], sn) for i in range(len(out)) ] ) # avoid duplicate values in branch_outage_keys branch_outage_keys = list(set(branch_outage_keys)) # set contingengy flow constraint if sub._fixed_branches.empty: contingency_flow.update( { (out[i][0], out[i][1], mon[i][0], mon[i][1], sn): [ [ ( BODF_FACTOR * sign[i], network.model.passive_branch_p[mon[i], sn], ), ( BODF_FACTOR * sign[i] * bodf[out[i][1]][mon[i][1]], network.model.passive_branch_p[out[i], sn], ), (-1 * BODF_FACTOR * network.lines_t.s_max_pu.loc[sn, mon[i][1]], s_nom[mon[i]]), ], "<=", 0, ] for i in range(len(mon)) } ) elif sub._extendable_branches.empty: contingency_flow.update( { (out[i][0], out[i][1], mon[i][0], mon[i][1], sn): [ [ ( sign[i], network.model.passive_branch_p[mon[i], sn], ), ( sign[i] * bodf[out[i][1]][mon[i][1]], network.model.passive_branch_p[out[i], sn], ), ], "<=", s_nom[mon[i]], ] for i in range(len(mon)) } ) else: print("Not implemented!") z = time.time() track_time[datetime.datetime.now()] = "Contingency constraints calculated" logger.info( "Security constraints calculated in [min] " + str((z - x) / 60) ) # remove constraints from previous iteration network.model.del_component("contingency_flow") network.model.del_component("contingency_flow_index") # Delete rows with small BODFs to avoid nummerical problems for c in list(contingency_flow.keys()): if ( (abs(contingency_flow[c][0][1][0]) < 1e-8 * BODF_FACTOR) & (abs(contingency_flow[c][0][1][0]) != 0) ) | (abs(contingency_flow[c][0][1][0]) > 1.1 * BODF_FACTOR): contingency_flow.pop(c) # set constraints for new iteration l_constraint( network.model, "contingency_flow", contingency_flow, # branch_outage_keys) contingency_flow.keys(), ) y = time.time() logger.info("Security constraints updated in [min] " + str((y - x) / 60)) return len(contingency_flow)
[docs] def split_extended_lines(network, percent): split_extended_lines.counter += 1 expansion_rel = network.lines.s_nom_opt / network.lines.s_nom num_lines = expansion_rel[expansion_rel > percent] expanded_lines = network.lines[network.lines.index.isin(num_lines.index)] new_lines = pd.DataFrame(columns=network.lines.columns) for line in num_lines.index: data = expanded_lines[expanded_lines.index == line] n = 0 while num_lines[line] > 0: if num_lines[line] < 1: factor = num_lines[line] else: factor = 1 data_new = data.copy() for col in ["x", "r", "x_pu"]: data_new[col] = ( data_new[col] / factor * data.s_nom_opt / data.s_nom ) data_new.s_nom_opt = data_new.s_nom * factor if n == 0: new_lines = pd.concat([new_lines, data_new]) else: data_new.s_nom_min = 0 new_lines = pd.concat( [ new_lines, data_new.rename( index={ line: str(line) + "_" + str(np.ceil(num_lines[line]).astype(int)) + "iter_" + str(split_extended_lines.counter) } ), ], ) num_lines[line] = num_lines[line] - factor n = n + 1 network.lines = network.lines.drop( new_lines.index, axis="index", errors="ignore" ) # new_lines.index += network.lines.index.astype(int).max() + 1 network.import_components_from_dataframe(new_lines, "Line") l_snom_pre = network.lines.s_nom_opt.copy() l_snom_pre[l_snom_pre == 0] = network.lines.s_nom[l_snom_pre == 0] t_snom_pre = network.transformers.s_nom_opt.copy() return l_snom_pre, t_snom_pre
[docs] def calc_new_sc_combinations(combinations, new): for sn in new.keys(): com = [[], [], []] com[0] = combinations[sn][0] + list(new[sn][0]) com[1] = combinations[sn][1] + list(new[sn][1]) com[2] = combinations[sn][2] + new[sn][2] df = pd.DataFrame([com[0], com[1], com[2]]) data = df.transpose().drop_duplicates().transpose() combinations[sn] = data.values.tolist() return combinations
[docs] def split_parallel_lines(network): print("Splitting parallel lines...") parallel_lines = network.lines[network.lines.num_parallel > 1] s_max_pu = network.lines_t.s_max_pu[ parallel_lines[ parallel_lines.index.isin(network.lines_t.s_max_pu.columns)].index] new_lines = pd.DataFrame(columns=network.lines.columns) new_lines_t = pd.DataFrame(index=network.snapshots) for i in parallel_lines.index: data_new = parallel_lines[parallel_lines.index == i] for col in ["b", "g", "s_nom", "s_nom_min", "s_nom_max", "s_nom_opt"]: data_new[col] = data_new[col] / data_new.num_parallel for col in ["x", "r"]: data_new[col] = data_new[col] * data_new.num_parallel data_new.cables = 3 data_new.num_parallel = 1 num = parallel_lines.num_parallel[i] for n in range(int(num)): data_new.index = [str(i) + "_" + str(int(n + 1))] new_lines = pd.concat( [ new_lines, data_new, ], ) if i in s_max_pu.columns: new_lines_t.loc[:, data_new.index] = s_max_pu[i] network.mremove("Line", parallel_lines.index) network.import_components_from_dataframe(new_lines, "Line") if not new_lines_t.empty: network.import_series_from_dataframe(new_lines_t, "Line", "s_max_pu") for i in network.lines.index[ ~network.lines.index.isin(network.lines_t.s_max_pu.columns)]: network.lines_t.s_max_pu[i] = network.lines.s_max_pu[i] return network
[docs] def plot_sc_lines(out, mon, network): line_w = pd.Series(index=network.lines.index, data=1) line_w[out] = 5 line_w[mon] = 5 line_colors = pd.Series(index=network.lines.index, data="grey") line_colors[out] = "red" line_colors[mon] = "blue" network.plot( link_widths=0, bus_sizes=0.001, line_widths=line_w, line_colors=line_colors, )
[docs] def iterate_sclopf( etrago, n_process=4, delta=0.01, n_overload=0, post_lopf=False, div_ext_lines=False, ): if etrago.args["method"]["formulation"] != "pyomo": etrago.args["method"]["formulation"] = "pyomo" logger.info(""" SCLOPF currently only implemented for pyomo. Setting etrago.args["method"]["formulation"] = 'pyomo' """) network = etrago.network network = split_parallel_lines(network) network.lines.s_max_pu = pd.Series(index=network.lines.index, data=1.0) args = etrago.args track_time = pd.Series() l_snom_pre = network.lines.s_nom.copy() t_snom_pre = network.transformers.s_nom.copy() add_all_contingency_constraints.counter = 0 n = 0 track_time[datetime.datetime.now()] = "Iterative SCLOPF started" x = time.time() # results_to_csv.counter=0 split_extended_lines.counter = 0 # 1. LOPF without SC solver_options_lopf = args["solver_options"] solver_options_lopf["FeasibilityTol"] = 1e-5 solver_options_lopf["BarConvTol"] = 1e-6 # If LOPF was performed beforehand, this can be used as the starting # point for the SCLOPF if post_lopf: if div_ext_lines: l_snom_pre, t_snom_pre = split_extended_lines(network, percent=1.5) # branch_outages=network.lines[network.lines.country == "DE"].index network_lopf_build_model(network, formulation="kirchhoff") network_lopf_prepare_solver(network, solver_name="gurobi") network.lines.s_nom = network.lines.s_nom_opt.copy() network.links.p_nom = network.links.p_nom_opt.copy() network.lines.s_nom_extendable = False network.links.p_nom_extendable = False network.storage_units.p_nom = network.storage_units.p_nom_opt.copy() network.storage_units.p_nom_extendable = False if div_ext_lines: network_lopf_build_model(network, formulation="kirchhoff") network_lopf_prepare_solver(network, solver_name="gurobi") path_name = "/post_sclopf_iteration_" # Run first iteration without any contingency constraint else: network.lopf( network.snapshots, solver_name=args["solver"], solver_options=solver_options_lopf, extra_functionality=Constraints(etrago.args, False).functionality, formulation=args["model_formulation"], pyomo=True, ) path_name = "/sclopf_iteration_" track_time[datetime.datetime.now()] = "Solve SCLOPF" # Update electrical parameters if network is extendable if network.lines.s_nom_extendable.any(): l_snom_pre, t_snom_pre = update_electrical_parameters( network, l_snom_pre, t_snom_pre ) track_time[datetime.datetime.now()] = "Adjust impedances" if not post_lopf: if div_ext_lines: l_snom_pre, t_snom_pre = split_extended_lines( network, percent=1.5 ) # branch_outages=network.lines[network.lines.country == "DE"].index network_lopf_build_model(network, formulation="kirchhoff") # Add extra_functionalities depending on args Constraints(etrago.args, False).functionality( etrago.network, etrago.network.snapshots ) network_lopf_prepare_solver(network, solver_name="gurobi") # Calculate security constraints nb = 0 main_subnet = str(network.buses.sub_network.value_counts().argmax()) branch_outages = network.lines[ network.lines.sub_network == main_subnet ].index new = post_contingency_analysis_per_line( network, branch_outages, n_process=n_process, delta=delta ) track_time[datetime.datetime.now()] = "Overall post contingency analysis" # Initalzie dict of security constraints combinations = dict.fromkeys(network.snapshots, [[], [], []]) size = 0 for i in range(len(new.keys())): size = ( size + len(new.values()[i][0]) * network.snapshot_weightings.generators[new.keys()[i]] ) while size > n_overload: if n < 100: print(len(branch_outages)) # if not post_lopf: # if div_ext_lines: # l_snom_pre, t_snom_pre = split_extended_lines( # network, percent=1.5 # ) # branch_outages=network.lines[network.lines.country == "DE"].index # network_lopf_build_model(network, formulation="kirchhoff") # network_lopf_prepare_solver(network, solver_name="gurobi") logger.info(str(size) + " overloadings") combinations = calc_new_sc_combinations(combinations, new) nb = ( int( add_all_contingency_constraints( network, combinations, track_time ) ) / 2 ) track_time[datetime.datetime.now()] = ( "Update Contingency constraints" ) logger.info( "SCLOPF No. " + str(n + 1) + " started with " + str(2 * nb) + " SC-constraints." ) iterate_lopf_calc(network, args, l_snom_pre, t_snom_pre) track_time[datetime.datetime.now()] = "Solve SCLOPF" # nur mit dieser Reihenfolge (x anpassen, dann lpf_check) kann Netzausbau n-1 sicher werden if network.lines.s_nom_extendable.any(): l_snom_pre, t_snom_pre = update_electrical_parameters( network, l_snom_pre, t_snom_pre ) track_time[datetime.datetime.now()] = "Adjust impedances" if not post_lopf: if div_ext_lines: l_snom_pre, t_snom_pre = split_extended_lines( network, percent=1.5 ) # branch_outages=network.lines[network.lines.country == "DE"].index network_lopf_build_model(network, formulation="kirchhoff") network_lopf_prepare_solver(network, solver_name="gurobi") track_time[datetime.datetime.now()] = "Adjust impedances" new = post_contingency_analysis_per_line( network, branch_outages, n_process, delta ) size = 0 for i in range(len(new.keys())): size = ( size + len(new.values()[i][0]) * network.snapshot_weightings.generators[new.keys()[i]] ) track_time[datetime.datetime.now()] = ( "Overall post contingency analysis" ) n += 1 else: print("Maximum number of iterations reached.") break if args["csv_export"] != False: etrago.export_to_csv(args["csv_export"] + "/grid_optimization") y = (time.time() - x) / 60 logger.info( "SCLOPF with " + str(2 * nb) + " constraints solved in " + str(n) + " iterations in " + str(round(y, 2)) + " minutes." )