diff --git a/Snakefile b/Snakefile index 1ddda951b..1f46f2265 100644 --- a/Snakefile +++ b/Snakefile @@ -281,6 +281,7 @@ rule modify_district_heat_share: rule modify_prenetwork: params: + efuel_export_ban=config_provider("solving", "constraints", "efuel_export_ban"), enable_kernnetz=config_provider("wasserstoff_kernnetz", "enable"), costs=config_provider("costs"), max_hours=config_provider("electricity", "max_hours"), @@ -476,7 +477,7 @@ rule cluster_wasserstoff_kernnetz: rule download_ariadne_template: input: storage( - "https://github.com/iiasa/ariadne-intern-workflow/raw/main/attachments/2024-11-28_template_Ariadne.xlsx", + "https://github.com/iiasa/ariadne-intern-workflow/raw/main/attachments/2025-01-27_template_Ariadne.xlsx", keep_local=True, ), output: diff --git a/config/config.yaml b/config/config.yaml index 97fcee4da..b5ecae3c4 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: 20250124-finalfixes + prefix: 20250130-morefinalfixes name: # - CurrentPolicies - KN2045_Bal_v4 @@ -432,6 +432,7 @@ solving: H2 pipeline retrofitted: 0.05 fractional_last_unit_size: true constraints: + efuel_export_ban: true limits_capacity_max: Generator: onwind: diff --git a/config/scenarios.manual.yaml b/config/scenarios.manual.yaml index e1350702d..831879b05 100644 --- a/config/scenarios.manual.yaml +++ b/config/scenarios.manual.yaml @@ -896,4 +896,3 @@ KN2045_Bal_HighDemand: 2035: 0.18 2040: 0.24 2045: 0.29 - diff --git a/scripts/build_existing_heating_distribution.py b/scripts/build_existing_heating_distribution.py index 8467dbe17..85c2419a8 100644 --- a/scripts/build_existing_heating_distribution.py +++ b/scripts/build_existing_heating_distribution.py @@ -57,14 +57,6 @@ def build_existing_heating(): # data is for buildings only (i.e. NOT district heating) and represents the year 2012 # TODO start from original file - # Add existing heating capacities, data comes from the study - # "Mapping and analyses of the current and future (2020 - 2030) - # heating/cooling fuel deployment (fossil/renewables) " - # https://energy.ec.europa.eu/publications/mapping-and-analyses-current-and-future-2020-2030-heatingcooling-fuel-deployment-fossilrenewables-1_en - # file: "WP2_DataAnnex_1_BuildingTechs_ForPublication_201603.xls" -> "existing_heating_raw.csv". - # data is for buildings only (i.e. NOT district heating) and represents the year 2012 - # TODO start from original file - existing_heating = pd.read_csv( snakemake.input.existing_heating, index_col=0, header=0 ) diff --git a/scripts/pypsa-de/additional_functionality.py b/scripts/pypsa-de/additional_functionality.py index e5a1b347b..d10cb4acc 100644 --- a/scripts/pypsa-de/additional_functionality.py +++ b/scripts/pypsa-de/additional_functionality.py @@ -668,44 +668,7 @@ 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="", - ) + # Export bans on efuels are implemented in modify_prenetwork by restricting p_max_pu of the DE -> EU links def adapt_nuclear_output(n): diff --git a/scripts/pypsa-de/build_scenarios.py b/scripts/pypsa-de/build_scenarios.py index bc256fc3d..9475c4fa7 100644 --- a/scripts/pypsa-de/build_scenarios.py +++ b/scripts/pypsa-de/build_scenarios.py @@ -178,9 +178,7 @@ def write_to_scenario_yaml(input, output, scenarios, df): # f"For CO2 budget: Using {fallback_reference_scenario} as fallback reference scenario for {scenario}." # ) co2_budget_fractions = get_co2_budget( - df.loc[ - snakemake.params.leitmodelle["general"], reference_scenario - ], + df.loc[snakemake.params.leitmodelle["general"], reference_scenario], co2_budget_source, ) diff --git a/scripts/pypsa-de/export_ariadne_variables.py b/scripts/pypsa-de/export_ariadne_variables.py index 39ac81bbe..615396455 100644 --- a/scripts/pypsa-de/export_ariadne_variables.py +++ b/scripts/pypsa-de/export_ariadne_variables.py @@ -16,8 +16,8 @@ from numpy import isclose from scripts._helpers import configure_logging, mock_snakemake -from scripts.prepare_sector_network import prepare_costs from scripts.add_electricity import calculate_annuity +from scripts.prepare_sector_network import prepare_costs logger = logging.getLogger(__name__) diff --git a/scripts/pypsa-de/modify_cost_data.py b/scripts/pypsa-de/modify_cost_data.py index 56b6a8308..c8ae7b56c 100644 --- a/scripts/pypsa-de/modify_cost_data.py +++ b/scripts/pypsa-de/modify_cost_data.py @@ -156,7 +156,12 @@ def carbon_component_fossils(costs, co2_price): ) costs.at[("Fischer-Tropsch", "efficiency"), "source"] = "inverse of hydrogen-input" + logger.info( + f"Setting Fischer-Tropsch efficiency to 1 / hydrogen-input. New value: {costs.loc['Fischer-Tropsch', 'efficiency'].value} {costs.loc['Fischer-Tropsch', 'efficiency'].unit}." + ) + # increase FOM of offshore wind connection (fix for costs.csv) + logger.info(f"Setting FOM of offshore wind connections to 0.35 %.") costs.loc[("offwind-dc-connection-submarine", "FOM"), "value"] = 0.35 costs.loc[("offwind-dc-connection-submarine", "FOM"), "unit"] = costs.at[ ("offwind", "FOM"), "unit" @@ -174,4 +179,7 @@ def carbon_component_fossils(costs, co2_price): ("offwind", "FOM"), "unit" ] + logger.info(f"Setting investment cost of hydrogen storage to 0.55 EUR/kWh.") + costs.loc[("hydrogen storage underground", "investment"), "value"] = 0.55 + costs.to_csv(snakemake.output[0]) diff --git a/scripts/pypsa-de/modify_existing_heating.py b/scripts/pypsa-de/modify_existing_heating.py index d3cb06d09..93492b60f 100644 --- a/scripts/pypsa-de/modify_existing_heating.py +++ b/scripts/pypsa-de/modify_existing_heating.py @@ -17,23 +17,9 @@ configure_logging(snakemake) - leitmodell = snakemake.params.leitmodelle["buildings"] - logger.info(f"Using {leitmodell} for heating demand modification.") - existing_heating = pd.read_csv(snakemake.input.existing_heating, index_col=0) - ariadne = pd.read_csv( - snakemake.input.ariadne, - index_col=["model", "scenario", "region", "variable", "unit"], - ).loc[ - leitmodell, - snakemake.params.fallback_reference_scenario, - "Deutschland", - :, - "million", - ] - - logger.info("Heating demand before modification:{existing_heating.loc['Germany']}") + logger.info(f"Heating demand before modification:{existing_heating.loc['Germany']}") mapping = { "gas boiler": "Gas Boiler", @@ -43,22 +29,26 @@ "biomass boiler": "Biomass Boiler", } - year = "2020" - for tech in mapping: - stock = ariadne.at[ - f"Stock|Space Heating|{mapping[tech]}", - year, - ] - - peak = ( - stock - * existing_heating.loc["Germany"].sum() - / ariadne.at[f"Stock|Space Heating", year] - ) + new_values = pd.Series() + + logger.warning( + f"Adjusting heating stock towards hard coded values from a previous REMod run. This is only a hotfix." + ) # Because REMod is not consistent and a better solution takes too long. + + new_values["gas boiler"] = 11.44 # million + new_values["oil boiler"] = 5.99 + new_values["air heat pump"] = 0.38 + new_values["ground heat pump"] = 0.38 + new_values["biomass boiler"] = 2.8 + + total_stock = new_values.sum() + existing_factor = existing_heating.loc["Germany"].sum() / total_stock + + new_values *= existing_factor + + for tech, peak in new_values.items(): existing_heating.at["Germany", tech] = peak - logger.info( - f"Heating demand after modification with {leitmodell}: {existing_heating.loc['Germany']}" - ) + logger.info(f"Heating demand after modification: {existing_heating.loc['Germany']}") existing_heating.to_csv(snakemake.output.existing_heating) diff --git a/scripts/pypsa-de/modify_prenetwork.py b/scripts/pypsa-de/modify_prenetwork.py index e42dfcb43..cb9dc5032 100644 --- a/scripts/pypsa-de/modify_prenetwork.py +++ b/scripts/pypsa-de/modify_prenetwork.py @@ -405,6 +405,12 @@ def unravel_carbonaceous_fuels(n): marginal_cost=0.01, ) + if snakemake.params.efuel_export_ban: + logger.info( + "Efuel export ban: Setting p_max_pu to 0 for DE renewable oil -> EU oil" + ) + n.links.loc["DE renewable oil -> EU oil", "p_max_pu"] = 0 + n.add( "Link", [ @@ -482,6 +488,12 @@ def unravel_carbonaceous_fuels(n): marginal_cost=0.01, ) + if snakemake.params.efuel_export_ban: + logger.info( + "Efuel export ban: Setting p_max_pu to 0 for DE methanol -> EU methanol" + ) + n.links.loc["DE methanol -> EU methanol", "p_max_pu"] = 0 + # add stores EU_meoh_store = n.stores.loc["EU methanol Store"].copy() n.add( @@ -702,6 +714,12 @@ def unravel_gasbus(n, costs): marginal_cost=0.01, ) + if snakemake.params.efuel_export_ban: + logger.info( + "Efuel export ban: Setting p_max_pu to 0 for DE renewable gas -> EU gas" + ) + n.links.loc["DE renewable gas -> EU gas", "p_max_pu"] = 0 + ### add links between renewable and fossil gas buses n.add( "Link", diff --git a/scripts/pypsa-de/plot_ariadne_report.py b/scripts/pypsa-de/plot_ariadne_report.py index 444271f4d..e0b17027b 100644 --- a/scripts/pypsa-de/plot_ariadne_report.py +++ b/scripts/pypsa-de/plot_ariadne_report.py @@ -1475,7 +1475,6 @@ def plot_elec_prices_spatial( df = onshore_regions df["elec_price"] = n.buses_t.marginal_price[buses].mean() - # Netzentgelte, Annuität NEP 2045 - Annuität PyPSA 2045 / Stromverbrauch Pypsa 2045 pypsa_netzentgelt = (15.82 - 6.53) / 1.237 elec_price_de = df["elec_price"][df.index.str.contains("DE")]