diff --git a/doc/api.rst b/doc/api.rst index d6533797..4a49271d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -6,6 +6,10 @@ API :glob: :titlesonly: + api/etrago.analyze api/etrago.cluster + api/etrago.disaggregate + api/etrago.execute api/etrago.tools api/appl.rst + api/network.rst diff --git a/doc/api/etrago.analyze.rst b/doc/api/etrago.analyze.rst new file mode 100644 index 00000000..e86609ee --- /dev/null +++ b/doc/api/etrago.analyze.rst @@ -0,0 +1,20 @@ +etrago.analyze package +====================== + +etrago.analyze.calc\_results module +----------------------------------- + +.. automodule:: etrago.analyze.calc_results + :members: + :undoc-members: + :show-inheritance: + +etrago.analyze.plot module +--------------------------- + +.. automodule:: etrago.analyze.plot + :members: + :undoc-members: + :show-inheritance: + + diff --git a/doc/api/etrago.cluster.rst b/doc/api/etrago.cluster.rst index 40fc8db3..6fd27ff3 100644 --- a/doc/api/etrago.cluster.rst +++ b/doc/api/etrago.cluster.rst @@ -1,13 +1,6 @@ etrago.cluster package ======================= -etrago.cluster.disaggregation module ------------------------------------------ - -.. automodule:: etrago.cluster.disaggregation - :members: - :undoc-members: - :show-inheritance: etrago.cluster.electrical module ----------------------------------------- @@ -25,10 +18,10 @@ etrago.cluster.gas module :undoc-members: :show-inheritance: -etrago.cluster.snapshot module +etrago.cluster.temporal module -------------------------------- -.. automodule:: etrago.cluster.snapshot +.. automodule:: etrago.cluster.temporal :members: :undoc-members: :show-inheritance: diff --git a/doc/api/etrago.disaggregate.rst b/doc/api/etrago.disaggregate.rst new file mode 100644 index 00000000..9929a487 --- /dev/null +++ b/doc/api/etrago.disaggregate.rst @@ -0,0 +1,19 @@ +etrago.disaggregate package +=========================== + +etrago.disaggregate.spatial module +---------------------------------- + +.. automodule:: etrago.disaggregate.spatial + :members: + :undoc-members: + :show-inheritance: + +etrago.disaggregate.temporal module +----------------------------------- + +.. automodule:: etrago.disaggregate.temporal + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/api/network.rst b/doc/api/network.rst new file mode 100644 index 00000000..3189e5a4 --- /dev/null +++ b/doc/api/network.rst @@ -0,0 +1,7 @@ +etrago.network module +------------------- + +.. automodule:: etrago.network + :members: + :undoc-members: + :show-inheritance: diff --git a/etrago/__init__.py b/etrago/__init__.py index be816a58..c207f7d9 100644 --- a/etrago/__init__.py +++ b/etrago/__init__.py @@ -12,4 +12,4 @@ __author__ = "ulfmueller, lukasol, wolfbunke, mariusves, s3pp" __version__ = "0.4" -from etrago.tools.network import Etrago +from etrago.network import Etrago diff --git a/etrago/analyze/__init__.py b/etrago/analyze/__init__.py new file mode 100644 index 00000000..5dd475b7 --- /dev/null +++ b/etrago/analyze/__init__.py @@ -0,0 +1,7 @@ +""" + +""" + +__copyright__ = "tba" +__license__ = "tba" +__author__ = "tba" diff --git a/etrago/tools/calc_results.py b/etrago/analyze/calc_results.py similarity index 100% rename from etrago/tools/calc_results.py rename to etrago/analyze/calc_results.py diff --git a/etrago/tools/plot.py b/etrago/analyze/plot.py similarity index 97% rename from etrago/tools/plot.py rename to etrago/analyze/plot.py index 6e524a3e..07dd75d6 100644 --- a/etrago/tools/plot.py +++ b/etrago/analyze/plot.py @@ -21,15 +21,14 @@ """ Plot.py defines functions necessary to plot results of eTraGo. """ -from math import log10, sqrt +from math import sqrt import logging import os -from etrago.execute import import_gen_from_links from matplotlib import pyplot as plt from matplotlib.legend_handler import HandlerPatch from matplotlib.patches import Circle, Ellipse -from pyproj import Proj, transform +from pypsa.plot import draw_map_cartopy import matplotlib import matplotlib.patches as mpatches import numpy as np @@ -40,17 +39,19 @@ import cartopy.crs as ccrs except ImportError: cartopy_present = False -from pypsa.plot import draw_map_cartopy + logger = logging.getLogger(__name__) if "READTHEDOCS" not in os.environ: - from geoalchemy2.shape import to_shape + from geoalchemy2.shape import to_shape # noqa: F401 from pyproj import Proj, transform - from shapely.geometry import LineString, MultiPoint, Point, Polygon + from shapely.geometry import LineString, Point import geopandas as gpd import tilemapbase + from etrago.execute import import_gen_from_links + __copyright__ = ( "Flensburg University of Applied Sciences, " "Europa-Universität Flensburg, " @@ -58,8 +59,9 @@ "DLR-Institute for Networked Energy Systems" ) __license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" -__author__ = """ulfmueller, MarlonSchlemminger, mariusves, lukasol, ClaraBuettner, -CarlosEpia, pieterhexen, gnn, fwitte, lukasol, KathiEsterl, BartelsJ""" +__author__ = """ulfmueller, MarlonSchlemminger, mariusves, lukasol, +ClaraBuettner, CarlosEpia, pieterhexen, gnn, fwitte, lukasol, KathiEsterl, +BartelsJ""" def set_epsg_network(network): @@ -913,9 +915,9 @@ def calc_storage_expansion_per_bus( [(idx, "battery") for idx in battery_distribution.index] ) - dist.loc[ - dist.index.get_level_values("carrier") == "battery" - ] = battery_distribution + dist.loc[dist.index.get_level_values("carrier") == "battery"] = ( + battery_distribution + ) if "H2_overground" in carriers: h2_overground = network.stores[ network.stores.carrier == "H2_overground" @@ -931,9 +933,9 @@ def calc_storage_expansion_per_bus( [(idx, "H2_overground") for idx in h2_over_distribution.index] ) - dist.loc[ - dist.index.get_level_values("carrier") == "H2_overground" - ] = h2_over_distribution + dist.loc[dist.index.get_level_values("carrier") == "H2_overground"] = ( + h2_over_distribution + ) if "H2_overground" in carriers: h2_underground = network.stores[ @@ -1820,7 +1822,9 @@ def plot_background_grid(network, ax, geographical_boundaries, osm): ) -def demand_side_management(self, buses, snapshots, agg="5h", used=False, apply_on="grid_model"): +def demand_side_management( + self, buses, snapshots, agg="5h", used=False, apply_on="grid_model" +): """Calculate shifting potential of demand side management Parameters @@ -1855,13 +1859,13 @@ def demand_side_management(self, buses, snapshots, agg="5h", used=False, apply_o else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) df = pd.DataFrame(index=network.snapshots[snapshots]) link = network.links[ - (network.links.carrier == "dsm") - & (network.links.bus0.isin(buses)) + (network.links.carrier == "dsm") & (network.links.bus0.isin(buses)) ] s = network.stores[ (network.stores.carrier == "dsm") @@ -1953,15 +1957,14 @@ def bev_flexibility_potential( else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) # Initialize DataFrame df = pd.DataFrame(index=network.snapshots[snapshots]) # Select BEV buses and links - bev_buses = network.buses[ - network.buses.carrier.str.contains("Li ion") - ] + bev_buses = network.buses[network.buses.carrier.str.contains("Li ion")] bev_links = network.links[ (network.links.bus1.isin(bev_buses.index.values)) & (network.links.bus0.isin(buses)) @@ -2009,9 +2012,7 @@ def bev_flexibility_potential( ) if used: - bev_links_t_used = network.links_t.p0[bev_links.index].iloc[ - snapshots - ] + bev_links_t_used = network.links_t.p0[bev_links.index].iloc[snapshots] bev_links_t_used.columns = bev_links_t_used.columns.map(bev_links.bus1) @@ -2077,7 +2078,8 @@ def heat_stores( else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) df = pd.DataFrame(index=network.snapshots[snapshots]) @@ -2089,9 +2091,7 @@ def heat_stores( ].index ) & network.links.bus1.isin( - network.buses[ - network.buses.carrier.str.contains("heat") - ].index + network.buses[network.buses.carrier.str.contains("heat")].index ) ].bus1.unique() @@ -2175,7 +2175,8 @@ def hydrogen_stores( else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) df = pd.DataFrame(index=network.snapshots[snapshots]) @@ -2187,9 +2188,7 @@ def hydrogen_stores( ].index ) & network.links.bus1.isin( - network.buses[ - network.buses.carrier.str.contains("H2") - ].index + network.buses[network.buses.carrier.str.contains("H2")].index ) ].bus1.unique() @@ -2209,7 +2208,13 @@ def hydrogen_stores( def flexibility_usage( - self, flexibility, agg="5h", snapshots=[], buses=[], pre_path=None, apply_on="grid_model", + self, + flexibility, + agg="5h", + snapshots=[], + buses=[], + pre_path=None, + apply_on="grid_model", ): """Plots temporal distribution of potential and usage for flexibilities @@ -2247,7 +2252,8 @@ def flexibility_usage( else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) colors = coloring() colors["dlr"] = "orange" @@ -2301,9 +2307,7 @@ def flexibility_usage( df["p_min"] = su.p_nom_opt.sum() * (-1) df["p_max"] = su.p_nom_opt.sum() df["p"] = ( - network.storage_units_t.p[su.index] - .sum(axis=1) - .iloc[snapshots] + network.storage_units_t.p[su.index].sum(axis=1).iloc[snapshots] ) df["e_min"] = 0 @@ -2345,7 +2349,9 @@ def flexibility_usage( fig_e.savefig(pre_path + f"stored_e_{flexibility}") -def plot_carrier(etrago, carrier_links=["AC"], carrier_buses=["AC"], apply_on="grid_model"): +def plot_carrier( + etrago, carrier_links=["AC"], carrier_buses=["AC"], apply_on="grid_model" +): """ Parameters ---------- @@ -2375,7 +2381,8 @@ def plot_carrier(etrago, carrier_links=["AC"], carrier_buses=["AC"], apply_on="g else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) colors = coloring() line_colors = "lightblue" @@ -2474,7 +2481,8 @@ def plot_grid( Set static bus color or attribute to plot. The default is 'grey'. Current options: - * 'nodal_production_balance': net producer/consumer in selected timeteps + * 'nodal_production_balance': net producer/consumer in selected + time steps * 'storage_expansion': storage expansion per bus and technology * 'storage_distribution': installed storage units per bus * 'h2_battery_storage_expansion': storage expansion per bus and @@ -2484,7 +2492,7 @@ def plot_grid( * 'ramp_down': re-dispatch down per carrier in selected timesteps * 'PowerToH2': location and sizes of electrolyzers * 'flexibility_usage': use of DSM and BEV charger - * 'PowerToH2_correlation': indication of degree of correlation to + * 'PowerToH2_correlation': indication of degree of correlation to market or nodal price of electrolyzers timesteps : array, optional @@ -2539,7 +2547,8 @@ def plot_grid( else: logger.warning( """Parameter apply_on must be one of ['grid_model', 'market_model' - 'pre_market_model', 'disaggregated_network'.""") + 'pre_market_model', 'disaggregated_network'.""" + ) # Set colors for plotting plotting_colors(network) @@ -2852,31 +2861,40 @@ def plot_grid( bus_colors = coloring()["power_to_H2"] bus_legend = "PowerToH2" bus_unit = "TW" - - - market_bus_de = self.market_model.buses[(self.market_model.buses.country=="DE") & (self.market_model.buses.carrier=="AC")].index + market_bus_de = self.market_model.buses[ + (self.market_model.buses.country == "DE") + & (self.market_model.buses.carrier == "AC") + ].index market_price = self.market_model.buses_t.marginal_price[market_bus_de] - + bus_colors = pd.Series(index=network.buses.index, data=0) - for bus in network.links[(network.links.carrier == "power_to_H2")].bus0: - + for bus in network.links[ + (network.links.carrier == "power_to_H2") + ].bus0: + nodal_price = network.buses_t.marginal_price[bus] - - ely = network.links_t.p0[network.links[(network.links.carrier == "power_to_H2") - & (network.links.bus0==bus)].index] + + ely = network.links_t.p0[ + network.links[ + (network.links.carrier == "power_to_H2") + & (network.links.bus0 == bus) + ].index + ] df_corr = pd.DataFrame() - + df_corr["ely"] = ely df_corr["market"] = market_price df_corr["nodal_price"] = nodal_price - - bus_colors[bus] = (df_corr.corr(method = 'spearman').loc["nodal_price", "ely"]/ ( - df_corr.corr(method = 'spearman').loc["nodal_price", "ely"])+ - df_corr.corr(method = 'spearman').loc["market", "ely"]) - + + bus_colors[bus] = ( + df_corr.corr(method="spearman").loc["nodal_price", "ely"] + / (df_corr.corr(method="spearman").loc["nodal_price", "ely"]) + + df_corr.corr(method="spearman").loc["market", "ely"] + ) + bus_colors = bus_colors.abs() - + # ely.corr # ely_corr_market = ely.corrwith( # market_price, method = 'spearman', axis=1) @@ -2901,11 +2919,11 @@ def plot_grid( link_cmap=plt.cm.viridis, bus_sizes=bus_sizes, bus_colors=bus_colors, - bus_cmap = plt.cm.viridis, + bus_cmap=plt.cm.viridis, line_widths=line_widths, link_widths=link_widths, flow=flow, - #title=title, + # title=title, geomap=False, projection=ccrs.PlateCarree(), color_geomap=True, @@ -3024,7 +3042,7 @@ def plot_grid( ) ax.add_artist(l3) - if type(line_colors) != str: + if type(line_colors) is not str: # Set fixed boundaries if selected in parameters if not boundaries: boundaries = [ @@ -3044,18 +3062,19 @@ def plot_grid( ll[1], values=v, ticks=v[0:101:10], - #fraction=0.028, + # fraction=0.028, pad=0.04, - orientation="horizontal" + orientation="horizontal", ) # Set legend label cb.set_label(label) - - elif type(bus_colors) != str: - #import pdb; pdb.set_trace() - ll[0].set_clim([0, bus_colors.max()]) - plt.colorbar(ll[0], fraction=0.04, pad=0.004, label="correlation factor", ax=ax) + elif type(bus_colors) is not str: + # import pdb; pdb.set_trace() + ll[0].set_clim([0, bus_colors.max()]) + plt.colorbar( + ll[0], fraction=0.04, pad=0.004, label="correlation factor", ax=ax + ) # Show plot or save to file if filename is None: @@ -3210,10 +3229,15 @@ def plot_clusters( & lines["bus1"].isin(map_buses.index) ] lines["geom"] = lines.apply( - lambda x: x["geom"] - if not pd.isna(x["geom"]) - else LineString( - [map_buses["geom"][x["bus0"]], map_buses["geom"][x["bus1"]]] + lambda x: ( + x["geom"] + if not pd.isna(x["geom"]) + else LineString( + [ + map_buses["geom"][x["bus0"]], + map_buses["geom"][x["bus1"]], + ] + ) ), axis=1, ) diff --git a/etrago/cluster/snapshot.py b/etrago/cluster/temporal.py similarity index 100% rename from etrago/cluster/snapshot.py rename to etrago/cluster/temporal.py diff --git a/etrago/disaggregate/__init__.py b/etrago/disaggregate/__init__.py new file mode 100644 index 00000000..5dd475b7 --- /dev/null +++ b/etrago/disaggregate/__init__.py @@ -0,0 +1,7 @@ +""" + +""" + +__copyright__ = "tba" +__license__ = "tba" +__author__ = "tba" diff --git a/etrago/cluster/disaggregation.py b/etrago/disaggregate/spatial.py similarity index 100% rename from etrago/cluster/disaggregation.py rename to etrago/disaggregate/spatial.py diff --git a/etrago/disaggregate/temporal.py b/etrago/disaggregate/temporal.py new file mode 100644 index 00000000..5e7b6ede --- /dev/null +++ b/etrago/disaggregate/temporal.py @@ -0,0 +1,164 @@ +# -*- 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 . + +# File description +""" +execute.py defines optimization and simulation methods for the etrago object. +""" +import logging +import os +import time + +import pandas as pd + +logger = logging.getLogger(__name__) + +if "READTHEDOCS" not in os.environ: + + from etrago.execute import iterate_lopf + from etrago.tools.constraints import Constraints + + +__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__ = ( + "ulfmueller, s3pp, wolfbunke, mariusves, lukasol, KathiEsterl, " + "ClaraBuettner, CarlosEpia, AmeliaNadal" +) + + +def dispatch_disaggregation(self): + """ + Function running the tempral disaggregation meaning the optimization + of dispatch in the temporally fully resolved network; therfore, the problem + is reduced to smaller subproblems by slicing the whole considered time span + while keeping inforation on the state of charge of storage units and stores + to ensure compatibility and to reproduce saisonality. + + Returns + ------- + None. + + """ + + if self.args["temporal_disaggregation"]["active"]: + x = time.time() + + if self.args["temporal_disaggregation"]["no_slices"]: + # split dispatch_disaggregation into subproblems + # keep some information on soc in beginning and end of slices + # to ensure compatibility and to reproduce saisonality + + # define number of slices and corresponding slice length + no_slices = self.args["temporal_disaggregation"]["no_slices"] + slice_len = int(len(self.network.snapshots) / no_slices) + + # transition snapshots defining start and end of slices + transits = self.network.snapshots[0::slice_len] + if len(transits) > 1: + transits = transits[1:] + if transits[-1] != self.network.snapshots[-1]: + transits = transits.insert( + (len(transits)), self.network.snapshots[-1] + ) + # for stores, exclude emob and dsm because of their special + # constraints + sto = self.network.stores[ + ~self.network.stores.carrier.isin( + ["battery_storage", "battery storage", "dsm"] + ) + ] + + # save state of charge of storage units and stores at those + # transition snapshots + self.conduct_dispatch_disaggregation = pd.DataFrame( + columns=self.network.storage_units.index.append(sto.index), + index=transits, + ) + for storage in self.network.storage_units.index: + self.conduct_dispatch_disaggregation[storage] = ( + self.network.storage_units_t.state_of_charge[storage] + ) + for store in sto.index: + self.conduct_dispatch_disaggregation[store] = ( + self.network.stores_t.e[store] + ) + + extra_func = self.args["extra_functionality"] + self.args["extra_functionality"] = {} + + load_shedding = self.args["load_shedding"] + if not load_shedding: + self.args["load_shedding"] = True + self.load_shedding(temporal_disaggregation=True) + + iterate_lopf( + self, + Constraints( + self.args, self.conduct_dispatch_disaggregation + ).functionality, + method=self.args["method"], + ) + + # switch to temporally fully resolved network as standard network, + # temporally reduced network is stored in network_tsa + network1 = self.network.copy() + self.network = self.network_tsa.copy() + self.network_tsa = network1.copy() + network1 = 0 + + # keep original settings + + if self.args["temporal_disaggregation"]["no_slices"]: + self.args["extra_functionality"] = extra_func + self.args["load_shedding"] = load_shedding + + self.network.lines["s_nom_extendable"] = self.network_tsa.lines[ + "s_nom_extendable" + ] + self.network.links["p_nom_extendable"] = self.network_tsa.links[ + "p_nom_extendable" + ] + self.network.transformers.s_nom_extendable = ( + self.network_tsa.transformers.s_nom_extendable + ) + self.network.storage_units["p_nom_extendable"] = ( + self.network_tsa.storage_units["p_nom_extendable"] + ) + self.network.stores["e_nom_extendable"] = self.network_tsa.stores[ + "e_nom_extendable" + ] + self.network.storage_units.cyclic_state_of_charge = ( + self.network_tsa.storage_units.cyclic_state_of_charge + ) + self.network.stores.e_cyclic = self.network_tsa.stores.e_cyclic + + if not self.args["csv_export"]: + path = self.args["csv_export"] + self.export_to_csv(path) + self.export_to_csv(path + "/temporal_disaggregaton") + + y = time.time() + z = (y - x) / 60 + logger.info("Time for LOPF [min]: {}".format(round(z, 2))) diff --git a/etrago/execute/__init__.py b/etrago/execute/__init__.py index 16e71be0..7d00e861 100644 --- a/etrago/execute/__init__.py +++ b/etrago/execute/__init__.py @@ -141,56 +141,60 @@ def run_lopf(etrago, extra_functionality, method): ) )[0] - if method["pyomo"]: - # repeat the optimization for all slices - for i in range(0, no_slices): - # keep information on the initial state of charge for the - # respectng slice - initial = transits[i - 1] - soc_initial = etrago.conduct_dispatch_disaggregation.loc[ - [etrago.network_tsa.snapshots[initial]] - ].transpose() - etrago.network_tsa.storage_units.state_of_charge_initial = ( - soc_initial - ) - etrago.network_tsa.stores.e_initial = soc_initial - etrago.network_tsa.stores.e_initial.fillna(0, inplace=True) - # the state of charge at the end of each slice is set within - # split_dispatch_disaggregation_constraints in constraints.py - - # adapt start and end snapshot of respecting slice - start = transits[i - 1] + skipped - end = transits[i] + (skipped - 1) - if i == 0: - start = 0 - if i == no_slices - 1: - end = len(etrago.network_tsa.snapshots) - - etrago.network_tsa.lopf( - etrago.network_tsa.snapshots[start : end + 1], - solver_name=etrago.args["solver"], - solver_options=etrago.args["solver_options"], - pyomo=True, - extra_functionality=extra_functionality, - formulation=etrago.args["model_formulation"], - ) - - if etrago.network_tsa.results["Solver"][0]["Status"] != "ok": - raise Exception("LOPF not solved.") - - else: - for i in range(0, no_slices): - status, termination_condition = network_lopf( - etrago.network_tsa, - etrago.network_tsa.snapshots[start : end + 1], - solver_name=etrago.args["solver"], - solver_options=etrago.args["solver_options"], - extra_functionality=extra_functionality, - formulation=etrago.args["model_formulation"], - ) + # repeat the optimization for all slices + for i in range(0, no_slices): + # keep information on the initial state of charge for the + # respectng slice + initial = transits[i - 1] + soc_initial = etrago.conduct_dispatch_disaggregation.loc[ + [etrago.network_tsa.snapshots[initial]] + ].transpose() + etrago.network_tsa.storage_units.state_of_charge_initial = ( + soc_initial + ) + etrago.network_tsa.stores.e_initial = soc_initial + etrago.network_tsa.stores.e_initial.fillna(0, inplace=True) + # the state of charge at the end of each slice is set within + # split_dispatch_disaggregation_constraints in constraints.py + + # adapt start and end snapshot of respecting slice + start = transits[i - 1] + skipped + end = transits[i] + (skipped - 1) + if i == 0: + start = 0 + if i == no_slices - 1: + end = len(etrago.network_tsa.snapshots) + + if method["formulation"] == "pyomo": + etrago.network_tsa.lopf( + etrago.network_tsa.snapshots[start : end + 1], + solver_name=etrago.args["solver"], + solver_options=etrago.args["solver_options"], + pyomo=True, + extra_functionality=extra_functionality, + formulation=etrago.args["model_formulation"], + ) - if status != "ok": - raise Exception("LOPF not solved.") + if ( + etrago.network_tsa.results["Solver"][0]["Status"] + != "ok" + ): + raise Exception("LOPF not solved.") + else: + raise Exception( + """Temporal disaggregation currently only works when + using pyomo.""" + ) + status, termination_condition = network_lopf( + etrago.network_tsa, + etrago.network_tsa.snapshots[start : end + 1], + solver_name=etrago.args["solver"], + solver_options=etrago.args["solver_options"], + extra_functionality=extra_functionality, + formulation=etrago.args["model_formulation"], + ) + if status != "ok": + raise Exception("LOPF not solved.") etrago.network_tsa.storage_units.state_of_charge_initial = 0 etrago.network_tsa.stores.e_initial = 0 @@ -447,122 +451,6 @@ def optimize(self): print("Method not defined") -def dispatch_disaggregation(self): - """ - Function running the tempral disaggregation meaning the optimization - of dispatch in the temporally fully resolved network; therfore, the problem - is reduced to smaller subproblems by slicing the whole considered time span - while keeping inforation on the state of charge of storage units and stores - to ensure compatibility and to reproduce saisonality. - - Returns - ------- - None. - - """ - - if self.args["temporal_disaggregation"]["active"]: - x = time.time() - - if self.args["temporal_disaggregation"]["no_slices"]: - # split dispatch_disaggregation into subproblems - # keep some information on soc in beginning and end of slices - # to ensure compatibility and to reproduce saisonality - - # define number of slices and corresponding slice length - no_slices = self.args["temporal_disaggregation"]["no_slices"] - slice_len = int(len(self.network.snapshots) / no_slices) - - # transition snapshots defining start and end of slices - transits = self.network.snapshots[0::slice_len] - if len(transits) > 1: - transits = transits[1:] - if transits[-1] != self.network.snapshots[-1]: - transits = transits.insert( - (len(transits)), self.network.snapshots[-1] - ) - # for stores, exclude emob and dsm because of their special - # constraints - sto = self.network.stores[ - ~self.network.stores.carrier.isin( - ["battery_storage", "battery storage", "dsm"] - ) - ] - - # save state of charge of storage units and stores at those - # transition snapshots - self.conduct_dispatch_disaggregation = pd.DataFrame( - columns=self.network.storage_units.index.append(sto.index), - index=transits, - ) - for storage in self.network.storage_units.index: - self.conduct_dispatch_disaggregation[storage] = ( - self.network.storage_units_t.state_of_charge[storage] - ) - for store in sto.index: - self.conduct_dispatch_disaggregation[store] = ( - self.network.stores_t.e[store] - ) - - extra_func = self.args["extra_functionality"] - self.args["extra_functionality"] = {} - - load_shedding = self.args["load_shedding"] - if not load_shedding: - self.args["load_shedding"] = True - self.load_shedding(temporal_disaggregation=True) - - iterate_lopf( - self, - Constraints( - self.args, self.conduct_dispatch_disaggregation - ).functionality, - method=self.args["method"], - ) - - # switch to temporally fully resolved network as standard network, - # temporally reduced network is stored in network_tsa - network1 = self.network.copy() - self.network = self.network_tsa.copy() - self.network_tsa = network1.copy() - network1 = 0 - - # keep original settings - - if self.args["temporal_disaggregation"]["no_slices"]: - self.args["extra_functionality"] = extra_func - self.args["load_shedding"] = load_shedding - - self.network.lines["s_nom_extendable"] = self.network_tsa.lines[ - "s_nom_extendable" - ] - self.network.links["p_nom_extendable"] = self.network_tsa.links[ - "p_nom_extendable" - ] - self.network.transformers.s_nom_extendable = ( - self.network_tsa.transformers.s_nom_extendable - ) - self.network.storage_units["p_nom_extendable"] = ( - self.network_tsa.storage_units["p_nom_extendable"] - ) - self.network.stores["e_nom_extendable"] = self.network_tsa.stores[ - "e_nom_extendable" - ] - self.network.storage_units.cyclic_state_of_charge = ( - self.network_tsa.storage_units.cyclic_state_of_charge - ) - self.network.stores.e_cyclic = self.network_tsa.stores.e_cyclic - - if not self.args["csv_export"]: - path = self.args["csv_export"] - self.export_to_csv(path) - self.export_to_csv(path + "/temporal_disaggregaton") - - y = time.time() - z = (y - x) / 60 - logger.info("Time for LOPF [min]: {}".format(round(z, 2))) - - def import_gen_from_links(network, drop_small_capacities=True): """ create gas generators from links in order to not lose them when diff --git a/etrago/execute/market_optimization.py b/etrago/execute/market_optimization.py index c0a40007..e175ed44 100644 --- a/etrago/execute/market_optimization.py +++ b/etrago/execute/market_optimization.py @@ -72,10 +72,7 @@ def market_optimization(self): f"""Optimization failed with status {status} and condition {condition}""" ) - self.pre_market_model.model.print_infeasibilities() - import pdb - pdb.set_trace() else: logger.warning("Method type must be either 'pyomo' or 'linopy'") diff --git a/etrago/tools/network.py b/etrago/network.py similarity index 97% rename from etrago/tools/network.py rename to etrago/network.py index 8b38f5eb..c69d0e0b 100644 --- a/etrago/tools/network.py +++ b/etrago/network.py @@ -33,29 +33,7 @@ from etrago.tools import db from etrago import __version__ -from etrago.cluster.disaggregation import run_disaggregation -from etrago.cluster.electrical import ehv_clustering, run_spatial_clustering -from etrago.cluster.gas import run_spatial_clustering_gas -from etrago.cluster.snapshot import skip_snapshots, snapshot_clustering -from etrago.execute import ( - dispatch_disaggregation, - lopf, - optimize, - run_pf_post_lopf, -) -from etrago.execute.grid_optimization import ( - add_redispatch_generators, - grid_optimization, -) -from etrago.execute.market_optimization import ( - build_market_model, - market_optimization, -) -from etrago.execute.sclopf import ( - iterate_sclopf, - post_contingency_analysis_lopf, -) -from etrago.tools.calc_results import ( +from etrago.analyze.calc_results import ( ac_export, ac_export_per_country, calc_etrago_results, @@ -64,14 +42,7 @@ german_network, system_costs_germany, ) -from etrago.tools.extendable import extendable -from etrago.tools.io import ( - NetworkScenario, - add_ch4_h2_correspondence, - decommissioning, - extension, -) -from etrago.tools.plot import ( +from etrago.analyze.plot import ( bev_flexibility_potential, demand_side_management, flexibility_usage, @@ -88,6 +59,31 @@ plot_heat_summary, shifted_energy, ) +from etrago.cluster.electrical import ehv_clustering, run_spatial_clustering +from etrago.cluster.gas import run_spatial_clustering_gas +from etrago.cluster.temporal import skip_snapshots, snapshot_clustering +from etrago.disaggregate.spatial import run_disaggregation +from etrago.disaggregate.temporal import dispatch_disaggregation +from etrago.execute import lopf, optimize, run_pf_post_lopf +from etrago.execute.grid_optimization import ( + add_redispatch_generators, + grid_optimization, +) +from etrago.execute.market_optimization import ( + build_market_model, + market_optimization, +) +from etrago.execute.sclopf import ( + iterate_sclopf, + post_contingency_analysis_lopf, +) +from etrago.tools.extendable import extendable +from etrago.tools.io import ( + NetworkScenario, + add_ch4_h2_correspondence, + decommissioning, + extension, +) from etrago.tools.utilities import ( add_missing_components, adjust_CH4_gen_carriers, diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 131c5b4e..06dff351 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3390,7 +3390,7 @@ def functionality(self, network, snapshots): ) if self.conduct_dispatch_disaggregation is not False: - if self.args["method"]["pyomo"]: + if self.args["method"]["formulation"]=="pyomo": split_dispatch_disaggregation_constraints( self, network, snapshots ) diff --git a/etrago/tools/extendable.py b/etrago/tools/extendable.py index df115aa6..001cf5bd 100644 --- a/etrago/tools/extendable.py +++ b/etrago/tools/extendable.py @@ -27,7 +27,7 @@ import numpy as np import pandas as pd -from etrago.cluster.snapshot import snapshot_clustering +from etrago.cluster.temporal import snapshot_clustering from etrago.tools.utilities import convert_capital_costs, find_snapshots __copyright__ = ( diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index eb63e603..3214981e 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2581,13 +2581,13 @@ def check_args(etrago): etrago.args["snapshot_clustering"]["n_segments"] ), "Number of segments is higher than number of snapshots" - if not etrago.args["method"]["pyomo"]: + if etrago.args["method"]["formulation"] != "pyomo": logger.warning( "Snapshot clustering constraints are" " not yet correctly implemented without pyomo." - " Setting `args['method']['pyomo']` to `True`." + " Setting `args['method']['formulation']` to `pyomo`." ) - etrago.args["method"]["pyomo"] = True + etrago.args["method"]["formulation"] = "pyomo" if etrago.args["method"]["formulation"] != "pyomo": try: @@ -2607,6 +2607,16 @@ def check_args(etrago): ) raise + if (etrago.args["method"]["formulation"] != "pyomo") & ( + etrago.args["temporal_disaggregation"]["active"] + ): + logger.warning( + "Temporal disaggregation is" + " not yet correctly implemented without pyomo." + " Setting `args['method']['formulation']` to `pyomo`." + ) + etrago.args["method"]["formulation"] = "pyomo" + def drop_sectors(self, drop_carriers): """ diff --git a/noxfile.py b/noxfile.py index a161d0ca..0166dd29 100644 --- a/noxfile.py +++ b/noxfile.py @@ -5,18 +5,21 @@ import nox cleaned = [ - "etrago/cluster/disaggregation.py", + "etrago/analyze/__init__.py", + "etrago/analyze/calc_results.py", + "etrago/analyze/plot.py", "etrago/cluster/electrical.py", "etrago/cluster/gas.py", - "etrago/cluster/snapshot.py", "etrago/cluster/spatial.py", + "etrago/cluster/temporal.py", + "etrago/disaggregate/spatial.py", + "etrago/disaggregate/temporal.py", "etrago/execute/__init__.py", "etrago/execute/grid_optimization.py", "etrago/execute/market_optimization.py", - "etrago/tools/calc_results.py", + "etrago/network.py", "etrago/tools/extendable.py", "etrago/tools/io.py", - "etrago/tools/network.py", "etrago/tools/utilities.py", "noxfile.py", "setup.py", diff --git a/requirements-doc.txt b/requirements-doc.txt index 864c6327..afabde02 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -1,12 +1,16 @@ # Packages for read the docs # Using single requirments for docs, see: # https://github.com/rtfd/readthedocs.org/issues/2070 -sphinx_rtd_theme > 1.2.2 -pypsa == 0.20.1 -numpydoc -sqlalchemy geoalchemy2 +keyring +loguru matplotlib nbsphinx +numpydoc +pandas < 2 +pyomo != 6.4.3 +pypsa == 0.20.1 saio -pyomo != 6.4.3 \ No newline at end of file +scikit-learn +sphinx_rtd_theme > 1.2.2 +sqlalchemy \ No newline at end of file diff --git a/setup.py b/setup.py index caeedec8..bca02e2e 100755 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ def read(*names, **kwargs): "matplotlib >= 3.0.3", "oedialect", "pandas < 2.2", + "pyomo < 6.6", "pypsa == 0.26.2", "rtree", "saio",