From 59a7d84b09bbebc88dad7a66bf8c03b2cbb3312a Mon Sep 17 00:00:00 2001 From: martacki Date: Fri, 26 Jul 2024 12:39:01 +0200 Subject: [PATCH 1/8] table_DSR --- plots/table_DSR.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 plots/table_DSR.py diff --git a/plots/table_DSR.py b/plots/table_DSR.py new file mode 100644 index 0000000..a0db088 --- /dev/null +++ b/plots/table_DSR.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: Open Energy Transition gGmbH +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os +import sys +sys.path.append("../submodules/pypsa-eur") +import pypsa +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import warnings +warnings.filterwarnings("ignore") +from _helpers import mock_snakemake, update_config_from_wildcards, load_network, \ + change_path_to_pypsa_eur, change_path_to_base, \ + LINE_LIMITS, CO2L_LIMITS, BAU_HORIZON, replace_multiindex_values + +def DSR(n, style, sector): + + if sector == "heating": + techs = ["residential rural heat", "residential urban decentral heat", "urban central heat"] + query = "carrier in @techs" + elif sector == "transport": + query = "carrier == 'Li ion'" + + if style == "upward": + DSR = n.stores_t.p.multiply(n.snapshot_weightings.objective,axis=0)[n.stores.query(query).index].clip(lower=0).sum().sum() + elif style == "downward": + DSR = n.stores_t.p.multiply(n.snapshot_weightings.objective,axis=0)[n.stores.query(query).index].clip(upper=0).sum().sum() + return DSR + +if __name__ == "__main__": + if "snakemake" not in globals(): + snakemake = mock_snakemake( + "get_heat_pump", + clusters="48", + ) + # update config based on wildcards + config = update_config_from_wildcards(snakemake.config, snakemake.wildcards) + + # move to submodules/pypsa-eur + change_path_to_pypsa_eur() + # network parameters + co2l_limits = CO2L_LIMITS + line_limits = LINE_LIMITS + clusters = config["plotting"]["clusters"] + planning_horizons = config["plotting"]["planning_horizon"] + planning_horizons = [str(x) for x in planning_horizons if not str(x) == BAU_HORIZON] + opts = config["plotting"]["sector_opts"] + + # define scenario namings + scenarios = {"flexible": "Optimal Renovation and Cost-Optimal Heating", + "retro_tes": "Optimal Renovation and Electric Heating", + "flexible-moderate": "Limited Renovation and Cost-Optimal Heating", + "rigid": "No Renovation and Electric Heating"} + + # define heat pumps dataframe + df_DSR_heat = pd.DataFrame( + index = [scenario + dsr for scenario in scenarios.keys() for dsr in [" upward", " downward"]], + columns = planning_horizons, + ) + df_DSR_transport = pd.DataFrame( + index = [scenario + dsr for scenario in scenarios.keys() for dsr in [" upward", " downward"]], + columns = planning_horizons, + ) + + # heat pumps estimation + for planning_horizon in planning_horizons: + lineex = line_limits[planning_horizon] + sector_opts = f"Co2L{co2l_limits[planning_horizon]}-{opts}" + + for scenario, nice_name in scenarios.items(): + # load networks + n = load_network(lineex, clusters, sector_opts, planning_horizon, scenario) + + if n is None: + # Skip further computation for this scenario if network is not loaded + print(f"Network is not found for scenario '{scenario}', planning year '{planning_horizon}'. Skipping...") + continue + + # store into table + df_DSR_heat.loc[scenario + " upward", planning_horizon] = DSR(n, "upward", "heating")/1e6 + df_DSR_heat.loc[scenario + " downward", planning_horizon] = -1*DSR(n, "downward", "heating")/1e6 + + df_DSR_transport.loc[scenario + " upward", planning_horizon] = DSR(n, "upward", "transport")/1e6 + df_DSR_transport.loc[scenario + " downward", planning_horizon] = DSR(n, "downward", "transport")/1e6 + + + df_DSR_heat.index = df_DSR_heat.index + " heating" + df_DSR_transport.index = df_DSR_transport.index + " transport" + df_DSR = pd.concat([df_DSR_heat, df_DSR_transport]) + print(df_DSR) + + # move to base directory + change_path_to_base() + + df_DSR.to_csv(snakemake.output.table) From 226c5b94b03a4f70a1330af0c0227a93a0f36ecf Mon Sep 17 00:00:00 2001 From: martacki Date: Fri, 26 Jul 2024 12:40:04 +0200 Subject: [PATCH 2/8] include Snakefile --- Snakefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Snakefile b/Snakefile index 9722223..8556900 100644 --- a/Snakefile +++ b/Snakefile @@ -230,6 +230,18 @@ rule plot_co2_level: "plots/plot_co2_level.py" +rule plot_DSR: + params: + clusters=config["plotting"]["clusters"], + planning_horizon=config["plotting"]["planning_horizon"], + output: + table=RESULTS+"table_DSR_{clusters}.csv", + resources: + mem_mb=20000, + script: + "plots/table_DSR.py" + + rule plot_co2_levels: input: expand( From b37654b8b9d79c290d6a6befc6c2ba02c5c99ea3 Mon Sep 17 00:00:00 2001 From: martacki Date: Fri, 26 Jul 2024 12:41:46 +0200 Subject: [PATCH 3/8] remove unused imports --- plots/table_DSR.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plots/table_DSR.py b/plots/table_DSR.py index a0db088..e4da856 100644 --- a/plots/table_DSR.py +++ b/plots/table_DSR.py @@ -3,12 +3,8 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -import os import sys sys.path.append("../submodules/pypsa-eur") -import pypsa -import matplotlib.pyplot as plt -import numpy as np import pandas as pd import warnings warnings.filterwarnings("ignore") From 41fa0d22e793c6c8507a319db3dbcd8c0a15ffec Mon Sep 17 00:00:00 2001 From: martacki Date: Mon, 29 Jul 2024 17:34:10 +0200 Subject: [PATCH 4/8] include industry opex plot --- Snakefile | 1 + plots/plot_electricity_bills.py | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Snakefile b/Snakefile index 8556900..38b90f3 100644 --- a/Snakefile +++ b/Snakefile @@ -68,6 +68,7 @@ rule plot_electricity_bill: output: figure_bills=RESULTS+"plot_bill_per_household_{clusters}_{planning_horizon}.png", figure_price=RESULTS+"plot_prices_per_MWh_{clusters}_{planning_horizon}.png", + figure_opex=RESULTS+"plot_opex_industry_{clusters}_{planning_horizon}.png", resources: mem_mb=20000, script: diff --git a/plots/plot_electricity_bills.py b/plots/plot_electricity_bills.py index 9bbb054..33318f3 100644 --- a/plots/plot_electricity_bills.py +++ b/plots/plot_electricity_bills.py @@ -129,7 +129,7 @@ def electricity_bills(network, households): def plot_electricity_cost(df_prices, name): # Check if name is one of the allowed values - allowed_names = ["bills", "prices"] + allowed_names = ["bills", "prices", "industry"] if name not in allowed_names: raise ValueError("name must be one of {}".format(allowed_names)) @@ -171,9 +171,14 @@ def plot_electricity_cost(df_prices, name): ylabel = ax.set_ylabel("EUR/MWh") ax.set_ylim([0, 300]) plt.savefig(snakemake.output.figure_price, bbox_inches='tight', dpi=600) + elif name == "industry": + ax.set_title("OPEX for the industry") + ylabel = ax.set_ylabel("bn EUR") + #ax.set_ylim([0, 300]) + plt.savefig(snakemake.output.figure_opex, bbox_inches='tight', dpi=600) -def electricity_prices(network, households): +def electricity_prices(network): n = network rh_techs_elec = ['residential rural ground heat pump', @@ -249,6 +254,14 @@ def electricity_prices(network, households): return energy_price_MWh +def calc_opex(network, per_MWh): + hours = network.snapshot_weightings.objective.sum() + query = "carrier == 'industry electricity'" + mapping = network.loads.bus.map(network.buses.country) + load = (hours*network.loads.query(query).groupby(mapping).sum().p_set) + return ( + load * per_MWh + ) if __name__ == "__main__": @@ -297,21 +310,25 @@ def electricity_prices(network, households): # calculate electricity bills per household for each network and electricity prices total_elec_bills = pd.DataFrame() total_elec_prices = pd.DataFrame() + industry_opex = pd.DataFrame() for name, network in networks.items(): if network is None: # Skip further computation for this scenario if network is not loaded print(f"Network is not found for scenario '{scenario}', planning year '{planning_horizon}'. Skipping...") continue # get electricity bills - elec_bills_household = electricity_bills(network, households) + elec_bills_household = electricity_bills(network, households).rename(name) # get electricity prices - elec_bills_MWh = electricity_prices(network, households) + elec_bills_MWh = electricity_prices(network).rename(name) + # OPEX for industry + opex = calc_opex(network, elec_bills_MWh).rename(name) # rename series name to scenario name elec_bills_household.name = name elec_bills_MWh.name = name # concatenate current results total_elec_bills = pd.concat([total_elec_bills, elec_bills_household.to_frame().T], axis=0) total_elec_prices = pd.concat([total_elec_prices, elec_bills_MWh.to_frame().T], axis=0) + industry_opex = pd.concat([industry_opex, opex.to_frame().T], axis=0) # plot and store electricity bills if not total_elec_bills.empty: @@ -321,3 +338,7 @@ def electricity_prices(network, households): if not total_elec_prices.empty: plot_electricity_cost(total_elec_prices, "prices") + # plot and store industry opex + if not total_elec_prices.empty: + plot_electricity_cost(total_elec_prices, "industry") + From 3236617f30bf75d83e00a7785d93c76bbedff2f2 Mon Sep 17 00:00:00 2001 From: martacki Date: Wed, 31 Jul 2024 09:28:17 +0200 Subject: [PATCH 5/8] include missing fuels for industry opex --- plots/plot_electricity_bills.py | 116 ++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/plots/plot_electricity_bills.py b/plots/plot_electricity_bills.py index 33318f3..3c53311 100644 --- a/plots/plot_electricity_bills.py +++ b/plots/plot_electricity_bills.py @@ -3,12 +3,9 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -import os import sys sys.path.append("../submodules/pypsa-eur") -import pypsa import matplotlib.pyplot as plt -import numpy as np import pandas as pd import warnings warnings.filterwarnings("ignore") @@ -129,7 +126,7 @@ def electricity_bills(network, households): def plot_electricity_cost(df_prices, name): # Check if name is one of the allowed values - allowed_names = ["bills", "prices", "industry"] + allowed_names = ["bills", "prices"] if name not in allowed_names: raise ValueError("name must be one of {}".format(allowed_names)) @@ -171,13 +168,37 @@ def plot_electricity_cost(df_prices, name): ylabel = ax.set_ylabel("EUR/MWh") ax.set_ylim([0, 300]) plt.savefig(snakemake.output.figure_price, bbox_inches='tight', dpi=600) - elif name == "industry": - ax.set_title("OPEX for the industry") - ylabel = ax.set_ylabel("bn EUR") - #ax.set_ylim([0, 300]) - plt.savefig(snakemake.output.figure_opex, bbox_inches='tight', dpi=600) +def plot_industry_opex(df): + + # color codes f + color_codes = { + "Coal":"#545454", + "Methanol":"#468c8b", + "Biomass":"#baa741", + "Hydrogen":"#f073da", + "Oil products": "#aaaaaa", + "Gas": "#e05b09", + "Heat": "#cc1f1f", + "Electricity": "#110d63", + } + + fig, ax = plt.subplots(figsize=(3,3)) + df.T.plot.bar(ax=ax, width=0.7, color=color_codes, stacked=True) + ax.set_facecolor("white") + handles, labels = ax.get_legend_handles_labels() + if planning_horizon in ["2040", "2050"]: + labels = ["LREH" if label == "LROH" else label for label in labels] + ax.legend(handles[::-1], labels[::-1], loc=[1.05,0], ncol=1, facecolor="white", fontsize='x-small') + ax.set_title("") + ax.spines['left'].set_color('black') + ax.spines['bottom'].set_color('black') + + ylabel = ax.set_ylabel("operating expenses [bn EUR]") + ax.set_ylim([0, 550]) + plt.savefig(snakemake.output.figure_opex, bbox_inches='tight', dpi=600) + def electricity_prices(network): n = network @@ -254,13 +275,61 @@ def electricity_prices(network): return energy_price_MWh -def calc_opex(network, per_MWh): - hours = network.snapshot_weightings.objective.sum() - query = "carrier == 'industry electricity'" +def calc_opex(network, scenarioname): mapping = network.loads.bus.map(network.buses.country) - load = (hours*network.loads.query(query).groupby(mapping).sum().p_set) + + query = "carrier == 'industry electricity'" + load = network.loads.query(query).groupby(mapping).sum().p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'AC'").index].T.groupby(network.buses.country).mean().T + elec = (load * per_MWh).sum().sum() + + query = "carrier == 'solid biomass for industry'" + load = network.loads.query(query).groupby(mapping).sum().p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'solid biomass'").index].T.groupby(network.buses.country).mean().T + solid_biomass = (load * per_MWh).sum().sum() + + query = "carrier == 'gas for industry'" + load = network.loads.query(query).groupby(mapping).sum().p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'gas for industry'").index].T.groupby(network.buses.country).mean().T + gas = (load * per_MWh).sum().values[0] + + query = "carrier == 'coal for industry'" + load = network.loads.query(query).groupby(mapping).sum().p_set.sum() + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'coal'").index].T.groupby(network.buses.country).mean().T + coal = (load * per_MWh).sum().values[0] + + query = "carrier == 'H2 for industry'" + load = network.loads.query(query).groupby(mapping).sum().p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'H2'").index].T.groupby(network.buses.country).mean().T + H2 = (load * per_MWh).sum().sum() + + query = "carrier == 'H2 for industry'" + load = network.loads.query(query).groupby(mapping).sum().p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier == 'H2'").index].T.groupby(network.buses.country).mean().T + H2 = (load * per_MWh).sum().sum() + + query = "carrier in ['industry methanol', 'shipping methanol']" + load = network.loads.query(query).p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier in ['industry methanol', 'shipping methanol']").index] + methanol = (load * per_MWh).sum().sum() + + query = "carrier in ['shipping oil', 'naphtha for industry']" + load = network.loads.query(query).p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier in ['shipping oil', 'naphtha for industry']").index] + oil = (load * per_MWh).sum().sum() + + query = "carrier == 'low-temperature heat for industry'" + load = network.loads.query(query).set_index("bus").p_set + per_MWh = network.buses_t.marginal_price[network.buses.query("carrier in ['urban central heat', 'services urban decentral heat']").index].clip(lower=0) + heat = (load * per_MWh).fillna(0).sum().sum() + + opex = pd.DataFrame( + index=["Electricity", "Biomass", "Gas", "Coal", "Hydrogen", "Methanol", "Oil products", "Heat"], + columns=[scenarioname], + data=[elec, solid_biomass, gas, coal, H2, methanol, oil, heat] + )/1e9 return ( - load * per_MWh + opex.loc[["Coal", "Methanol", "Hydrogen", "Oil products", "Biomass", "Heat", "Gas", "Electricity"]] ) @@ -289,11 +358,16 @@ def calc_opex(network, per_MWh): # define scenario namings if planning_horizon == BAU_HORIZON: scenarios = {"BAU": "BAU"} + scenario_abbrev = scenarios else: - scenarios = {"flexible": "Optimal Renovation and Cost-Optimal Heating", - "retro_tes": "Optimal Renovation and Electric Heating", - "flexible-moderate": "Limited Renovation and Cost-Optimal Heating", + scenarios = {"flexible": "Optimal Renovation and Cost-Optimal Heating", + "retro_tes": "Optimal Renovation and Electric Heating", + "flexible-moderate": "Limited Renovation and Cost-Optimal Heating", "rigid": "No Renovation and Electric Heating"} + scenario_abbrev = {"Optimal Renovation and Cost-Optimal Heating": "OROH", + "Optimal Renovation and Electric Heating": "OREH", + "Limited Renovation and Cost-Optimal Heating": "LROH", + "No Renovation and Electric Heating": "NREH"} # load networks networks = {} @@ -321,14 +395,14 @@ def calc_opex(network, per_MWh): # get electricity prices elec_bills_MWh = electricity_prices(network).rename(name) # OPEX for industry - opex = calc_opex(network, elec_bills_MWh).rename(name) + opex = calc_opex(network, scenario_abbrev[name]) # rename series name to scenario name elec_bills_household.name = name elec_bills_MWh.name = name # concatenate current results total_elec_bills = pd.concat([total_elec_bills, elec_bills_household.to_frame().T], axis=0) total_elec_prices = pd.concat([total_elec_prices, elec_bills_MWh.to_frame().T], axis=0) - industry_opex = pd.concat([industry_opex, opex.to_frame().T], axis=0) + industry_opex = pd.concat([industry_opex, opex], axis=1) # plot and store electricity bills if not total_elec_bills.empty: @@ -339,6 +413,6 @@ def calc_opex(network, per_MWh): plot_electricity_cost(total_elec_prices, "prices") # plot and store industry opex - if not total_elec_prices.empty: - plot_electricity_cost(total_elec_prices, "industry") + if not industry_opex.empty: + plot_industry_opex(industry_opex) From 6fbe0d9aaddd204b42161cccf81307f4e6043e4d Mon Sep 17 00:00:00 2001 From: yerbol-akhmetov Date: Wed, 31 Jul 2024 23:13:39 +0500 Subject: [PATCH 6/8] add arrows with annotations for heat saving plots --- plots/plot_heat_savings.py | 63 +++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/plots/plot_heat_savings.py b/plots/plot_heat_savings.py index d416cd7..fb722e0 100644 --- a/plots/plot_heat_savings.py +++ b/plots/plot_heat_savings.py @@ -74,6 +74,9 @@ def plot_elec_consumption_for_heat(dict_elec, full_year=False): ax = axes[i] ax.set_facecolor("whitesmoke") + # heat saved ratio + heat_saved_ratio = heat_demand.sum()["Heat savings by renovation"] / heat_demand.sum().sum() + print("Hard coded coordinates on x-axis, selected for 3H granularity") if not full_year: where = [359, 695] @@ -92,8 +95,46 @@ def plot_elec_consumption_for_heat(dict_elec, full_year=False): cumulative = (heat_demand / 1e3).cumsum(axis=1) ax.plot(cumulative.iloc[where[0]:where[1]], color='black', linewidth=0.5) + # x position for arrow + if not full_year: + x_loc = 436 + y_shift_arrow = 0 + x_shift_arrow = -24 + x_shift_after = 33 + x_shift_before = -65 + x_shift_mid = -12 + y_after = 1550 + y_before = 2000 + y_mid = 1800 + else: + x_loc = 90 + y_shift_arrow = -50 + x_shift_arrow = 0 + x_shift_after = 33 + x_shift_before = -15 + x_shift_mid = 20 + y_after = 746 + y_before = 1600 + y_mid = 1150 + + # Heating demand before renovation + ax.annotate('heat demand before renovation', xy=(x_loc+x_shift_arrow, cumulative['Heat savings by renovation'][x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_before, y_before), + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="#e39191") + + if not "BAU" == name: + # Heating demand after renovation + ax.annotate('heat demand after renovation', xy=(x_loc, cumulative['Net heat demand'][x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_after, y_after), + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="#a63f3f") + # Heating savings + mid_point = cumulative.sum(axis=1) / 2 + ax.annotate(f'saved heating demand ({100*heat_saved_ratio:.1f}%)', xy=(x_loc, mid_point[x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_mid, y_mid), + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="black") + ax.set_xlabel("", fontsize=12) - ax.set_ylim([1,1700]) + if full_year: + ax.set_ylim([1,2000]) + else: + ax.set_ylim([1,2200]) if i < 2: ax.set_xticks([]) @@ -126,15 +167,15 @@ def plot_elec_consumption_for_heat(dict_elec, full_year=False): ax.set_title(name, fontsize=10) i+= 1 - handles1, _ = axes[0].get_legend_handles_labels() - axes[0].legend( - reversed(handles1[0:7]), - [ - "Heat savings by renovation", - "Net heat demand", - ], - loc=[1.02, -.2], fontsize=10 - ) + # handles1, _ = axes[0].get_legend_handles_labels() + # axes[0].legend( + # reversed(handles1[0:7]), + # [ + # "Heat savings by renovation", + # "Net heat demand", + # ], + # loc=[1.02, -.2], fontsize=10 + # ) if len(dict_elec.keys()) == 1: ylabel = "Heat demand [$GW_{th}$]" axes[0].set_ylabel(ylabel, fontsize=10) @@ -275,6 +316,6 @@ def plot_flexibility(dict_elec): # plot heat demand data for short period plot_elec_consumption_for_heat(total_heat_data_with_flex, full_year=False) # plot heat demand data for full year - plot_elec_consumption_for_heat(total_heat_data, full_year=True) + plot_elec_consumption_for_heat(total_heat_data_with_flex, full_year=True) # plot heat flexibility plot_flexibility(total_flexibility) \ No newline at end of file From fab58549276333e644ae774d4cd7bb439e417c87 Mon Sep 17 00:00:00 2001 From: yerbol-akhmetov Date: Thu, 1 Aug 2024 21:09:05 +0500 Subject: [PATCH 7/8] plot after/before renovation for OROH only --- plots/plot_heat_savings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plots/plot_heat_savings.py b/plots/plot_heat_savings.py index fb722e0..9de2976 100644 --- a/plots/plot_heat_savings.py +++ b/plots/plot_heat_savings.py @@ -117,18 +117,18 @@ def plot_elec_consumption_for_heat(dict_elec, full_year=False): y_before = 1600 y_mid = 1150 - # Heating demand before renovation - ax.annotate('heat demand before renovation', xy=(x_loc+x_shift_arrow, cumulative['Heat savings by renovation'][x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_before, y_before), - arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="#e39191") - - if not "BAU" == name: + if name == "Optimal Renovation and Cost-Optimal Heating": + # Heating demand before renovation + ax.annotate('heat demand before renovation', xy=(x_loc+x_shift_arrow, cumulative['Heat savings by renovation'][x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_before, y_before), + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="black") # Heating demand after renovation ax.annotate('heat demand after renovation', xy=(x_loc, cumulative['Net heat demand'][x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_after, y_after), - arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="#a63f3f") + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="black") + if not name == "BAU": # Heating savings mid_point = cumulative.sum(axis=1) / 2 ax.annotate(f'saved heating demand ({100*heat_saved_ratio:.1f}%)', xy=(x_loc, mid_point[x_loc]+y_shift_arrow), xytext=(x_loc+x_shift_mid, y_mid), - arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="black") + arrowprops=dict(facecolor='black', edgecolor='black', arrowstyle='->', linewidth=0.75), fontsize=7, color="#a63f3f") ax.set_xlabel("", fontsize=12) if full_year: From 0525017092fb023832bf104d239fe8d9c0a4ce90 Mon Sep 17 00:00:00 2001 From: yerbol-akhmetov Date: Fri, 2 Aug 2024 20:29:37 +0500 Subject: [PATCH 8/8] add land usage into infra saving script --- Snakefile | 11 +++++++++++ plots/_helpers.py | 5 +++++ plots/table_infra_savings.py | 10 +++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Snakefile b/Snakefile index 479cb03..799be10 100644 --- a/Snakefile +++ b/Snakefile @@ -321,6 +321,7 @@ rule get_infra_saving: output: table_cap=RESULTS+"table_infra_savings_caps_{clusters}.csv", table_costs=RESULTS+"table_infra_savings_costs_{clusters}.csv", + table_land=RESULTS+"table_infra_savings_land_{clusters}.csv", resources: mem_mb=20000, script: @@ -339,6 +340,11 @@ rule get_infra_savings: + "table_infra_savings_costs_{clusters}.csv", **config["plotting"], ), + expand( + RESULTS + + "table_infra_savings_land_{clusters}.csv", + **config["plotting"], + ), rule plot_transmission_congestion: @@ -497,6 +503,11 @@ rule plot_all: + "table_infra_savings_costs_{clusters}.csv", **config["plotting"], ), + expand( + RESULTS + + "table_infra_savings_land_{clusters}.csv", + **config["plotting"], + ), expand( RESULTS + "table_res_share_{clusters}.csv", diff --git a/plots/_helpers.py b/plots/_helpers.py index 2fc3c1c..e991279 100644 --- a/plots/_helpers.py +++ b/plots/_helpers.py @@ -178,6 +178,11 @@ "NL": 103.0, "NO": 74.7, "PL": 87.0, "PT": 112.0, "RO": 110.9, "RS": 114.0, "SE": 66.0, "SI": 115.0, "SK": 102.8} +# land usage for 1 MW nameplate capacity of wind (34.5 ha = 0.345 km^2) URL: https://www.nrel.gov/docs/fy09osti/45834.pdf +LAND_FOR_WIND = 0.345 + +# land usage for 1 MW nameplate capacity of solar (4-5 acres = 0.02 km^2) URL: https://amplussolar.com/blogs/1mw-solar-power-plant +LAND_FOR_SOLAR = 0.02 def rename_techs(label): diff --git a/plots/table_infra_savings.py b/plots/table_infra_savings.py index 623632b..f49bb1c 100644 --- a/plots/table_infra_savings.py +++ b/plots/table_infra_savings.py @@ -13,7 +13,7 @@ warnings.filterwarnings("ignore") from _helpers import mock_snakemake, update_config_from_wildcards, load_network, \ change_path_to_pypsa_eur, change_path_to_base, \ - LINE_LIMITS, CO2L_LIMITS, BAU_HORIZON, replace_multiindex_values + LINE_LIMITS, CO2L_LIMITS, BAU_HORIZON, LAND_FOR_WIND, LAND_FOR_SOLAR from plot_total_costs import compute_costs @@ -62,8 +62,10 @@ ("2050", "wind"), ("2050", "solar"), ("2050", "gas") ] ) + land_usage = df_savings.copy() df_savings.columns = pd.MultiIndex.from_tuples(df_savings.columns, names=['horizon','Installed capacity [GW]']) cost_savings.columns = pd.MultiIndex.from_tuples(cost_savings.columns, names=['horizon','Capital cost [BEur]']) + land_usage.columns = pd.MultiIndex.from_tuples(land_usage.columns, names=['horizon','Land usage [km2]']) for planning_horizon in planning_horizons: lineex = line_limits[planning_horizon] @@ -101,6 +103,10 @@ gas_costs_carriers = ["Store:gas", "Link:Open-Cycle Gas"] cost_savings.loc[nice_name, (planning_horizon, "gas")] = (cap_costs.loc[gas_costs_carriers].sum()[0] / 1e9).round(2) + # land usage in thousand km^2 + land_usage.loc[nice_name, (planning_horizon, "solar")] = (solar * LAND_FOR_SOLAR).round(2) + land_usage.loc[nice_name, (planning_horizon, "wind")] = (wind * LAND_FOR_WIND).round(2) + # add name for columns df_savings.index.name = "Scenario [GW]" @@ -112,4 +118,6 @@ df_savings.to_csv(snakemake.output.table_cap) cost_savings.index = ["Limited Renovation and Cost-Optimal/Electric Heating" if s == "Limited Renovation and Cost-Optimal Heating" else s for s in cost_savings.index] cost_savings.to_csv(snakemake.output.table_costs) + land_usage.index = ["Limited Renovation and Cost-Optimal/Electric Heating" if s == "Limited Renovation and Cost-Optimal Heating" else s for s in land_usage.index] + land_usage.to_csv(snakemake.output.table_land)