diff --git a/config/config.default.yaml b/config/config.default.yaml index 1fe8d262d..57faeb9e0 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -498,7 +498,7 @@ sector: ICE_upper_degree_factor: 1.6 EV_lower_degree_factor: 0.98 EV_upper_degree_factor: 0.63 - bev_dsm: true + bev_dsm: 2030 bev_availability: 0.5 bev_energy: 0.05 bev_charge_efficiency: 0.9 diff --git a/config/config.yaml b/config/config.yaml index 795271aa1..97fcee4da 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -4,7 +4,7 @@ # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run run: - prefix: 20250122-demand-scenarios + prefix: 20250124-finalfixes name: # - CurrentPolicies - KN2045_Bal_v4 @@ -438,8 +438,8 @@ solving: DE: 2020: 54.5 2025: 69 - 2030: 157 # EEG2023 Ziel für 2035 - 2035: 250 + 2030: 115 # EEG2023 Ziel für 2030 + 2035: 160 # EEG2023 Ziel für 2040 2040: 250 2045: 250 offwind: @@ -447,16 +447,16 @@ solving: 2020: 7.8 2025: 11.3 2030: 29.3 # uba Projektionsbericht and NEP without delayed BalWin 3 - 2035: 70 - 2040: 70 + 2035: 50 # Planned projects until 2035 (offshore_connection_points.csv) -1.3 GW for potential delays + 2040: 65 # Planned projects until 2040 -1.5 GW for potential retirments 2045: 70 solar: DE: 2020: 53.7 2025: 110 # EEG2023; assumes for 2026: 128 GW, assuming a fair share reached by end of 2025 - 2030: 309 # EEG2023 Ziel für 2035 - 2035: 1000 - 2040: 1000 + 2030: 235 # PV Ziel 2030 + 20 GW + 2035: 400 + 2040: 800 2045: 1000 Store: co2 sequestered: diff --git a/config/scenarios.manual.yaml b/config/scenarios.manual.yaml index da22a8d2c..e1350702d 100644 --- a/config/scenarios.manual.yaml +++ b/config/scenarios.manual.yaml @@ -57,9 +57,7 @@ CurrentPolicies: 2020: 7.8 2025: 11.3 2030: 17.3 # 12 less than NEP, because of 1 year delay - 2035: 35 # 29.3 + a little extra - 2040: 70 - 2045: 70 + 2035: 40 # 29.3 + half of extra projects onwind: DE: 2030: 94.5 # uba Projektionsbericht @@ -605,9 +603,7 @@ KN2045minus_WorstCase: 2020: 7.8 2025: 11.3 2030: 17.3 # 12 less than NEP, because of 1 year delay - 2035: 35 # 29.3 + a little extra - 2040: 70 - 2045: 70 + 2035: 40 # 29.3 + half of extra projects onwind: DE: 2030: 94.5 # uba Projektionsbericht diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index eeee192eb..96ebe8ab3 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -38,7 +38,7 @@ ICE_lower_degree_factor,--,float,Share increase in energy demand in internal com ICE_upper_degree_factor,--,float,Share increase in energy demand in internal combustion engine (ICE) for each degree difference between the hot environment and the maximum temperature. EV_lower_degree_factor,--,float,Share increase in energy demand in electric vehicles (EV) for each degree difference between the cold environment and the minimum temperature. EV_upper_degree_factor,--,float,Share increase in energy demand in electric vehicles (EV) for each degree difference between the hot environment and the maximum temperature. -bev_dsm,--,"{true, false}",Add the option for battery electric vehicles (BEV) to participate in demand-side management (DSM) +bev_dsm,--,"{true, false} or startyear as int",Add the option for battery electric vehicles (BEV) to participate in demand-side management (DSM). If an int is passed it is interpreted as the year from which on BEV DSM is available. ,,, bev_availability,--,float,The share for battery electric vehicles (BEV) that are able to do demand side management (DSM) bev_energy,--,float,The average size of battery electric vehicles (BEV) in MWh diff --git a/scripts/_helpers.py b/scripts/_helpers.py index c65e9e59e..f2bb56f9b 100644 --- a/scripts/_helpers.py +++ b/scripts/_helpers.py @@ -471,7 +471,7 @@ def mock_snakemake( else: root_dir = Path(root_dir).resolve() - user_in_script_dir = Path.cwd().resolve() == script_dir + user_in_script_dir = Path.cwd().resolve().is_relative_to(Path(script_dir)) if str(submodule_dir) in __file__: # the submodule_dir path is only need to locate the project dir os.chdir(Path(__file__[: __file__.find(str(submodule_dir))])) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 628acaac9..7508e4faa 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2029,7 +2029,7 @@ def add_EVs( efficiency=options["bev_charge_efficiency"], ) - if options["bev_dsm"]: + if options["bev_dsm"] and options["bev_dsm"] <= investment_year: e_nom = ( number_cars * options["bev_energy"] diff --git a/scripts/pypsa-de/additional_functionality.py b/scripts/pypsa-de/additional_functionality.py index 464c92aed..e5a1b347b 100644 --- a/scripts/pypsa-de/additional_functionality.py +++ b/scripts/pypsa-de/additional_functionality.py @@ -208,6 +208,27 @@ def h2_import_limits(n, investment_year, limits_volume_max): carrier_attribute="", ) + logger.info("Adding H2 export ban") + + cname = f"H2_export_ban-{ct}" + + n.model.add_constraints(lhs >= 0, name=f"GlobalConstraint-{cname}") + + if cname in n.global_constraints.index: + logger.warning( + f"Global constraint {cname} already exists. Dropping and adding it again." + ) + n.global_constraints.drop(cname, inplace=True) + + n.add( + "GlobalConstraint", + cname, + constant=0, + sense=">=", + type="", + carrier_attribute="", + ) + def h2_production_limits(n, investment_year, limits_volume_min, limits_volume_max): for ct in limits_volume_max["electrolysis"]: @@ -647,6 +668,45 @@ def add_h2_derivate_limit(n, investment_year, limits_volume_max): carrier_attribute="", ) + # The following export bans for DE are added unconditionally, independent of config + ct = "DE" + logger.info("Adding net export bans for H2 derivatives in DE") + + for incarrier, outcarrier in [ + ("EU methanol -> DE methanol", "DE methanol -> EU methanol"), + ("EU renewable gas -> DE gas", "DE renewable gas -> EU gas"), + ("EU renewable oil -> DE oil", "DE renewable oil -> EU oil"), + ]: + incoming = n.links.index[n.links.index == incarrier] + outgoing = n.links.index[n.links.index == outcarrier] + incoming_p = ( + n.model["Link-p"].loc[:, incoming] * n.snapshot_weightings.generators + ).sum() + outgoing_p = ( + n.model["Link-p"].loc[:, outgoing] * n.snapshot_weightings.generators + ).sum() + + lhs = incoming_p - outgoing_p + + cname = f"renewable{incarrier.split()[-1]}_export_ban-{ct}" + + n.model.add_constraints(lhs >= 0, name=f"GlobalConstraint-{cname}") + + if cname in n.global_constraints.index: + logger.warning( + f"Global constraint {cname} already exists. Dropping and adding it again." + ) + n.global_constraints.drop(cname, inplace=True) + + n.add( + "GlobalConstraint", + cname, + constant=0, + sense=">=", + type="", + carrier_attribute="", + ) + def adapt_nuclear_output(n): logger.info( diff --git a/scripts/pypsa-de/export_ariadne_variables.py b/scripts/pypsa-de/export_ariadne_variables.py index 95080c7d5..d7f8987bf 100644 --- a/scripts/pypsa-de/export_ariadne_variables.py +++ b/scripts/pypsa-de/export_ariadne_variables.py @@ -5,6 +5,9 @@ import os import re import sys + +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + "/../..")) + from functools import reduce import numpy as np @@ -248,7 +251,9 @@ def _get_h2_fossil_fraction(n): .sum() ) - h2_fossil_fraction = total_h2_supply.get("SMR") / total_h2_supply.sum() + h2_fossil_fraction = ( + total_h2_supply.filter(like="SMR").sum() / total_h2_supply.sum() + ) return h2_fossil_fraction diff --git a/scripts/pypsa-de/modify_cost_data.py b/scripts/pypsa-de/modify_cost_data.py index 22dbefe82..56b6a8308 100644 --- a/scripts/pypsa-de/modify_cost_data.py +++ b/scripts/pypsa-de/modify_cost_data.py @@ -150,4 +150,28 @@ def carbon_component_fossils(costs, co2_price): f"Setting lifetime of central gas CHP to {costs.at[("central gas CHP" , "lifetime") , "value"]} {costs.at[("central gas CHP" , "lifetime") , "unit"]}." ) + # decrease Fischer-Tropsch efficiency + costs.at[("Fischer-Tropsch", "efficiency"), "value"] = ( + 1 / costs.at[("Fischer-Tropsch", "hydrogen-input"), "value"] + ) + costs.at[("Fischer-Tropsch", "efficiency"), "source"] = "inverse of hydrogen-input" + + # increase FOM of offshore wind connection (fix for costs.csv) + costs.loc[("offwind-dc-connection-submarine", "FOM"), "value"] = 0.35 + costs.loc[("offwind-dc-connection-submarine", "FOM"), "unit"] = costs.at[ + ("offwind", "FOM"), "unit" + ] + costs.loc[("offwind-dc-connection-underground", "FOM"), "value"] = 0.35 + costs.loc[("offwind-dc-connection-underground", "FOM"), "unit"] = costs.at[ + ("offwind", "FOM"), "unit" + ] + costs.loc[("offwind-ac-connection-submarine", "FOM"), "value"] = 0.35 + costs.loc[("offwind-ac-connection-submarine", "FOM"), "unit"] = costs.at[ + ("offwind", "FOM"), "unit" + ] + costs.loc[("offwind-ac-connection-underground", "FOM"), "value"] = 0.35 + costs.loc[("offwind-ac-connection-underground", "FOM"), "unit"] = costs.at[ + ("offwind", "FOM"), "unit" + ] + costs.to_csv(snakemake.output[0]) diff --git a/scripts/pypsa-de/plot_ariadne_report.py b/scripts/pypsa-de/plot_ariadne_report.py index d72016454..806a1c83d 100644 --- a/scripts/pypsa-de/plot_ariadne_report.py +++ b/scripts/pypsa-de/plot_ariadne_report.py @@ -16,17 +16,16 @@ import numpy as np import pandas as pd import pypsa +from export_ariadne_variables import get_discretized_value, process_postnetworks from matplotlib.lines import Line2D from matplotlib.patches import Patch from matplotlib.ticker import FuncFormatter -from pypsa.plot import add_legend_lines +from pypsa.plot import add_legend_circles, add_legend_lines, add_legend_patches from scripts._helpers import configure_logging, mock_snakemake, set_scenario_config -from scripts.export_ariadne_variables import get_discretized_value, process_postnetworks from scripts.plot_power_network import load_projection from scripts.plot_summary import preferred_order, rename_techs from scripts.prepare_sector_network import prepare_costs -from scripts.pypsa.plot import add_legend_circles, add_legend_lines, add_legend_patches logger = logging.getLogger(__name__) @@ -2449,7 +2448,6 @@ def plot_elec_map_de( frameon=True, facecolor="white", fontsize=14, - ) add_legend_patches(ax, colors, labels, legend_kw=legend_kw_patches) diff --git a/scripts/pypsa-de/plot_ariadne_variables.py b/scripts/pypsa-de/plot_ariadne_variables.py index ce3d79236..760b9b0d5 100644 --- a/scripts/pypsa-de/plot_ariadne_variables.py +++ b/scripts/pypsa-de/plot_ariadne_variables.py @@ -6,6 +6,7 @@ import pandas as pd from scripts._helpers import mock_snakemake + TWh2PJ = 3.6