From fe5406e988ef264b2b0db1cff63b1b3b300fd48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Wed, 12 Mar 2025 16:04:39 +0100 Subject: [PATCH 1/3] Data added for scneario 2037.2025 --- .../scenario_parameters/parameters.py | 198 +++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/src/egon/data/datasets/scenario_parameters/parameters.py b/src/egon/data/datasets/scenario_parameters/parameters.py index ed8304c30..2d9c5041c 100755 --- a/src/egon/data/datasets/scenario_parameters/parameters.py +++ b/src/egon/data/datasets/scenario_parameters/parameters.py @@ -71,8 +71,33 @@ def global_settings(scenario): List of global parameters """ + if scenario == "eGon2037.2025": + parameters = { + "weather_year": 2011, + "population_year": 2037, + "fuel_costs": { # Szenarionrahmen zum NEP 2025, Version 2025, 1. Entwurf, + "oil": 21.3, # [EUR/MWh] + "gas": 15.2, # [EUR/MWh] + "coal": 6.1, # [EUR/MWh] + "lignite": 6.4, # [EUR/MWh] + "nuclear": 1.7, # [EUR/MWh] + "biomass": 40, # Dummyvalue, ToDo: Find a suitable source + }, + "co2_costs": 76.5, # [EUR/t_CO2] + "co2_emissions": { # Szenarionrahmen zum NEP 2025, Version 2025, 1. Entwurf, + "waste": 0.165, # [t_CO2/MW_th] + "lignite": 0.393, # [t_CO2/MW_th] + "gas": 0.201, # [t_CO2/MW_th] + "nuclear": 0.0, # [t_CO2/MW_th] + "oil": 0.286, # [t_CO2/MW_th] + "coal": 0.377, # [t_CO2/MW_th] + "other_non_renewable": 0.268, # [t_CO2/MW_th] + # hydrogen: 0 + }, + "interest_rate": 0.05, # [p.u.] + } - if scenario == "eGon2035": + elif scenario == "eGon2035": parameters = { "weather_year": 2011, "population_year": 2035, @@ -146,6 +171,177 @@ def electricity(scenario): List of parameters of electricity sector """ + if scenario == "eGon2037.2025": + + costs = read_csv(2037) + + parameters = {"grid_topology": "Status Quo"} + # Insert effciencies in p.u. + parameters["efficiency"] = { + "oil": read_costs(costs, "oil", "efficiency"), + "battery": { + "store": read_costs(costs, "battery inverter", "efficiency") + ** 0.5, + "dispatch": read_costs(costs, "battery inverter", "efficiency") + ** 0.5, + "standing_loss": 0, + "max_hours": 6, + }, + "pumped_hydro": { + "store": read_costs(costs, "PHS", "efficiency") ** 0.5, + "dispatch": read_costs(costs, "PHS", "efficiency") ** 0.5, + "standing_loss": 0, + "max_hours": 6, + }, + } + # Warning: Electrical parameters are set in osmTGmod, editing these values will not change the data! + parameters["electrical_parameters"] = { + "ac_line_110kV": { + "s_nom": 260, # [MVA] + "R": 0.109, # [Ohm/km] + "L": 1.2, # [mH/km] + }, + "ac_cable_110kV": { + "s_nom": 280, # [MVA] + "R": 0.0177, # [Ohm/km] + "L": 0.3, # [mH/km] + }, + "ac_line_220kV": { + "s_nom": 520, # [MVA] + "R": 0.109, # [Ohm/km] + "L": 1.0, # [mH/km] + }, + "ac_cable_220kV": { + "s_nom": 550, # [MVA] + "R": 0.0176, # [Ohm/km] + "L": 0.3, # [mH/km] + }, + "ac_line_380kV": { + "s_nom": 1790, # [MVA] + "R": 0.028, # [Ohm/km] + "L": 0.8, # [mH/km] + }, + "ac_cable_380kV": { + "s_nom": 925, # [MVA] + "R": 0.0175, # [Ohm/km] + "L": 0.3, # [mH/km] + }, + } + + # Insert overnight investment costs + # Source for eHV grid costs: Netzentwicklungsplan Strom 2035, Version 2021, 2. Entwurf + # Source for HV lines and cables: Dena Verteilnetzstudie 2021, p. 146 + parameters["overnight_cost"] = { + "ac_ehv_overhead_line": 2.5e6 + / ( + 2 + * parameters["electrical_parameters"]["ac_line_380kV"]["s_nom"] + ), # [EUR/km/MW] + "ac_ehv_cable": 11.5e6 + / ( + 2 + * parameters["electrical_parameters"]["ac_cable_380kV"][ + "s_nom" + ] + ), # [EUR/km/MW] + "ac_hv_overhead_line": 0.06e6 + / parameters["electrical_parameters"]["ac_line_110kV"][ + "s_nom" + ], # [EUR/km/MW] + "ac_hv_cable": 0.8e6 + / parameters["electrical_parameters"]["ac_cable_110kV"][ + "s_nom" + ], # [EUR/km/MW] + "dc_overhead_line": 0.5e3, # [EUR/km/MW] + "dc_cable": 3.25e3, # [EUR/km/MW] + "dc_inverter": 0.3e6, # [EUR/MW] + "transformer_380_110": 17.33e3, # [EUR/MVA] + "transformer_380_220": 13.33e3, # [EUR/MVA] + "transformer_220_110": 17.5e3, # [EUR/MVA] + "battery inverter": read_costs( + costs, "battery inverter", "investment" + ), + "battery storage": read_costs( + costs, "battery storage", "investment" + ), + } + + parameters["lifetime"] = { + "ac_ehv_overhead_line": read_costs( + costs, "HVAC overhead", "lifetime" + ), + "ac_ehv_cable": read_costs(costs, "HVAC overhead", "lifetime"), + "ac_hv_overhead_line": read_costs( + costs, "HVAC overhead", "lifetime" + ), + "ac_hv_cable": read_costs(costs, "HVAC overhead", "lifetime"), + "dc_overhead_line": read_costs(costs, "HVDC overhead", "lifetime"), + "dc_cable": read_costs(costs, "HVDC overhead", "lifetime"), + "dc_inverter": read_costs(costs, "HVDC inverter pair", "lifetime"), + "transformer_380_110": read_costs( + costs, "HVAC overhead", "lifetime" + ), + "transformer_380_220": read_costs( + costs, "HVAC overhead", "lifetime" + ), + "transformer_220_110": read_costs( + costs, "HVAC overhead", "lifetime" + ), + "battery inverter": read_costs( + costs, "battery inverter", "lifetime" + ), + "battery storage": read_costs( + costs, "battery storage", "lifetime" + ), + } + # Insert annualized capital costs + # lines in EUR/km/MW/a + # transfermer, inverter, battery in EUR/MW/a + parameters["capital_cost"] = {} + + for comp in parameters["overnight_cost"].keys(): + parameters["capital_cost"][comp] = annualize_capital_costs( + parameters["overnight_cost"][comp], + parameters["lifetime"][comp], + global_settings("eGon2035")["interest_rate"], + ) + + parameters["capital_cost"]["battery"] = ( + parameters["capital_cost"]["battery inverter"] + + parameters["efficiency"]["battery"]["max_hours"] + * parameters["capital_cost"]["battery storage"] + ) + + # Insert marginal_costs in EUR/MWh + # marginal cost can include fuel, C02 and operation and maintenance costs + parameters["marginal_cost"] = { + "oil": global_settings(scenario)["fuel_costs"]["oil"] + + read_costs(costs, "oil", "VOM") + + global_settings(scenario)["co2_costs"] + * global_settings(scenario)["co2_emissions"]["oil"], + "other_non_renewable": global_settings(scenario)["fuel_costs"][ + "gas" + ] + + global_settings(scenario)["co2_costs"] + * global_settings(scenario)["co2_emissions"][ + "other_non_renewable" + ], + "lignite": global_settings(scenario)["fuel_costs"]["lignite"] + + read_costs(costs, "lignite", "VOM") + + global_settings(scenario)["co2_costs"] + * global_settings(scenario)["co2_emissions"]["lignite"], + "coal": global_settings(scenario)["fuel_costs"]["coal"] + + read_costs(costs, "coal", "VOM") + + global_settings(scenario)["co2_costs"] + * global_settings(scenario)["co2_emissions"]["coal"], + "nuclear": global_settings(scenario)["fuel_costs"]["nuclear"] + + read_costs(costs, "nuclear", "VOM"), + "biomass": global_settings(scenario)["fuel_costs"]["biomass"] + + read_costs(costs, "biomass CHP", "VOM"), + "wind_offshore": read_costs(costs, "offwind", "VOM"), + "wind_onshore": read_costs(costs, "onwind", "VOM"), + "solar": read_costs(costs, "solar", "VOM"), + } if scenario == "eGon2035": From 3692428ba448f93cbd29ae485fcbd37e2424bc59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Wed, 12 Mar 2025 16:36:01 +0100 Subject: [PATCH 2/3] correct scenatio name --- src/egon/data/datasets/scenario_parameters/parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egon/data/datasets/scenario_parameters/parameters.py b/src/egon/data/datasets/scenario_parameters/parameters.py index 2d9c5041c..1f4e05d9d 100755 --- a/src/egon/data/datasets/scenario_parameters/parameters.py +++ b/src/egon/data/datasets/scenario_parameters/parameters.py @@ -71,7 +71,7 @@ def global_settings(scenario): List of global parameters """ - if scenario == "eGon2037.2025": + if scenario == "nep2037.2025": parameters = { "weather_year": 2011, "population_year": 2037, From 69ec21258f130a5d728b5ffa89b40951e97ef225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Schl=C3=B6sser?= Date: Thu, 24 Apr 2025 14:44:59 +0200 Subject: [PATCH 3/3] updated scenario nep2037_2025 --- src/egon/data/airflow/dags/pipeline.py | 20 + src/egon/data/cli.py | 2 +- src/egon/data/datasets.yml | 27 +- src/egon/data/datasets/DSM_cts_ind.py | 58 +- src/egon/data/datasets/ch4_prod.py | 14 +- src/egon/data/datasets/ch4_storages.py | 21 +- src/egon/data/datasets/chp/__init__.py | 149 +- src/egon/data/datasets/chp/match_nep.py | 74 +- src/egon/data/datasets/chp/small_chp.py | 51 +- src/egon/data/datasets/chp_etrago.py | 2 +- .../data/datasets/demandregio/__init__.py | 43 +- .../district_heating_areas/__init__.py | 32 +- .../datasets/district_heating_areas/plot.py | 6 +- .../data/datasets/electrical_neighbours.py | 186 +- .../datasets/electricity_demand/__init__.py | 7 +- .../cts_buildings.py | 21 +- .../hh_buildings.py | 7 +- .../hh_profiles.py | 28 +- .../heavy_duty_transport/__init__.py | 14 +- .../motorized_individual_travel/__init__.py | 3 + .../model_timeseries.py | 13 +- src/egon/data/datasets/gas_areas.py | 10 + src/egon/data/datasets/gas_grid.py | 16 +- .../data/datasets/gas_neighbours/__init__.py | 16 +- .../datasets/gas_neighbours/gas_abroad.py | 61 +- .../datasets/gas_neighbours/nep2037_2025.py | 1587 +++++++++++++++++ .../heat_demand_timeseries/__init__.py | 11 +- .../data/datasets/heat_supply/__init__.py | 2 +- .../datasets/heat_supply/district_heating.py | 6 +- .../heat_supply/individual_heating.py | 3 +- .../data/datasets/hydrogen_etrago/__init__.py | 10 +- src/egon/data/datasets/hydrogen_etrago/bus.py | 9 +- .../datasets/hydrogen_etrago/h2_to_ch4.py | 2 +- .../data/datasets/hydrogen_etrago/storage.py | 54 +- .../data/datasets/industrial_gas_demand.py | 84 +- .../loadarea/loadareas_add_demand_cts.sql | 60 + .../loadarea/loadareas_add_demand_hh.sql | 60 + .../loadarea/loadareas_add_demand_ind.sql | 69 + .../datasets/low_flex_scenario/__init__.py | 16 +- .../low_flex_nep2037_2025.sql | 406 +++++ src/egon/data/datasets/osmtgmod/__init__.py | 3 +- .../data/datasets/power_etrago/__init__.py | 4 +- .../data/datasets/power_plants/__init__.py | 481 +++-- .../datasets/power_plants/conventional.py | 24 +- .../power_plants/pv_ground_mounted.py | 211 ++- .../data/datasets/power_plants/pv_rooftop.py | 28 +- .../power_plants/pv_rooftop_buildings.py | 3 +- .../data/datasets/power_plants/wind_farms.py | 3 +- .../datasets/power_plants/wind_offshore.py | 24 +- src/egon/data/datasets/renewable_feedin.py | 4 +- src/egon/data/datasets/sanity_checks.py | 176 +- .../datasets/scenario_capacities_2037_2025.py | 1006 +++++++++++ .../datasets/scenario_parameters/__init__.py | 39 +- .../scenario_parameters/parameters.py | 252 ++- src/egon/data/datasets/society_prognosis.py | 4 +- src/egon/data/datasets/storages/__init__.py | 35 +- .../data/datasets/storages/pumped_hydro.py | 19 +- src/egon/data/datasets_original.yml | 27 +- ....egon_building_electricity_peak_loads.json | 2 +- .../demand.egon_building_heat_peak_loads.json | 2 +- ...cts_electricity_demand_building_share.json | 2 +- ...d.egon_cts_heat_demand_building_share.json | 2 +- ...ld_electricity_profile_in_census_cell.json | 8 +- 63 files changed, 5046 insertions(+), 573 deletions(-) create mode 100755 src/egon/data/datasets/gas_neighbours/nep2037_2025.py create mode 100644 src/egon/data/datasets/low_flex_scenario/low_flex_nep2037_2025.sql create mode 100755 src/egon/data/datasets/scenario_capacities_2037_2025.py diff --git a/src/egon/data/airflow/dags/pipeline.py b/src/egon/data/airflow/dags/pipeline.py index 32974206e..dd6f749e3 100755 --- a/src/egon/data/airflow/dags/pipeline.py +++ b/src/egon/data/airflow/dags/pipeline.py @@ -56,6 +56,7 @@ from egon.data.datasets.heat_supply.individual_heating import ( HeatPumpsStatusQuo, HeatPumps2035, + HeatPumps2037_2025, HeatPumps2050, HeatPumpsPypsaEur, ) @@ -70,6 +71,7 @@ IndustrialGasDemand, IndustrialGasDemandeGon100RE, IndustrialGasDemandeGon2035, + IndustrialGasDemandeGon2037_2025, ) from egon.data.datasets.industrial_sites import MergeIndustrialSites from egon.data.datasets.industry import IndustrialDemandCurves @@ -431,6 +433,7 @@ dependencies=[setup_etrago, insert_hydrogen_buses, vg250] ) + # Insert hydrogen grid insert_h2_grid = HydrogenGridEtrago( dependencies=[ @@ -482,6 +485,11 @@ dependencies=[create_gas_polygons, industrial_gas_demand] ) + # Assign industrial gas demand nep2037_2025 + IndustrialGasDemandeGon2037_2025( + dependencies=[create_gas_polygons, industrial_gas_demand] + ) + # Assign industrial gas demand eGon100RE IndustrialGasDemandeGon100RE( dependencies=[create_gas_polygons, industrial_gas_demand, run_pypsaeur,] @@ -614,6 +622,18 @@ ] ) + # Heat pump disaggregation for nep2037_2025 + heat_pumps_2037_2025 = HeatPumps2037_2025( + dependencies=[ + cts_demand_buildings, + DistrictHeatingAreas, + heat_supply, + heat_time_series, + heat_pumps_pypsa_eur, + power_plants, + ] + ) + # HTS to eTraGo table hts_etrago_table = HtsEtragoTable( dependencies=[ diff --git a/src/egon/data/cli.py b/src/egon/data/cli.py index e01f8a2cb..34722db02 100644 --- a/src/egon/data/cli.py +++ b/src/egon/data/cli.py @@ -176,7 +176,7 @@ ) @click.option( "--scenarios", - default=["status2023", "eGon2035"], + default=["status2023","nep2037_2025" , "eGon2035"], metavar="SCENARIOS", help=("List of scenario names for which a data model shall be created."), multiple=True, diff --git a/src/egon/data/datasets.yml b/src/egon/data/datasets.yml index d6080e10c..9b6a5cd53 100644 --- a/src/egon/data/datasets.yml +++ b/src/egon/data/datasets.yml @@ -151,6 +151,7 @@ demandregio_cts_ind_demand: schema: 'boundaries' table: 'vg250_krs' new_consumers_2035: 'new_largescale_consumers_nep.csv' + new_consumers_2037_2025: 'new_largescale_consumers_nep.csv' new_consumers_2050: "pes-demand-today": "industrial_energy_demand_per_country_today.csv" "pes-production-tomorrow": "industrial_production_per_country_tomorrow_2050.csv" @@ -177,7 +178,7 @@ electrical_demands_households: demandregio: schema: 'demand' table: 'egon_demandregio_hh' - scenarios: ["eGon2035", "eGon100RE"] + scenarios: ["eGon2035", "nep2037_2025", "eGon100RE"] population_prognosis_zensus: schema: 'society' table: 'egon_population_prognosis' @@ -194,7 +195,7 @@ electrical_demands_cts: demandregio: schema: 'demand' table: 'egon_demandregio_cts_ind' - scenarios: ["eGon2035", "eGon100RE"] + scenarios: ["eGon2035", "nep2037_2025", "eGon100RE"] demandregio_wz: schema: 'demand' table: 'egon_demandregio_wz' @@ -283,6 +284,9 @@ scenario_input: eGon2035: capacities: "NEP2035_V2021_scnC2035.xlsx" list_conv_pp: "Kraftwerksliste_NEP_2021_konv.csv" + nep2037_2025: + capacities: "NEP2037_V2025_scnC2037.xlsx" + list_conv_pp: "Kraftwerksliste_NEP_2025_konv.csv" eGon100RE: capacities: "nodal_capacities.csv" boundaries: @@ -403,6 +407,7 @@ power_plants: pv: 'supply.egon_power_plants_pv' wind: 'supply.egon_power_plants_wind' nep_2035: "NEP2035_V2021_scnC2035.xlsx" + nep_2037_2025: "NEP2037_V2025_scnC2037.xlsx" wind_offshore_status2019: "windoffshore_status2019.xlsx" storages: 'supply.egon_storages' target: @@ -419,6 +424,7 @@ storages: ehv_voronoi: "grid.egon_ehv_substation_voronoi" nep_conv: "supply.egon_nep_2021_conventional_powerplants" nep_capacities: "NEP2035_V2021_scnC2035.xlsx" + nep_capacities: "NEP2037_V2025_scnC2037.xlsx" generators: "grid.egon_etrago_generator" bus: "grid.egon_etrago_bus" target: @@ -731,7 +737,7 @@ distributed_industrial_demand: demandregio: schema: 'demand' table: 'egon_demandregio_cts_ind' - scenarios: ["status2019","eGon2021", "eGon2035", "eGon100RE"] + scenarios: ["status2019","eGon2021", "eGon2035", "nep2037_2025", "eGon100RE"] wz: schema: 'demand' table: 'egon_demandregio_wz' @@ -1161,6 +1167,9 @@ emobility_mit: eGon2035: file: "eGon2035_RS7_min2k_2022-06-01_175429_simbev_run.tar.gz" file_metadata: "metadata_simbev_run.json" + nep2037_2025: + file: "eGon2035_RS7_min2k_2022-06-01_175429_simbev_run.tar.gz" + file_metadata: "metadata_simbev_run.json" eGon100RE: file: "eGon100RE_RS7_min2k_2022-06-01_175444_simbev_run.tar.gz" file_metadata: "metadata_simbev_run.json" @@ -1170,12 +1179,14 @@ emobility_mit: status2019: "status2019" status2023: "status2023" eGon2035: "NEP C 2035" + nep2037_2025: "NEP C 2037" eGon100RE: "Reference 2050" # name of low-flex scenario lowflex: create_lowflex_scenario: True names: eGon2035: "eGon2035_lowflex" + nep2037_2025: "nep2037_2025_lowflex" eGon100RE: "eGon100RE_lowflex" model_timeseries: @@ -1243,6 +1254,7 @@ mobility_hgv: fcev_share: 1. scenarios: - "eGon2035" + - "nep2037_2025" - "eGon100RE" carrier: "H2_hgv_load" energy_value_h2: 39.4 # kWh/kg @@ -1257,6 +1269,14 @@ mobility_hgv: # hgv_mean_mileage = 100000 # Total mileage eGon2035: 10000000000 + # NEP data + # https://www.netzentwicklungsplan.de/sites/default/files/paragraphs-files/NEP_2035_V2021_2_Entwurf_Teil1.pdf + # total amount of HGVs - Scenario C 2035 + # hgv_amount = 100000 + # HGV traffic annual mileage per vehicle + # hgv_mean_mileage = 100000 + # Total mileage + nep2037_2025: 10000000000 # Langfristszenarien # https://www.langfristszenarien.de/enertile-explorer-wAssets/docs/LFS3_Langbericht_Verkehr_final.pdf#page=17 eGon100RE: 40000000000 @@ -1265,6 +1285,7 @@ home_batteries: constants: scenarios: - "eGon2035" + - "nep2037_2025" - "eGon100RE" # Mean ratio between the storage capacity and the power of the pv rooftop system cbat_ppv_ratio: 1 diff --git a/src/egon/data/datasets/DSM_cts_ind.py b/src/egon/data/datasets/DSM_cts_ind.py index 80e9a6591..f2e7682f6 100644 --- a/src/egon/data/datasets/DSM_cts_ind.py +++ b/src/egon/data/datasets/DSM_cts_ind.py @@ -713,6 +713,10 @@ def ind_sites_data_import(): if "eGon2035" in scenarios: dsm_2035 = calc_ind_site_timeseries("eGon2035").reset_index() dsm = pd.concat([dsm, dsm_2035], ignore_index=True) + # scenario nep2037_2025 + if "nep2037_2025" in scenarios: + dsm_2037_2025 = calc_ind_site_timeseries("nep2037_2025").reset_index() + dsm = pd.concat([dsm, dsm_2037_2025], ignore_index=True) # scenario eGon100RE if "eGon100RE" in scenarios: dsm_100 = calc_ind_site_timeseries("eGon100RE").reset_index() @@ -904,12 +908,18 @@ def create_dsm_components( dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0) ) - bus_id.iloc[ - rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get( - "eGon2035", 0 - ) - + rows_per_scenario.get("eGon100RE", 0) - ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0)) + bus_id.iloc[rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("nep2037_2025", 0) + ) + + bus_id.iloc[rows_per_scenario.get("eGon2035", 0) + rows_per_scenario.get( + "nep2037_2025", 0) : rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", + 0) + rows_per_scenario.get("eGon100RE", + 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0) + ) dsm_buses["bus_id"] = bus_id @@ -935,12 +945,18 @@ def create_dsm_components( dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0) ) - link_id.iloc[ - rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get( - "eGon2035", 0 - ) - + rows_per_scenario.get("eGon100RE", 0) - ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0)) + link_id.iloc[rows_per_scenario.get("eGon2035", 0): rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("nep2037_2025", 0) + ) + + link_id.iloc[rows_per_scenario.get("eGon2035", 0) + rows_per_scenario.get( + "nep2037_2025", 0): rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", + 0) + rows_per_scenario.get("eGon100RE", + 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0) + ) dsm_links["link_id"] = link_id @@ -972,12 +988,18 @@ def create_dsm_components( dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0) ) - store_id.iloc[ - rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get( - "eGon2035", 0 - ) - + rows_per_scenario.get("eGon100RE", 0) - ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0)) + store_id.iloc[rows_per_scenario.get("eGon2035", 0): rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("nep2037_2025", 0) + ) + + store_id.iloc[rows_per_scenario.get("eGon2035", 0) + rows_per_scenario.get( + "nep2037_2025", 0): rows_per_scenario.get( + "eGon2035", 0) + rows_per_scenario.get("nep2037_2025", + 0) + rows_per_scenario.get("eGon100RE", + 0)] = range( + dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0) + ) dsm_stores["store_id"] = store_id diff --git a/src/egon/data/datasets/ch4_prod.py b/src/egon/data/datasets/ch4_prod.py index 73aa9ead7..7cd6eaf00 100755 --- a/src/egon/data/datasets/ch4_prod.py +++ b/src/egon/data/datasets/ch4_prod.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- """ -The central module containing code dealing with importing CH4 production data for eGon2035. +The central module containing code dealing with importing CH4 production data for +nep2037_2025 and eGon2035. -For eGon2035, the gas produced in Germany can be natural gas or biogas. +For nep2037_2025 and eGon2035, the gas produced in Germany can be natural gas or biogas. The source productions are geolocalised potentials described as PyPSA generators. These generators are not extendable and their overall production over the year is limited directly in eTraGo by values from @@ -27,12 +28,13 @@ class CH4Production(Dataset): """ - Insert the CH4 productions into the database for eGon2035 + Insert the CH4 productions into the database for nep2037_2025 and eGon2035 - Insert the CH4 productions into the database for eGon2035 by using + Insert the CH4 productions into the database for nep2037_2025 and eGon2035 by using the function :py:func:`import_gas_generators`. *Dependencies* + * :py:class:`GasAreasnep2037_2025 ` * :py:class:`GasAreaseGon2035 ` * :py:class:`GasNodesAndPipes ` @@ -289,7 +291,7 @@ def import_gas_generators(): steps are followed: * cleaning of the database table grid.egon_etrago_generator of the - CH4 generators of the specific scenario (eGon2035), + CH4 generators of the specific scenario (nep2037_2025, eGon2035), * call of the functions :py:func:`load_NG_generators` and :py:func:`load_biogas_generators` that respectively return dataframes containing the natural- an bio-gas production units @@ -338,7 +340,7 @@ def import_gas_generators(): """ ) - if scn_name == "eGon2035": + if scn_name == "nep2037_2025" or scn_name == "eGon2035": CH4_generators_list = pd.concat( [ load_NG_generators(scn_name), diff --git a/src/egon/data/datasets/ch4_storages.py b/src/egon/data/datasets/ch4_storages.py index be0674ef4..164f281c7 100755 --- a/src/egon/data/datasets/ch4_storages.py +++ b/src/egon/data/datasets/ch4_storages.py @@ -30,11 +30,12 @@ class CH4Storages(Dataset): Inserts the gas stores in Germany Inserts the non extendable gas stores in Germany into the database - for the scnenarios eGon2035 and eGon100RE using the function + for the scnenarios nep2037_2025, eGon2035 and eGon100RE using the function :py:func:`insert_ch4_storages`. *Dependencies* * :py:class:`GasAreaseGon2035 ` + * :py:class:`GasAreasnep2037_2025 ` * :py:class:`GasAreaseGon100RE ` * :py:class:`GasNodesAndPipes ` @@ -164,9 +165,14 @@ def import_installed_ch4_storages(scn_name): ] # Remove unused storage units - Gas_storages_list = Gas_storages_list[ - Gas_storages_list["end_year"] >= 2035 - ] + if scn_name == "eGon2035": + Gas_storages_list = Gas_storages_list[ + Gas_storages_list["end_year"] >= 2035 + ] + elif scn_name == "nep2037_2025": + Gas_storages_list = Gas_storages_list[ + Gas_storages_list["end_year"] >= 2037 + ] Gas_storages_list = Gas_storages_list.rename( columns={"lat": "y", "long": "x"} @@ -307,12 +313,12 @@ def insert_ch4_stores(scn_name): # Clean table db.execute_sql( f""" - DELETE FROM {target['stores']['schema']}.{target['stores']['table']} + DELETE FROM {target['stores']['schema']}.{target['stores']['table']} WHERE "carrier" = 'CH4' AND scn_name = '{scn_name}' AND bus IN ( SELECT bus_id FROM {source['buses']['schema']}.{source['buses']['table']} - WHERE scn_name = '{scn_name}' + WHERE scn_name = '{scn_name}' AND country = 'DE' ); """ @@ -352,9 +358,10 @@ def insert_ch4_storages(): Overall function to import non extendable gas stores in Germany This function inserts the methane stores in Germany for the - scenarios eGon2035 and eGon100RE by using the function + scenarios nep2037_2025, eGon2035 and eGon100RE by using the function :py:func:`insert_ch4_stores` and has no return. """ + insert_ch4_stores("nep2037_2025") insert_ch4_stores("eGon2035") insert_ch4_stores("eGon100RE") diff --git a/src/egon/data/datasets/chp/__init__.py b/src/egon/data/datasets/chp/__init__.py index 7893ddafc..8655cc022 100644 --- a/src/egon/data/datasets/chp/__init__.py +++ b/src/egon/data/datasets/chp/__init__.py @@ -385,8 +385,9 @@ def insert_biomass_chp(scenario): ) ] - # Scaling will be done per federal state in case of eGon2035 scenario. - if scenario == "eGon2035": + # Scaling will be done per federal state in case of eGon2035 and + # nep2037_2025 scenario. + if scenario == "eGon2035" or scenario == "nep2037_2025": level = "federal_state" else: level = "country" @@ -566,6 +567,46 @@ def insert_chp_statusquo(scn="status2019"): session.add(entry) session.commit() +def insert_chp_nep2037_2025(): + """Insert CHP plants for nep2037_2025 considering NEP and MaStR data + + Returns + ------- + None. + + """ + + sources = config.datasets()["chp_location"]["sources"] + + targets = config.datasets()["chp_location"]["targets"] + + insert_biomass_chp("nep2037_2025") + + # Insert large CHPs based on NEP's list of conventional power plants + MaStR_konv = insert_large_chp(sources, targets["chp_table"], EgonChp, + scn = "nep2037_2025") + + # Insert smaller CHPs (< 10MW) based on existing locations from MaStR + existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp, "nep2037_2025") + + gpd.GeoDataFrame( + MaStR_konv[ + [ + "EinheitMastrNummer", + "el_capacity", + "geometry", + "carrier", + "plz", + "city", + "federal_state", + ] + ] + ).to_postgis( + targets["mastr_conventional_without_chp"]["table"], + schema=targets["mastr_conventional_without_chp"]["schema"], + con=db.engine(), + if_exists="replace", + ) def insert_chp_egon2035(): """Insert CHP plants for eGon2035 considering NEP and MaStR data @@ -583,10 +624,11 @@ def insert_chp_egon2035(): insert_biomass_chp("eGon2035") # Insert large CHPs based on NEP's list of conventional power plants - MaStR_konv = insert_large_chp(sources, targets["chp_table"], EgonChp) + MaStR_konv = insert_large_chp(sources, targets["chp_table"], EgonChp, + scn = "eGon2035") # Insert smaller CHPs (< 10MW) based on existing locations from MaStR - existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp) + existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp, "eGon2035") gpd.GeoDataFrame( MaStR_konv[ @@ -608,68 +650,68 @@ def insert_chp_egon2035(): ) -def extension_BW(): - extension_per_federal_state("BadenWuerttemberg", EgonChp) +def extension_BW(scn): + extension_per_federal_state("BadenWuerttemberg", EgonChp, scn) -def extension_BY(): - extension_per_federal_state("Bayern", EgonChp) +def extension_BY(scn): + extension_per_federal_state("Bayern", EgonChp, scn) -def extension_HB(): - extension_per_federal_state("Bremen", EgonChp) +def extension_HB(scn): + extension_per_federal_state("Bremen", EgonChp, scn) -def extension_BB(): - extension_per_federal_state("Brandenburg", EgonChp) +def extension_BB(scn): + extension_per_federal_state("Brandenburg", EgonChp, scn) -def extension_HH(): - extension_per_federal_state("Hamburg", EgonChp) +def extension_HH(scn): + extension_per_federal_state("Hamburg", EgonChp, scn) -def extension_HE(): - extension_per_federal_state("Hessen", EgonChp) +def extension_HE(scn): + extension_per_federal_state("Hessen", EgonChp, scn) -def extension_MV(): - extension_per_federal_state("MecklenburgVorpommern", EgonChp) +def extension_MV(scn): + extension_per_federal_state("MecklenburgVorpommern", EgonChp, scn) -def extension_NS(): - extension_per_federal_state("Niedersachsen", EgonChp) +def extension_NS(scn): + extension_per_federal_state("Niedersachsen", EgonChp, scn) -def extension_NW(): - extension_per_federal_state("NordrheinWestfalen", EgonChp) +def extension_NW(scn): + extension_per_federal_state("NordrheinWestfalen", EgonChp, scn) -def extension_SN(): - extension_per_federal_state("Sachsen", EgonChp) +def extension_SN(scn): + extension_per_federal_state("Sachsen", EgonChp, scn) -def extension_TH(): - extension_per_federal_state("Thueringen", EgonChp) +def extension_TH(scn): + extension_per_federal_state("Thueringen", EgonChp, scn) -def extension_SL(): - extension_per_federal_state("Saarland", EgonChp) +def extension_SL(scn): + extension_per_federal_state("Saarland", EgonChp, scn) -def extension_ST(): - extension_per_federal_state("SachsenAnhalt", EgonChp) +def extension_ST(scn): + extension_per_federal_state("SachsenAnhalt", EgonChp, scn) -def extension_RP(): - extension_per_federal_state("RheinlandPfalz", EgonChp) +def extension_RP(scn): + extension_per_federal_state("RheinlandPfalz", EgonChp, scn) -def extension_BE(): - extension_per_federal_state("Berlin", EgonChp) +def extension_BE(scn): + extension_per_federal_state("Berlin", EgonChp, scn) -def extension_SH(): - extension_per_federal_state("SchleswigHolstein", EgonChp) +def extension_SH(scn): + extension_per_federal_state("SchleswigHolstein", EgonChp, scn) def insert_chp_egon100re(): @@ -772,6 +814,9 @@ def insert_chp_egon100re(): wrapped_partial(insert_chp_statusquo, scn="status2023", postfix="_2023") ) +if "nep037_2025" in config.settings()["egon-data"]["--scenarios"]: + insert_per_scenario.add(insert_chp_nep037_2025) + if "eGon2035" in config.settings()["egon-data"]["--scenarios"]: insert_per_scenario.add(insert_chp_egon2035) @@ -782,6 +827,33 @@ def insert_chp_egon100re(): extension = set() +if "nep2037_2025" in config.settings()["egon-data"]["--scenarios"]: + # Add one task per federal state for small CHP extension + if ( + config.settings()["egon-data"]["--dataset-boundary"] + == "Schleswig-Holstein" + ): + extension = extension_SH + else: + extension = { + extension_BW, + extension_BY, + extension_HB, + extension_BB, + extension_HE, + extension_MV, + extension_NS, + extension_NW, + extension_SH, + extension_HH, + extension_RP, + extension_SL, + extension_SN, + extension_ST, + extension_TH, + extension_BE, + } + if "eGon2035" in config.settings()["egon-data"]["--scenarios"]: # Add one task per federal state for small CHP extension if ( @@ -820,7 +892,8 @@ class Chp(Dataset): Extract combined heat and power plants for each scenario This dataset creates combined heat and power (CHP) plants for each scenario and defines their use case. - The method bases on existing CHP plants from Marktstammdatenregister. For the eGon2035 scenario, + The method bases on existing CHP plants from Marktstammdatenregister. For the + eGon2035 and nep2037_2025 scenario, a list of CHP plans from the grid operator is used for new largescale CHP plants. CHP < 10MW are randomly distributed. Depending on the distance to a district heating grid, it is decided if the CHP is used to @@ -830,6 +903,8 @@ class Chp(Dataset): *Dependencies* * :py:class:`GasAreaseGon100RE ` * :py:class:`GasAreaseGon2035 ` + * :py:class:`GasAreasnep2037_2025 + ` * :py:class:`DistrictHeatingAreas ` * :py:class:`IndustrialDemandCurves ` * :py:class:`OsmLanduse ` diff --git a/src/egon/data/datasets/chp/match_nep.py b/src/egon/data/datasets/chp/match_nep.py index a6eddf6bf..2cb3ff71f 100755 --- a/src/egon/data/datasets/chp/match_nep.py +++ b/src/egon/data/datasets/chp/match_nep.py @@ -19,7 +19,7 @@ ##################################### NEP treatment ################################# -def select_chp_from_nep(sources): +def select_chp_from_nep(sources, scn): """Select CHP plants with location from NEP's list of power plants Returns @@ -28,17 +28,21 @@ def select_chp_from_nep(sources): CHP plants from NEP list """ + if scn = "eGon2035" + year = "2035" + if scn = "nep2037_2025" + year = "2037" # Select CHP plants with geolocation from list of conventional power plants chp_NEP_data = db.select_dataframe( f""" SELECT bnetza_id, name, carrier, chp, postcode, capacity, city, - federal_state, c2035_chp, c2035_capacity + federal_state, c{year}_chp, c{year}_capacity FROM {sources['list_conv_pp']['schema']}. {sources['list_conv_pp']['table']} WHERE bnetza_id != 'KW<10 MW' - AND (chp = 'Ja' OR c2035_chp = 'Ja') - AND c2035_capacity > 0 + AND (chp = 'Ja' OR c{year}_chp = 'Ja') + AND c{year}_capacity > 0 AND postcode != 'None' """ ) @@ -59,8 +63,8 @@ def select_chp_from_nep(sources): "postcode", "carrier", "capacity", - "c2035_capacity", - "c2035_chp", + f"c{year}_capacity", + f"c{year}_chp", "city", ] ) @@ -74,8 +78,8 @@ def select_chp_from_nep(sources): "postcode", "carrier", "capacity", - "c2035_capacity", - "c2035_chp", + f"c{year}_capacity", + f"c{year}_chp", "city", "federal_state", ], @@ -88,11 +92,11 @@ def select_chp_from_nep(sources): "carrier", "name", "postcode", - "c2035_chp", + f"{year}_chp", "city", "federal_state", ] - )["capacity", "c2035_capacity", "city", "federal_state"] + )["capacity", f"c{year}_capacity", "city", "federal_state"] .sum() .reset_index() ).reset_index() @@ -190,6 +194,7 @@ def match_nep_chp( consider_location="plz", consider_carrier=True, consider_capacity=True, + scn ): """Match CHP plants from MaStR to list of power plants from NEP @@ -214,6 +219,12 @@ def match_nep_chp( CHP plants from NEP which are not matched to MaStR """ + if scn == "eGon2035": + year = "2035" + nep_release = "2021" + elif scn == "nep2037_2025": + year = "2037" + nep_release = "2025" list_federal_states = pd.Series( { @@ -287,15 +298,18 @@ def match_nep_chp( chp_NEP_matched = chp_NEP_matched.append( geopandas.GeoDataFrame( data={ - "source": "MaStR scaled with NEP 2021 list", + "source": f"MaStR scaled with NEP {nep_release} list", "MaStRNummer": selected.EinheitMastrNummer.head(1), "carrier": ( - ET if row.c2035_chp == "Nein" else "gas" + ET if getattr(row, f"c{year}_chp") == + "Nein" + else + "gas" ), "chp": True, - "el_capacity": row.c2035_capacity, + "el_capacity": getattr(row, f"c{year}_capacity"), "th_capacity": selected.th_capacity.head(1), - "scenario": "eGon2035", + "scenario": f"{scn}", "geometry": selected.geometry.head(1), "voltage_level": selected.voltage_level.head(1), } @@ -313,9 +327,9 @@ def match_nep_chp( ################################################### Final table ################################################### -def insert_large_chp(sources, target, EgonChp): +def insert_large_chp(sources, target, EgonChp, scn): # Select CHP from NEP list - chp_NEP = select_chp_from_nep(sources) + chp_NEP = select_chp_from_nep(sources, scn) # Select CHP from MaStR MaStR_konv = select_chp_from_mastr(sources) @@ -344,7 +358,7 @@ def insert_large_chp(sources, target, EgonChp): # Match CHP from NEP list using PLZ, carrier and capacity chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( - chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1 + chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1,scn ) # Match CHP from NEP list using first 4 numbers of PLZ, @@ -355,6 +369,7 @@ def insert_large_chp(sources, target, EgonChp): chp_NEP_matched, buffer_capacity=0.1, consider_location="city", + scn ) # Aggregate units from MaStR to one power plant @@ -383,7 +398,7 @@ def insert_large_chp(sources, target, EgonChp): # Match CHP from NEP list with aggregated MaStR units chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( - chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1 + chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1, scn ) # Match CHP from NEP list with aggregated MaStR units @@ -393,20 +408,21 @@ def insert_large_chp(sources, target, EgonChp): chp_NEP_matched, buffer_capacity=0.1, consider_location="city", + scn ) # Aggregate units from NEP to one power plant chp_NEP = ( chp_NEP.groupby( - ["postcode", "carrier", "city", "c2035_chp", "federal_state"] - )[["capacity", "c2035_capacity"]] + ["postcode", "carrier", "city", f"c{year}_chp", "federal_state"] + )[["capacity", f"c{year}_capacity"]] .sum() .reset_index() ) # Match CHP from NEP list with aggregated MaStR units chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( - chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1 + chp_NEP, MaStR_konv, chp_NEP_matched, buffer_capacity=0.1, scn ) # Match CHP from NEP list with aggregated MaStR units @@ -416,6 +432,7 @@ def insert_large_chp(sources, target, EgonChp): chp_NEP_matched, buffer_capacity=0.1, consider_location="city", + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -424,6 +441,7 @@ def insert_large_chp(sources, target, EgonChp): chp_NEP_matched, buffer_capacity=0.3, consider_location="city", + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -433,6 +451,7 @@ def insert_large_chp(sources, target, EgonChp): buffer_capacity=0.3, consider_location="city", consider_carrier=False, + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -443,6 +462,7 @@ def insert_large_chp(sources, target, EgonChp): consider_location="city", consider_carrier=True, consider_capacity=False, + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -452,6 +472,7 @@ def insert_large_chp(sources, target, EgonChp): consider_location="city", consider_carrier=True, consider_capacity=False, + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -461,6 +482,7 @@ def insert_large_chp(sources, target, EgonChp): consider_location="city", consider_carrier=False, consider_capacity=False, + scn ) chp_NEP_matched, MaStR_konv, chp_NEP = match_nep_chp( @@ -470,6 +492,7 @@ def insert_large_chp(sources, target, EgonChp): consider_location="federal_state", consider_carrier=False, consider_capacity=False, + scn ) # Prepare geometry for database import @@ -478,7 +501,8 @@ def insert_large_chp(sources, target, EgonChp): ) print(f"{chp_NEP_matched.el_capacity.sum()} MW matched") - print(f"{chp_NEP.c2035_capacity.sum()} MW not matched") + print(f"{getattr(chp_NEP, f"c{year}_capacity").sum()} MW not " + f"matched") chp_NEP.to_csv("not_matched_chp.csv") @@ -506,7 +530,7 @@ def insert_large_chp(sources, target, EgonChp): # Assign gas bus_id insert_chp["gas_bus_id"] = db.assign_gas_bus_id( - insert_chp_c, "eGon2035", "CH4" + insert_chp_c, f"{scn}", "CH4" ).bus insert_chp = assign_use_case(insert_chp, sources) @@ -515,7 +539,7 @@ def insert_large_chp(sources, target, EgonChp): db.execute_sql( f""" DELETE FROM {target['schema']}.{target['table']} WHERE carrier IN ('gas', 'other_non_renewable', 'oil') - AND scenario='eGon2035';""" + AND scenario='{scn}';""" ) # Insert into target table @@ -535,7 +559,7 @@ def insert_large_chp(sources, target, EgonChp): electrical_bus_id=row.bus_id, ch4_bus_id=row.gas_bus_id, district_heating=row.district_heating, - scenario="eGon2035", + scenario=f"{scn}", geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})", ) session.add(entry) diff --git a/src/egon/data/datasets/chp/small_chp.py b/src/egon/data/datasets/chp/small_chp.py index b940033da..884638d8f 100755 --- a/src/egon/data/datasets/chp/small_chp.py +++ b/src/egon/data/datasets/chp/small_chp.py @@ -14,7 +14,7 @@ ) -def insert_mastr_chp(mastr_chp, EgonChp): +def insert_mastr_chp(mastr_chp, EgonChp, scn): """Insert MaStR data from exising CHPs into database table Parameters @@ -23,6 +23,8 @@ def insert_mastr_chp(mastr_chp, EgonChp): List of existing CHPs in MaStR. EgonChp : class Class definition of daabase table for CHPs + scn : str + Name of the scenario Returns ------- @@ -46,14 +48,14 @@ def insert_mastr_chp(mastr_chp, EgonChp): ch4_bus_id=row.gas_bus_id, district_heating=row.district_heating, voltage_level=row.voltage_level, - scenario="eGon2035", + scenario=scn, geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})", ) session.add(entry) session.commit() -def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp): +def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp, scn): """Insert existing small CHPs based on MaStR and target values Parameters @@ -62,6 +64,8 @@ def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp): List of conevntional CHPs in MaStR whoes locateion is not used EgonChp : class Class definition of daabase table for CHPs + scn : str + Name of the scenario Returns ------- @@ -76,7 +80,7 @@ def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp): & (MaStR_konv.th_capacity > 0) ] - targets = select_target("small_chp", "eGon2035") + targets = select_target("small_chp", scn) for federal_state in targets.index: mastr_chp = gpd.GeoDataFrame( @@ -88,7 +92,7 @@ def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp): # Assign gas bus_id mastr_chp_c = mastr_chp.copy() mastr_chp["gas_bus_id"] = db.assign_gas_bus_id( - mastr_chp_c, "eGon2035", "CH4" + mastr_chp_c, scn, "CH4" ).bus # Assign bus_id @@ -96,9 +100,9 @@ def existing_chp_smaller_10mw(sources, MaStR_konv, EgonChp): mastr_chp, config.datasets()["chp_location"] ).bus_id - mastr_chp = assign_use_case(mastr_chp, sources, "eGon2035") + mastr_chp = assign_use_case(mastr_chp, sources, scn) - insert_mastr_chp(mastr_chp, EgonChp) + insert_mastr_chp(mastr_chp, EgonChp, scn) def extension_to_areas( @@ -289,6 +293,7 @@ def extension_district_heating( flh_chp, EgonChp, areas_without_chp_only=False, + scn ): """Build new CHP < 10 MW for district areas considering existing CHP and the heat demand. @@ -327,8 +332,8 @@ def extension_district_heating( {targets['chp_table']['table']} a, {sources['district_heating_areas']['schema']}. {sources['district_heating_areas']['table']} b - WHERE a.scenario = 'eGon2035' - AND b.scenario = 'eGon2035' + WHERE a.scenario = '{scn}' + AND b.scenario = '{scn}' AND district_heating = True AND ST_Intersects( ST_Transform( @@ -354,7 +359,7 @@ def extension_district_heating( FROM {sources['district_heating_areas']['schema']}. {sources['district_heating_areas']['table']} - WHERE scenario = 'eGon2035' + WHERE scenario = '{scn}' AND ST_Intersects(ST_Transform(ST_Centroid(geom_polygon), 4326), ( SELECT ST_Union(d.geometry) FROM @@ -364,7 +369,7 @@ def extension_district_heating( SELECT district_heating_area_id FROM {targets['chp_table']['schema']}. {targets['chp_table']['table']} - WHERE scenario = 'eGon2035' + WHERE scenario = '{scn}' AND district_heating = TRUE) """ ) @@ -391,8 +396,8 @@ def extension_district_heating( {targets['chp_table']['table']} a, {sources['district_heating_areas']['schema']}. {sources['district_heating_areas']['table']} b - WHERE b.scenario = 'eGon2035' - AND a.scenario = 'eGon2035' + WHERE b.scenario = '{scn}' + AND a.scenario = '{scn}' AND ST_Intersects( ST_Transform(ST_Centroid(geom_polygon), 4326), (SELECT ST_Union(d.geometry) @@ -422,7 +427,7 @@ def extension_district_heating( return not_distributed_capacity -def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp): +def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp, scn): """Build new CHP < 10 MW for industry considering existing CHP, osm landuse areas and electricity demands. @@ -439,6 +444,8 @@ def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp): Assumed number of full load hours of electricity output. EgonChp : class ORM-class definition of CHP database-table. + scn : str + Name of the scenario Returns ------- @@ -455,7 +462,7 @@ def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp): FROM {targets['chp_table']['schema']}. {targets['chp_table']['table']} a - WHERE a.scenario = 'eGon2035' + WHERE a.scenario = '{scn}' AND district_heating = False AND el_capacity < 10 ORDER BY el_capacity @@ -473,7 +480,7 @@ def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp): {sources['industrial_demand_osm']['table']} a, {sources['osm_landuse']['schema']}. {sources['osm_landuse']['table']} b - WHERE a.scenario = 'eGon2035' + WHERE a.scenario = '{scn}' AND b.id = a.osm_id AND NOT ST_Intersects( ST_Transform(b.geom, 4326), @@ -517,7 +524,7 @@ def extension_industrial(federal_state, additional_capacity, flh_chp, EgonChp): return not_distributed_capacity -def extension_per_federal_state(federal_state, EgonChp): +def extension_per_federal_state(federal_state, EgonChp, scn): """Adds new CHP plants to meet target value per federal state. The additional capacity for CHPs < 10 MW is distributed discretly. @@ -537,6 +544,8 @@ def extension_per_federal_state(federal_state, EgonChp): Name of the federal state EgonChp : class ORM-class definition of CHP table + scn : str + Name of the scenario Returns ------- @@ -547,7 +556,7 @@ def extension_per_federal_state(federal_state, EgonChp): sources = config.datasets()["chp_location"]["sources"] target_table = config.datasets()["chp_location"]["targets"]["chp_table"] - targets = select_target("small_chp", "eGon2035") + targets = select_target("small_chp", scn) existing_capacity = db.select_dataframe( f""" @@ -556,7 +565,7 @@ def extension_per_federal_state(federal_state, EgonChp): {target_table['table']} WHERE sources::json->>'el_capacity' = 'MaStR' AND carrier != 'biomass' - AND scenario = 'eGon2035' + AND scenario = '{scn}' AND ST_Intersects(geom, ( SELECT ST_Union(geometry) FROM {sources['vg250_lan']['schema']}.{sources['vg250_lan']['table']} b @@ -592,7 +601,7 @@ def extension_per_federal_state(federal_state, EgonChp): f"Distributing {capacity_district_heating} MW_el to district heating" ) not_distributed_capacity_dh = extension_district_heating( - federal_state, capacity_district_heating, flh_chp, EgonChp + federal_state, capacity_district_heating, flh_chp, EgonChp, scn ) if not_distributed_capacity_dh > 1: @@ -608,6 +617,7 @@ def extension_per_federal_state(federal_state, EgonChp): additional_capacity * (1 - share_dh), flh_chp, EgonChp, + scn ) print( @@ -626,6 +636,7 @@ def extension_per_federal_state(federal_state, EgonChp): not_distributed_capacity_industry, flh_chp, EgonChp, + scn ) else: diff --git a/src/egon/data/datasets/chp_etrago.py b/src/egon/data/datasets/chp_etrago.py index e4bb5a0ce..93765f416 100644 --- a/src/egon/data/datasets/chp_etrago.py +++ b/src/egon/data/datasets/chp_etrago.py @@ -426,7 +426,7 @@ def insert(): """Insert combined heat and power plants into eTraGo tables. Gas CHP plants are modeled as links to the gas grid, - biomass CHP plants (only in eGon2035) are modeled as generators + biomass CHP plants (only in nep22037_2025 and eGon2035) are modeled as generators Returns ------- diff --git a/src/egon/data/datasets/demandregio/__init__.py b/src/egon/data/datasets/demandregio/__init__.py index db10704d5..e9d10906c 100644 --- a/src/egon/data/datasets/demandregio/__init__.py +++ b/src/egon/data/datasets/demandregio/__init__.py @@ -373,7 +373,7 @@ def adjust_ind_pes(ec_cts_ind): def adjust_cts_ind_nep(ec_cts_ind, sector): """Add electrical demand of new largescale CTS und industrial consumers - according to NEP 2021, scneario C 2035. Values per federal state are + according to NEP 2025, scneario C 2037. Values per federal state are linear distributed over all CTS branches and nuts3 regions. Parameters @@ -394,8 +394,8 @@ def adjust_cts_ind_nep(ec_cts_ind, sector): file_path = ( Path(".") / "data_bundle_egon_data" - / "nep2035_version2021" - / sources["new_consumers_2035"] + / "nep2037_version2025" + / sources["new_largescale_consumers_nep"] ) # get data from NEP per federal state @@ -470,7 +470,8 @@ def disagg_households_power( df = data.households_per_size(year=year) * power_per_HH # Bottom-Up: Power demand by household sizes in [MWh/a] for each scenario - elif scenario in ["status2019", "status2023", "eGon2021", "eGon2035"]: + elif scenario in ["status2019", "status2023", "eGon2021", "eGon2035", + "nep2037_2025"]: # chose demand per household size from survey including weighted DHW power_per_HH = demand_per_hh_size["weighted"] / 1e3 @@ -480,8 +481,12 @@ def disagg_households_power( * power_per_HH ) + if scenario == "nep2037_2025": + # scale to fit demand of NEP 2025 scenario C 2037 (166TWh) + df *= 166 * 1e6 / df.sum().sum() + if scenario == "eGon2035": - # scale to fit demand of NEP 2021 scebario C 2035 (119TWh) + # scale to fit demand of NEP 2021 scenario C 2035 (119TWh) df *= 119 * 1e6 / df.sum().sum() if scenario == "status2023": @@ -667,7 +672,20 @@ def insert_cts_ind(scenario, year, engine, target_values): ] # Workaround: Since the disaggregator does not work anymore, data from - # previous runs is used for eGon2035 and eGon100RE + # previous runs is used for nep2037_2025, eGon2035 and eGon100RE + if scenario == "nep2037_2025": + ec_cts_ind2 = pd.read_csv( + "data_bundle_powerd_data/egon_demandregio_cts_ind_egon2035.csv" + ) + ec_cts_ind2.to_sql( + targets["cts_ind_demand"]["table"], + engine, + targets["cts_ind_demand"]["schema"], + if_exists="append", + index=False, + ) + return + if scenario == "eGon2035": ec_cts_ind2 = pd.read_csv( "data_bundle_powerd_data/egon_demandregio_cts_ind_egon2035.csv" @@ -711,8 +729,9 @@ def insert_cts_ind(scenario, year, engine, target_values): target_values[scenario][sector] / ec_cts_ind.sum().sum() ) - # include new largescale consumers according to NEP 2021 - if scenario == "eGon2035": + + # include new largescale consumers according to NEP + if scenario == "eGon2035" or scenario == "nep2037_2025": ec_cts_ind = adjust_cts_ind_nep(ec_cts_ind, sector) # include new industrial demands due to sector coupling if (scenario == "eGon100RE") & (sector == "industry"): @@ -803,8 +822,14 @@ def insert_cts_ind_demands(): # target values per scenario in MWh target_values = { + # according to NEP 2025 + # new consumers will be added separately (reference year 2022) + "nep2037_2025": { + "CTS": 122500 * 1e3, + "industry": 202500 * 1e3 + }, # according to NEP 2021 - # new consumers will be added seperatly + # new consumers will be added separately "eGon2035": { "CTS": 135300 * 1e3, "industry": 225400 * 1e3 diff --git a/src/egon/data/datasets/district_heating_areas/__init__.py b/src/egon/data/datasets/district_heating_areas/__init__.py index bb548ee4d..51ae92c78 100644 --- a/src/egon/data/datasets/district_heating_areas/__init__.py +++ b/src/egon/data/datasets/district_heating_areas/__init__.py @@ -966,7 +966,7 @@ def study_prospective_district_heating_areas(): resulting Prospective Supply Districts (PSDs) for district heating. This functions saves local shapefiles, because these data are not written into database. Moreover, heat density curves are drawn. - This function is tailor-made and includes the scenarios eGon2035 and + This function is tailor-made and includes the scenarios nep2037_2025, eGon2035 and eGon100RE. Parameters @@ -1003,11 +1003,13 @@ def study_prospective_district_heating_areas(): # INSERT INTO scenario.egon_scenario_parameters (name) # VALUES ('eGon2015'); # because egon2015 is not part of the regular EgonScenario table! + HD_2037_2025 = load_heat_demands("nep2037_2025") HD_2035 = load_heat_demands("eGon2035") HD_2050 = load_heat_demands("eGon100RE") # select only cells with heat demands > 100 GJ / (ha a) # HD_2015_above_100GJ = select_high_heat_demands(HD_2015) + HD_2037_2025_above_100GJ = select_high_heat_demands(HD_2037_2025) HD_2035_above_100GJ = select_high_heat_demands(HD_2035) HD_2050_above_100GJ = select_high_heat_demands(HD_2050) @@ -1021,6 +1023,10 @@ def study_prospective_district_heating_areas(): # minimum_total_demand=(10000/3.6) # ).dissolve('area_id', aggfunc='sum') # PSD_2015_201m.to_file(results_path+"PSDs_2015based.shp") + PSD_2037_2025_201m = area_grouping( + HD_2037_2025_above_100GJ, distance=200, minimum_total_demand=(10000 / 3.6) + ).dissolve("area_id", aggfunc="sum") + PSD_2037_2025_201m.to_file(results_path + "PSDs_2037_2025based.shp") PSD_2035_201m = area_grouping( HD_2035_above_100GJ, distance=200, minimum_total_demand=(10000 / 3.6) ).dissolve("area_id", aggfunc="sum") @@ -1044,6 +1050,18 @@ def study_prospective_district_heating_areas(): # ax.plot(HD_2015.Cumulative_Sum, # HD_2015.residential_and_service_demand, label='eGon2015') + HD_2037_2025 = HD_2037_2025.sort_values( + "residential_and_service_demand", ascending=False + ).reset_index() + HD_2037_2025["Cumulative_Sum"] = ( + HD_2037_2025.residential_and_service_demand.cumsum() + ) / 1000000 + ax.plot( + HD_2037_2025.Cumulative_Sum, + HD_2037_2025.residential_and_service_demand, + label="nep2037_2025", + ) + HD_2035 = HD_2035.sort_values( "residential_and_service_demand", ascending=False ).reset_index() @@ -1069,7 +1087,17 @@ def study_prospective_district_heating_areas(): ) # add the district heating shares - + heat_parameters = get_sector_parameters("heat", "nep2037_2025") + district_heating_share_2037_2025 = heat_parameters["DE_district_heating_share"] + plt.axvline( + x=HD_2037_2025.residential_and_service_demand.sum() + / 1000000 + * district_heating_share_2037_2025, + ls=":", + lw=0.5, + label="72TWh DH in 2037 in Germany => 14% DH", # modify to according value + color="black", + ) heat_parameters = get_sector_parameters("heat", "eGon2035") district_heating_share_2035 = heat_parameters["DE_district_heating_share"] plt.axvline( diff --git a/src/egon/data/datasets/district_heating_areas/plot.py b/src/egon/data/datasets/district_heating_areas/plot.py index 9cd3b2f58..062f7f0ca 100644 --- a/src/egon/data/datasets/district_heating_areas/plot.py +++ b/src/egon/data/datasets/district_heating_areas/plot.py @@ -53,11 +53,15 @@ def plot_heat_density_sorted(heat_denisty_per_scenario, scenario_name=None): fig, ax = plt.subplots(1, 1) colors = pd.DataFrame( - columns=["share", "curve"], index=["status2019", "status2023", "eGon2035", "eGon100RE"] + columns=["share", "curve"], index=["status2019", "status2023", "eGon2035", + "nep2037_2025", + "eGon100RE"] ) colors["share"]["eGon2035"] = "darkblue" colors["curve"]["eGon2035"] = "blue" + colors["share"]["nep2037_2025"] = "brown" + colors["curve"]["nep2037_2025"] = "pink" colors["share"]["eGon100RE"] = "red" colors["curve"]["eGon100RE"] = "orange" colors["share"]["status2019"] = "darkgreen" diff --git a/src/egon/data/datasets/electrical_neighbours.py b/src/egon/data/datasets/electrical_neighbours.py index bf48c9ea7..8d4aa4583 100644 --- a/src/egon/data/datasets/electrical_neighbours.py +++ b/src/egon/data/datasets/electrical_neighbours.py @@ -269,9 +269,10 @@ def buses(scenario, sources, targets): errors="ignore" ) - # Insert all central buses for eGon2035 + # Insert all central buses for eGon2035 and nep2037_2025 if scenario in [ "eGon2035", + "nep2037_2025", "status2019", "status2023", ]: # TODO: status2023 this is hardcoded shit @@ -523,7 +524,7 @@ def cross_border_lines(scenario, sources, targets, central_buses): print("WARNING! THERE ARE LINES WITH LENGTH = 0") condition = new_lines["length"] != 0 new_lines["length"] = new_lines["length"].where(condition, 1) - + # Set electrical parameters based on lines from osmtgmod for parameter in ["x", "r"]: @@ -957,7 +958,7 @@ def get_foreign_bus_id(scenario): return buses.set_index("node_id").bus_id -def calc_capacities(): +def calc_capacities(scn_name): """Calculates installed capacities from TYNDP data Returns @@ -1010,22 +1011,40 @@ def calc_capacities(): .set_index(["Node/Line", "Generator_ID"]) ) - # interpolate linear between 2030 and 2040 for 2035 accordning to - # scenario report of TSO's and the approval by BNetzA - df_2035 = pd.DataFrame(index=df_2030.index) - df_2035["cap_2030"] = df_2030.Value - df_2035["cap_2040"] = df_2040.Value - df_2035.fillna(0.0, inplace=True) - df_2035["cap_2035"] = ( - df_2035["cap_2030"] + (df_2035["cap_2040"] - df_2035["cap_2030"]) / 2 - ) - df_2035 = df_2035.reset_index() - df_2035["carrier"] = df_2035.Generator_ID.map(map_carriers_tyndp()) + if scn_name == "eGon2035" + # interpolate linear between 2030 and 2040 for 2035 accordning to + # scenario report of TSO's and the approval by BNetzA + df_2035 = pd.DataFrame(index=df_2030.index) + df_2035["cap_2030"] = df_2030.Value + df_2035["cap_2040"] = df_2040.Value + df_2035.fillna(0.0, inplace=True) + df_2035["cap_2035"] = ( + df_2035["cap_2030"] + (df_2035["cap_2040"] - df_2035["cap_2030"]) / 2 + ) + df_2035 = df_2035.reset_index() + df_2035["carrier"] = df_2035.Generator_ID.map(map_carriers_tyndp()) - # group capacities by new carriers - grouped_capacities = ( - df_2035.groupby(["carrier", "Node/Line"]).cap_2035.sum().reset_index() - ) + # group capacities by new carriers + grouped_capacities = ( + df_2035.groupby(["carrier", "Node/Line"]).cap_2035.sum().reset_index() + ) + elif scn_name == "nep2035_2025" + # interpolate linear between 2030 and 2040 for 2037 accordning to + # scenario report of TSO's and the approval by BNetzA + df_2037 = pd.DataFrame(index=df_2030.index) + df_2037["cap_2030"] = df_2030.Value + df_2037["cap_2040"] = df_2040.Value + df_2037.fillna(0.0, inplace=True) + df_2037["cap_2037"] = ( + df_2037["cap_2030"] + (df_2037["cap_2040"] - df_2037["cap_2030"]) / 2 + ) + df_2037 = df_2037.reset_index() + df_2037["carrier"] = df_2037.Generator_ID.map(map_carriers_tyndp()) + + # group capacities by new carriers + grouped_capacities = ( + df_2037.groupby(["carrier", "Node/Line"]).cap_2037.sum().reset_index() + ) # choose capacities for considered countries return grouped_capacities[ @@ -1033,7 +1052,7 @@ def calc_capacities(): ] -def insert_generators_tyndp(capacities): +def insert_generators_tyndp(capacities, scn_name): """Insert generators for foreign countries based on TYNDP-data Parameters @@ -1058,8 +1077,8 @@ def insert_generators_tyndp(capacities): SELECT bus_id FROM {targets['buses']['schema']}.{targets['buses']['table']} WHERE country != 'DE' - AND scn_name = 'eGon2035') - AND scn_name = 'eGon2035' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' AND carrier != 'CH4' """ ) @@ -1073,7 +1092,7 @@ def insert_generators_tyndp(capacities): SELECT generator_id FROM {targets['generators']['schema']}.{targets['generators']['table']} ) - AND scn_name = 'eGon2035' + AND scn_name = '{scn_name}' """ ) @@ -1105,38 +1124,52 @@ def insert_generators_tyndp(capacities): ) gen.loc[:, "bus"] = ( - get_foreign_bus_id(scenario="eGon2035") + get_foreign_bus_id(scenario=f"{scn_name}") .loc[gen.loc[:, "Node/Line"]] .values ) # Add scenario column - gen["scenario"] = "eGon2035" + gen["scenario"] = f"{scn_name}" # Add marginal costs gen = add_marginal_costs(gen) # insert generators data session = sessionmaker(bind=db.engine())() - for i, row in gen.iterrows(): - entry = etrago.EgonPfHvGenerator( - scn_name=row.scenario, - generator_id=int(db.next_etrago_id("generator")), - bus=row.bus, - carrier=row.carrier, - p_nom=row.cap_2035, - marginal_cost=row.marginal_cost, - ) + if scn_name = "eGon2035": + for i, row in gen.iterrows(): + entry = etrago.EgonPfHvGenerator( + scn_name=row.scenario, + generator_id=int(db.next_etrago_id("generator")), + bus=row.bus, + carrier=row.carrier, + p_nom=row.cap_2035, + marginal_cost=row.marginal_cost, + ) - session.add(entry) - session.commit() + session.add(entry) + session.commit() + elif scn_name = "nep2037_2025": + for i, row in gen.iterrows(): + entry = etrago.EgonPfHvGenerator( + scn_name=row.scenario, + generator_id=int(db.next_etrago_id("generator")), + bus=row.bus, + carrier=row.carrier, + p_nom=row.cap_2037, + marginal_cost=row.marginal_cost, + ) + + session.add(entry) + session.commit() # assign generators time-series data - renewable_timeseries_pypsaeur("eGon2035") + renewable_timeseries_pypsaeur(f"{scn_name}") -def insert_storage_tyndp(capacities): +def insert_storage_tyndp(capacities, scn_name): """Insert storage units for foreign countries based on TYNDP-data Parameters @@ -1161,17 +1194,17 @@ def insert_storage_tyndp(capacities): SELECT bus_id FROM {targets['buses']['schema']}.{targets['buses']['table']} WHERE country != 'DE' - AND scn_name = 'eGon2035') - AND scn_name = 'eGon2035' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' """ ) # Add missing information suitable for eTraGo selected from scenario_parameter table - parameters_pumped_hydro = scenario_parameters.electricity("eGon2035")[ + parameters_pumped_hydro = scenario_parameters.electricity("{scn_name}")[ "efficiency" ]["pumped_hydro"] - parameters_battery = scenario_parameters.electricity("eGon2035")[ + parameters_battery = scenario_parameters.electricity("{scn_name}")[ "efficiency" ]["battery"] @@ -1188,7 +1221,7 @@ def insert_storage_tyndp(capacities): ) store.loc[:, "bus"] = ( - get_foreign_bus_id(scenario="eGon2035") + get_foreign_bus_id(scenario="{scn_name}") .loc[store.loc[:, "Node/Line"]] .values ) @@ -1213,9 +1246,25 @@ def insert_storage_tyndp(capacities): # insert data session = sessionmaker(bind=db.engine())() - for i, row in store.iterrows(): + if scn_name == "eGon2035": + for i, row in store.iterrows(): + entry = etrago.EgonPfHvStorage( + scn_name=f"{scn_name}", + storage_id=int(db.next_etrago_id("storage")), + bus=row.bus, + max_hours=row.max_hours, + efficiency_store=row.store, + efficiency_dispatch=row.dispatch, + standing_loss=row.standing_loss, + carrier=row.carrier, + p_nom=row.cap_2035, + ) + + session.add(entry) + session.commit() + elif scn_name == "nep2037_2025" entry = etrago.EgonPfHvStorage( - scn_name="eGon2035", + scn_name=f"{scn_name}", storage_id=int(db.next_etrago_id("storage")), bus=row.bus, max_hours=row.max_hours, @@ -1223,7 +1272,7 @@ def insert_storage_tyndp(capacities): efficiency_dispatch=row.dispatch, standing_loss=row.standing_loss, carrier=row.carrier, - p_nom=row.cap_2035, + p_nom=row.cap_2037, ) session.add(entry) @@ -1256,7 +1305,7 @@ def get_map_buses(): } -def tyndp_generation(): +def tyndp_generation(scn_name): """Insert data from TYNDP 2020 accordning to NEP 2021 Scenario 'Distributed Energy', linear interpolate between 2030 and 2040 @@ -1265,14 +1314,14 @@ def tyndp_generation(): None. """ - capacities = calc_capacities() + capacities = calc_capacities(scn_name) - insert_generators_tyndp(capacities) + insert_generators_tyndp(capacities, scn_name) - insert_storage_tyndp(capacities) + insert_storage_tyndp(capacities, scn_name) -def tyndp_demand(): +def tyndp_demand(scn_name): """Copy load timeseries data from TYNDP 2020. According to NEP 2021, the data for 2030 and 2040 is interpolated linearly. @@ -1292,7 +1341,7 @@ def tyndp_demand(): DELETE FROM {targets['loads']['schema']}. {targets['loads']['table']} WHERE - scn_name = 'eGon2035' + scn_name = '{scn_name}' AND carrier = 'AC' AND bus NOT IN ( SELECT bus_i @@ -1334,7 +1383,7 @@ def tyndp_demand(): buses[buses.nodes.isin(map_buses.keys())].nodes.map(map_buses) ) buses.loc[:, "bus"] = ( - get_foreign_bus_id(scenario="eGon2035") + get_foreign_bus_id(scenario=f"{scn_name}") .loc[buses.loc[:, "nodes"]] .values ) @@ -1378,21 +1427,32 @@ def tyndp_demand(): data_2040 = data_2030 # According to the NEP, data for 2030 and 2040 is linear interpolated - data_2035 = ((data_2030 + data_2040) / 2)[:8760] + if scn_name == "eGon2035": + data_2035 = ((data_2030 + data_2040) / 2)[:8760] + elif scn_name == "nep2037_2025" + data_2037 = ((data_2030 + data_2040) / 2)[:8760] entry = etrago.EgonPfHvLoad( - scn_name="eGon2035", + scn_name=f"{scn_name}", load_id=int(load_id), carrier="AC", bus=int(buses.bus[bus]), ) - entry_ts = etrago.EgonPfHvLoadTimeseries( - scn_name="eGon2035", - load_id=int(load_id), - temp_id=1, - p_set=list(data_2035.values), - ) + if scn_name == "eGon2035": + entry_ts = etrago.EgonPfHvLoadTimeseries( + scn_name=f"{scn_name}", + load_id=int(load_id), + temp_id=1, + p_set=list(data_2035.values), + ) + elif scn_name == "nep2037_2025" + entry_ts = etrago.EgonPfHvLoadTimeseries( + scn_name=f"{scn_name}", + load_id=int(load_id), + temp_id=1, + p_set=list(data_2037.values), + ) session.add(entry) session.add(entry_ts) @@ -2168,8 +2228,8 @@ def insert_loads_sq(scn_name="status2019"): for scn_name in config.settings()["egon-data"]["--scenarios"]: - if scn_name == "eGon2035": - insert_per_scenario.update([tyndp_generation, tyndp_demand]) + if scn_name == "eGon2035" or scn_name == "nep2037_2025": + insert_per_scenario.update([partial(tyndp_generation, scn_name) , tyndp_demand]) if "status" in scn_name: postfix = f"_{scn_name.split('status')[-1]}" @@ -2218,7 +2278,7 @@ class ElectricalNeighbours(Dataset): * :py:class:`grid.egon_etrago_generator ` is extended * :py:class:`grid.egon_etrago_generator_timeseries ` is extended * :py:class:`grid.egon_etrago_transformer ` is extended - + """ #: diff --git a/src/egon/data/datasets/electricity_demand/__init__.py b/src/egon/data/datasets/electricity_demand/__init__.py index 8ebdce98a..08b4b2fe3 100644 --- a/src/egon/data/datasets/electricity_demand/__init__.py +++ b/src/egon/data/datasets/electricity_demand/__init__.py @@ -95,6 +95,7 @@ def get_annual_household_el_demand_cells(): HouseholdElectricityProfilesInCensusCells.factor_2019, HouseholdElectricityProfilesInCensusCells.factor_2023, HouseholdElectricityProfilesInCensusCells.factor_2035, + HouseholdElectricityProfilesInCensusCells.factor_2037_2025, HouseholdElectricityProfilesInCensusCells.factor_2050, ) .filter( @@ -128,12 +129,16 @@ def ve(s): df_annual_demand = pd.DataFrame( columns=scenarios + ["zensus_population_id"] ) - for _, df in df_buildings_and_profiles.groupby(by=iterate_over): df_annual_demand_iter = pd.DataFrame( columns=scenarios + ["zensus_population_id"] ) + if "nep2037_2025" in scenarios: + df_annual_demand_iter["nep2037_2025"] = ( + df_profiles.loc[:, df["profile_id"]].sum(axis=0) + * df["factor_2037_2025"].values + ) if "eGon2035" in scenarios: df_annual_demand_iter["eGon2035"] = ( df_profiles.loc[:, df["profile_id"]].sum(axis=0) diff --git a/src/egon/data/datasets/electricity_demand_timeseries/cts_buildings.py b/src/egon/data/datasets/electricity_demand_timeseries/cts_buildings.py index 28464a3e5..6a67c79ed 100644 --- a/src/egon/data/datasets/electricity_demand_timeseries/cts_buildings.py +++ b/src/egon/data/datasets/electricity_demand_timeseries/cts_buildings.py @@ -1,5 +1,5 @@ """ -CTS electricity and heat demand time series for scenarios in 2035 and 2050 +CTS electricity and heat demand time series for scenarios in 2035, 2037 and 2050 assigned to OSM-buildings are generated. Disaggregation of CTS heat & electricity demand time series from MV substation @@ -60,7 +60,8 @@ class EgonCtsElectricityDemandBuildingShare(Base): Class definition of table demand.egon_cts_electricity_demand_building_share. Table including the MV substation electricity profile share of all selected - CTS buildings for scenario eGon2035 and eGon100RE. This table is created + CTS buildings for scenario nep2037_2025, eGon2035 and eGon100RE. This table is + created within :func:`cts_electricity()`. """ __tablename__ = "egon_cts_electricity_demand_building_share" @@ -77,7 +78,8 @@ class EgonCtsHeatDemandBuildingShare(Base): Class definition of table demand.egon_cts_heat_demand_building_share. Table including the MV substation heat profile share of all selected - CTS buildings for scenario eGon2035 and eGon100RE. This table is created + CTS buildings for scenario eGon2035, nep2037_2025 and eGon100RE. This table is + created within :func:`cts_heat()`. """ __tablename__ = "egon_cts_heat_demand_building_share" @@ -124,7 +126,8 @@ class BuildingHeatPeakLoads(Base): class CtsDemandBuildings(Dataset): """ - Generates CTS electricity and heat demand time series for scenarios in 2035 and 2050 + Generates CTS electricity and heat demand time series for scenarios in 2035, 2037 + and 2050 assigned to OSM-buildings. Disaggregation of CTS heat & electricity demand time series from MV Substation @@ -840,7 +843,8 @@ def calc_census_cell_share(scenario, sector): Parameters ---------- scenario: str - Scenario for which the share is calculated: "eGon2035" or "eGon100RE" + Scenario for which the share is calculated: "eGon2035", "nep2037_2025" or + "eGon100RE" sector: str Scenario for which the share is calculated: "electricity" or "heat" @@ -985,14 +989,14 @@ def calc_building_amenity_share(df_cts_buildings): def get_peta_demand(mvgd, scenario): """ Retrieve annual peta heat demand for CTS for either - eGon2035 or eGon100RE scenario. + eGon2035, nep2037_2025 or eGon100RE scenario. Parameters ---------- mvgd : int ID of substation for which to get CTS demand. scenario : str - Possible options are eGon2035 or eGon100RE + Possible options are eGon2035, nep2037_2025 or eGon100RE Returns ------- @@ -1041,7 +1045,8 @@ def calc_cts_building_profiles( Ids of the substation for which selected building profiles are calculated. scenario: str - Scenario for which the share is calculated: "eGon2035" or "eGon100RE" + Scenario for which the share is calculated: "eGon2035", "nep2037_2025" or + "eGon100RE" sector: str Sector for which the share is calculated: "electricity" or "heat" diff --git a/src/egon/data/datasets/electricity_demand_timeseries/hh_buildings.py b/src/egon/data/datasets/electricity_demand_timeseries/hh_buildings.py index 53ee0b4c0..c231b1bb8 100755 --- a/src/egon/data/datasets/electricity_demand_timeseries/hh_buildings.py +++ b/src/egon/data/datasets/electricity_demand_timeseries/hh_buildings.py @@ -1,5 +1,5 @@ """ -Household electricity demand time series for scenarios in 2035 and 2050 +Household electricity demand time series for scenarios in 2035, 2037 and 2050 assigned to OSM-buildings. """ @@ -679,7 +679,7 @@ def get_building_peak_loads(): Peak loads of buildings are determined. Timeseries for every building are accumulated, the maximum value - determined and with the respective nuts3 factor scaled for 2035 and 2050 + determined and with the respective nuts3 factor scaled for 2035, 2037 and 2050 scenario. Note @@ -697,6 +697,7 @@ def get_building_peak_loads(): HouseholdElectricityProfilesInCensusCells.factor_2019, HouseholdElectricityProfilesInCensusCells.factor_2023, HouseholdElectricityProfilesInCensusCells.factor_2035, + HouseholdElectricityProfilesInCensusCells.factor_2037, HouseholdElectricityProfilesInCensusCells.factor_2050, ) .filter( @@ -753,12 +754,14 @@ def ve(s): df_building_peak_load_nuts3 * df["factor_2019"].unique(), df_building_peak_load_nuts3 * df["factor_2023"].unique(), df_building_peak_load_nuts3 * df["factor_2035"].unique(), + df_building_peak_load_nuts3 * df["factor_2037"].unique(), df_building_peak_load_nuts3 * df["factor_2050"].unique(), ], index=[ "status2019", "status2023", "eGon2035", + "nep2037_2025", "eGon100RE", ], ).T diff --git a/src/egon/data/datasets/electricity_demand_timeseries/hh_profiles.py b/src/egon/data/datasets/electricity_demand_timeseries/hh_profiles.py index 7c14993b3..86d5e840b 100644 --- a/src/egon/data/datasets/electricity_demand_timeseries/hh_profiles.py +++ b/src/egon/data/datasets/electricity_demand_timeseries/hh_profiles.py @@ -1,5 +1,6 @@ """ -Household electricity demand time series for scenarios eGon2035 and eGon100RE at +Household electricity demand time series for scenarios eGon2035, nep2037_2025 and +eGon100RE at census cell level are set up. Electricity demand data for households in Germany in 1-hourly resolution for @@ -67,6 +68,7 @@ class HouseholdElectricityProfilesInCensusCells(Base): factor_2019 = Column(Float) factor_2023 = Column(Float) factor_2035 = Column(Float) + factor_2037_2025 = Column(Float) factor_2050 = Column(Float) @@ -106,7 +108,8 @@ class EgonEtragoElectricityHouseholds(Base): class HouseholdDemands(Dataset): """ - Household electricity demand time series for scenarios eGon2035 and eGon100RE at + Household electricity demand time series for scenarios eGon2035, nep2037_2025 and + eGon100RE at census cell level are set up. Electricity demand data for households in Germany in 1-hourly resolution for @@ -180,7 +183,7 @@ class HouseholdDemands(Dataset): `df_dist_households` to `df_census_households_grid_refined`. * Enriched 100 x 100 m household dataset is used to sample and aggregate household profiles. A table including individual profile id's for each cell - and scaling factor to match Demand-Regio annual sum projections for 2035 + and scaling factor to match Demand-Regio annual sum projections for 2035, 2037 and 2050 at NUTS3 level is created in the database as `demand.household_electricity_profiles_in_census_cells`. @@ -272,6 +275,18 @@ def __init__(self, dependencies): tasks = tasks + (mv_hh_electricity_load_2035,) + if ( + "nep2037_2025" + in egon.data.config.settings()["egon-data"]["--scenarios"] + ): + mv_hh_electricity_load_2037 = PythonOperator( + task_id="MV-hh-electricity-load-2037", + python_callable=mv_grid_district_HH_electricity_load, + op_args=["eGon2037", 2037], + ) + + tasks = tasks + (mv_hh_electricity_load_2037,) + if ( "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"] @@ -1353,6 +1368,7 @@ def assign_hh_demand_profiles_to_cells(df_zensus_cells, df_iee_profiles): "nuts3", "nuts1", "factor_2035", + "factor_2037_2025", "factor_2050", ], ) @@ -1418,7 +1434,7 @@ def adjust_to_demand_regio_nuts3_annual( ------- pd.DataFrame Returns the same data as :func:`assign_hh_demand_profiles_to_cells`, - but with filled columns `factor_2035` and `factor_2050`. + but with filled columns `factor_2035`, `factor_2037_2025` and `factor_2050`. """ for nuts3_id, df_nuts3 in df_hh_profiles_in_census_cells.groupby( by="nuts3" @@ -1733,6 +1749,7 @@ def get_cell_demand_metadata_from_db(attribute, list_of_identifiers): HouseholdElectricityProfilesInCensusCells.nuts3, HouseholdElectricityProfilesInCensusCells.nuts1, HouseholdElectricityProfilesInCensusCells.factor_2035, + HouseholdElectricityProfilesInCensusCells.factor_2037_2025, HouseholdElectricityProfilesInCensusCells.factor_2050, ).filter( HouseholdElectricityProfilesInCensusCells.nuts3.in_( @@ -1746,6 +1763,7 @@ def get_cell_demand_metadata_from_db(attribute, list_of_identifiers): HouseholdElectricityProfilesInCensusCells.nuts3, HouseholdElectricityProfilesInCensusCells.nuts1, HouseholdElectricityProfilesInCensusCells.factor_2035, + HouseholdElectricityProfilesInCensusCells.factor_2037_2025, HouseholdElectricityProfilesInCensusCells.factor_2050, ).filter( HouseholdElectricityProfilesInCensusCells.nuts1.in_( @@ -1759,6 +1777,7 @@ def get_cell_demand_metadata_from_db(attribute, list_of_identifiers): HouseholdElectricityProfilesInCensusCells.nuts3, HouseholdElectricityProfilesInCensusCells.nuts1, HouseholdElectricityProfilesInCensusCells.factor_2035, + HouseholdElectricityProfilesInCensusCells.factor_2037_2025, HouseholdElectricityProfilesInCensusCells.factor_2050, ).filter( HouseholdElectricityProfilesInCensusCells.cell_id.in_( @@ -1989,6 +2008,7 @@ def get_scaled_profiles_from_db( year: int * 2035 + * 2037 * 2050 aggregate: bool diff --git a/src/egon/data/datasets/emobility/heavy_duty_transport/__init__.py b/src/egon/data/datasets/emobility/heavy_duty_transport/__init__.py index 46a1e51ca..e5c5a1007 100644 --- a/src/egon/data/datasets/emobility/heavy_duty_transport/__init__.py +++ b/src/egon/data/datasets/emobility/heavy_duty_transport/__init__.py @@ -81,11 +81,13 @@ def download_hgv_data(): class HeavyDutyTransport(Dataset): """ Class for preparation of static and timeseries data for heavy duty transport. - + *Dependencies* * :py:class:`Vg250 ` * :py:class:`EtragoSetup ` * :py:class:`GasAreaseGon2035 ` + * :py:class:`GasAreaseGon2037_2025 + ` *Resulting tables* * :py:class:`demand.egon_heavy_duty_transport_voronoi @@ -111,6 +113,16 @@ class HeavyDutyTransport(Dataset): `supply chain leakage rate of 0.5 % `_. + ### Scenario NEP C 2037 + [needs to be updated] + + The ramp-up figures are taken from + `Scenario C 2037 Grid Development Plan 2025-2037 + `_. According to this, 100,000 e-trucks are + expected in Germany in 2035, each covering an average of 100,000 km per year. + In total this means 10 billion km. + ### Scenario NEP C 2035 The ramp-up figures are taken from diff --git a/src/egon/data/datasets/emobility/motorized_individual_travel/__init__.py b/src/egon/data/datasets/emobility/motorized_individual_travel/__init__.py index dba8a14c4..0a995be11 100644 --- a/src/egon/data/datasets/emobility/motorized_individual_travel/__init__.py +++ b/src/egon/data/datasets/emobility/motorized_individual_travel/__init__.py @@ -53,6 +53,7 @@ generate_model_data_status2023_remaining, generate_model_data_eGon100RE_remaining, generate_model_data_eGon2035_remaining, + generate_model_data_nep2037_2025_remaining, read_simbev_metadata_file, ) @@ -453,6 +454,8 @@ def generate_model_data_tasks(scenario_name): tasks.add(generate_model_data_status2019_remaining) if scenario_name == "status2023": tasks.add(generate_model_data_status2023_remaining) + elif scenario_name == "nep2037_2025": + tasks.add(generate_model_data_nep2037_2025_remaining) elif scenario_name == "eGon2035": tasks.add(generate_model_data_eGon2035_remaining) elif scenario_name == "eGon100RE": diff --git a/src/egon/data/datasets/emobility/motorized_individual_travel/model_timeseries.py b/src/egon/data/datasets/emobility/motorized_individual_travel/model_timeseries.py index c8372c717..0fb965c9b 100644 --- a/src/egon/data/datasets/emobility/motorized_individual_travel/model_timeseries.py +++ b/src/egon/data/datasets/emobility/motorized_individual_travel/model_timeseries.py @@ -2,7 +2,8 @@ Generate timeseries for eTraGo and pypsa-eur-sec Call order - * generate_model_data_eGon2035() / generate_model_data_eGon100RE() + * generate_model_data_eGon2035() / generate_model_data_nep2037_2025() / + generate_model_data_eGon100RE() * generate_model_data() * generate_model_data_grid_district() * load_evs_trips() @@ -24,6 +25,7 @@ https://nationale-leitstelle.de/wp-content/pdf/broschuere-lis-2025-2030-final.pdf (p.92): * eGon2035: home=0.8, work=1.0 +* nep2037_2025: home=0.86, work=1.0 * eGon100RE: home=1.0, work=1.0 """ @@ -1096,6 +1098,15 @@ def generate_model_data_eGon2035_remaining(): bunch=range(MVGD_MIN_COUNT, len(load_grid_district_ids())), ) +def generate_model_data_nep2037_2025_remaining(): + """Generates timeseries for nep2037_2025 scenario for grid districts which + has not been processed in the parallel tasks before. + """ + generate_model_data_bunch( + scenario_name="nep2037_2025", + bunch=range(MVGD_MIN_COUNT, len(load_grid_district_ids())), + ) + def generate_model_data_eGon100RE_remaining(): """Generates timeseries for eGon100RE scenario for grid districts which diff --git a/src/egon/data/datasets/gas_areas.py b/src/egon/data/datasets/gas_areas.py index 35dea0592..c1d087b0a 100755 --- a/src/egon/data/datasets/gas_areas.py +++ b/src/egon/data/datasets/gas_areas.py @@ -129,6 +129,13 @@ def voronoi_egon2035(): for carrier in ["CH4", "H2", "H2_saltcavern"]: create_voronoi("eGon2035", carrier) +def voronoi_egon2037_2025(): + """ + Create voronoi polygons for all gas carriers in nep2037_2025 scenario + """ + for carrier in ["CH4", "H2", "H2_saltcavern"]: + create_voronoi("nep2037_2025", carrier) + def voronoi_egon100RE(): """ @@ -261,6 +268,9 @@ class GasAreas(Dataset): if "eGon2035" in config.settings()["egon-data"]["--scenarios"]: tasks = tasks + (voronoi_egon2035,) + if "nep2037_2025" in config.settings()["egon-data"]["--scenarios"]: + tasks = tasks + (voronoi_egon2037_2025,) + if "eGon100RE" in config.settings()["egon-data"]["--scenarios"]: tasks = tasks + (voronoi_egon100RE,) extra_dependencies = extra_dependencies + ("insert_h2_grid",) diff --git a/src/egon/data/datasets/gas_grid.py b/src/egon/data/datasets/gas_grid.py index 45f44e178..fc1fe54f9 100755 --- a/src/egon/data/datasets/gas_grid.py +++ b/src/egon/data/datasets/gas_grid.py @@ -3,8 +3,8 @@ The module contains code used to insert the methane grid into the database The central module contains all code dealing with the import of data -from SciGRID_gas (IGGIELGN dataset) and inserting the CH4 buses and links -into the database for the scenarios eGon2035 and eGon100RE. +from SciGRID_gas (IGGIELGN dataset) and inserting the CH4 buses and links +into the database for the scenarios eGon2035, nep2037_2025 and eGon100RE. The SciGRID_gas data downloaded with :py:func:`download_SciGRID_gas_data` into the folder ./datasets/gas_data/data is also used by other modules. @@ -222,7 +222,7 @@ def insert_CH4_nodes_list(gas_nodes_list, scn_name="eGon2035"): # A completer avec nodes related to pipelines which have an end in the selected area et evt deplacer ds define_gas_nodes_list # Add missing columns - c = {"scn_name": "eGon2035", "carrier": "CH4"} + c = {"scn_name": scn_name, "carrier": "CH4"} gas_nodes_list = gas_nodes_list.assign(**c) gas_nodes_list = geopandas.GeoDataFrame( @@ -265,7 +265,7 @@ def define_gas_buses_abroad(scn_name="eGon2035"): Define central CH4 buses in foreign countries for eGon2035 For the scenario eGon2035, define central CH4 buses in foreign - countries. The considered foreign countries are the direct + countries. The considered foreign countries are the direct neighbouring countries, with the addition of Russia that is considered as a source of fossil CH4. Therefore, the following steps are executed: @@ -448,11 +448,11 @@ def insert_gas_buses_abroad(scn_name="eGon2035"): """ Insert CH4 buses in neighbouring countries into database for eGon2035 - * Definition of the CH4 buses abroad with the function + * Definition of the CH4 buses abroad with the function :py:func:`define_gas_buses_abroad` * Cleaning of the database table grid.egon_etrago_bus of the foreign CH4 buses of the specific scenario (eGon2035) - * Insertion of the neighbouring buses into the table grid.egon_etrago_bus. + * Insertion of the neighbouring buses into the table grid.egon_etrago_bus. Parameters ---------- @@ -926,7 +926,7 @@ def insert_gas_pipeline_list(gas_pipelines_list, scn_name="eGon2035"): Dataframe containing the gas pipelines in Germany scn_name : str Name of the scenario - + Returns ------- None @@ -1037,6 +1037,8 @@ def insert_gas_data(): scenarios = [] if "eGon2035" in s: scenarios.append("eGon2035") + if "nep2037_2025" in s: + scenarios.append("nep2037_2025") if "eGon100RE" in s: scenarios.append("eGon100RE") diff --git a/src/egon/data/datasets/gas_neighbours/__init__.py b/src/egon/data/datasets/gas_neighbours/__init__.py index 4c51b9208..fe670728c 100755 --- a/src/egon/data/datasets/gas_neighbours/__init__.py +++ b/src/egon/data/datasets/gas_neighbours/__init__.py @@ -6,6 +6,12 @@ from egon.data.datasets.gas_neighbours.eGon100RE import ( insert_gas_neigbours_eGon100RE, ) +from egon.data.datasets.gas_neighbours.nep2037_2025 import ( + grid, + insert_ocgt_abroad, + tyndp_gas_demand, + tyndp_gas_generation, +) from egon.data.datasets.gas_neighbours.eGon2035 import ( grid, insert_ocgt_abroad, @@ -26,6 +32,14 @@ def no_gas_neighbours_required(): tasks = () +if "nep2037_2025" in config.settings()["egon-data"]["--scenarios"]: + tasks = tasks + ( + tyndp_gas_generation, + tyndp_gas_demand, + grid, + insert_ocgt_abroad, + ) + if "eGon2035" in config.settings()["egon-data"]["--scenarios"]: tasks = tasks + ( tyndp_gas_generation, @@ -56,7 +70,7 @@ class GasNeighbours(Dataset): * :py:class:`grid.egon_etrago_link ` is extended * :py:class:`grid.egon_etrago_load ` is extended * :py:class:`grid.egon_etrago_generator ` is extended - + """ def __init__(self, dependencies): super().__init__( diff --git a/src/egon/data/datasets/gas_neighbours/gas_abroad.py b/src/egon/data/datasets/gas_neighbours/gas_abroad.py index 3e9b71e45..97be371ef 100755 --- a/src/egon/data/datasets/gas_neighbours/gas_abroad.py +++ b/src/egon/data/datasets/gas_neighbours/gas_abroad.py @@ -1,7 +1,7 @@ """Module containing functions to insert gas abroad In this module, functions used to insert the gas components (H2 and -CH4) abroad for eGon2035 and eGon100RE are defined. +CH4) abroad for eGon2035, nep2037_2025 and eGon100RE are defined. """ @@ -15,9 +15,9 @@ def insert_gas_grid_capacities(Neighbouring_pipe_capacities_list, scn_name): This function inserts a list of crossbordering gas pipelines after cleaning the database. - For eGon2035, all the CH4 crossbordering pipelines are inserted + For eGon2035 and nep2037_2025, all the CH4 crossbordering pipelines are inserted (no H2 grid in this scenario). - For eGon100RE, only the crossbordering pipelines with Germany + For eGon100RE and nep2037_2025, only the crossbordering pipelines with Germany are inserted (the other ones are inserted in PypsaEurSec), but in this scenario there are H2 and CH4 pipelines. @@ -43,22 +43,53 @@ def insert_gas_grid_capacities(Neighbouring_pipe_capacities_list, scn_name): db.execute_sql( f""" - DELETE FROM + DELETE FROM {sources['links']['schema']}.{sources['links']['table']} WHERE "bus0" IN ( - SELECT bus_id FROM + SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country != 'DE' AND carrier = '{carrier_bus}' AND scn_name = '{scn_name}') OR "bus1" IN ( - SELECT bus_id FROM + SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country != 'DE' - AND carrier = '{carrier_bus}' + AND carrier = '{carrier_bus}' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' + AND carrier = '{carrier_link}' + ; + """ + ) + + carriers = { + "CH4": {"bus_inDE": "CH4", "bus_abroad": "CH4"}, + "H2_retrofit": {"bus_inDE": "H2", "bus_abroad": "H2"}, + } + + if scn_name == "nep2037_2025": + carrier_link = "CH4" + carrier_bus = "CH4" + + db.execute_sql( + f""" + DELETE FROM + {sources['links']['schema']}.{sources['links']['table']} + WHERE "bus0" IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND carrier = '{carrier_bus}' + AND scn_name = '{scn_name}') + OR "bus1" IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND carrier = '{carrier_bus}' AND scn_name = '{scn_name}') AND scn_name = '{scn_name}' - AND carrier = '{carrier_link}' + AND carrier = '{carrier_link}' ; """ ) @@ -75,27 +106,27 @@ def insert_gas_grid_capacities(Neighbouring_pipe_capacities_list, scn_name): DELETE FROM {sources['links']['schema']}.{sources['links']['table']} WHERE ("bus0" IN ( - SELECT bus_id FROM + SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country != 'DE' AND carrier = '{carriers[c]["bus_abroad"]}' AND scn_name = '{scn_name}') - AND "bus1" IN (SELECT bus_id FROM + AND "bus1" IN (SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country = 'DE' - AND carrier = '{carriers[c]["bus_inDE"]}' + AND carrier = '{carriers[c]["bus_inDE"]}' AND scn_name = '{scn_name}')) OR ("bus0" IN ( - SELECT bus_id FROM + SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country = 'DE' AND carrier = '{carriers[c]["bus_inDE"]}' AND scn_name = '{scn_name}') AND "bus1" IN ( - SELECT bus_id FROM + SELECT bus_id FROM {sources['buses']['schema']}.{sources['buses']['table']} WHERE country != 'DE' - AND carrier = '{carriers[c]["bus_abroad"]}' + AND carrier = '{carriers[c]["bus_abroad"]}' AND scn_name = '{scn_name}')) AND scn_name = '{scn_name}' AND carrier = '{carriers[c]["bus_abroad"]}' @@ -122,7 +153,7 @@ def insert_gas_grid_capacities(Neighbouring_pipe_capacities_list, scn_name): INSERT INTO {targets['links']['schema']}.{targets['links']['table']} ( scn_name, link_id, carrier, bus0, bus1, p_nom, p_min_pu, length, geom, topo) - + SELECT scn_name, link_id, carrier, bus0, bus1, p_nom, p_min_pu, length, geom, topo FROM grid.egon_etrago_gas_link; diff --git a/src/egon/data/datasets/gas_neighbours/nep2037_2025.py b/src/egon/data/datasets/gas_neighbours/nep2037_2025.py new file mode 100755 index 000000000..74d7e503c --- /dev/null +++ b/src/egon/data/datasets/gas_neighbours/nep2037_2025.py @@ -0,0 +1,1587 @@ +"""Central module containing code dealing with gas neighbours for nep2037_2025 +""" + +from pathlib import Path +from urllib.request import urlretrieve +import ast +import zipfile + +from shapely.geometry import LineString, MultiLineString +import geopandas as gpd +import pandas as pd +import pypsa + +from egon.data import config, db +from egon.data.datasets.electrical_neighbours import ( + get_foreign_bus_id, + get_map_buses, +) +from egon.data.datasets.gas_neighbours.gas_abroad import ( + insert_gas_grid_capacities, +) +from egon.data.datasets.scenario_parameters import get_sector_parameters + +countries = [ + "AT", + "BE", + "CH", + "CZ", + "DK", + "FR", + "GB", + "LU", + "NL", + "NO", + "PL", + "RU", + "SE", + "UK", +] + + +def get_foreign_gas_bus_id(carrier="CH4"): + """Calculate the etrago bus id based on the geometry + + Map node_ids from TYNDP and etragos bus_id + + Parameters + ---------- + carrier : str + Name of the carrier + + Returns + ------- + pandas.Series + List of mapped node_ids from TYNDP and etragos bus_id + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + scn_name = "nep2037_2025" + + bus_id = db.select_geodataframe( + f""" + SELECT bus_id, ST_Buffer(geom, 1) as geom, country + FROM grid.egon_etrago_bus + WHERE scn_name = '{scn_name}' + AND carrier = '{carrier}' + AND country != 'DE' + """, + epsg=3035, + ) + + # insert installed capacities + file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}") + + # Select buses in neighbouring countries as geodataframe + buses = pd.read_excel( + file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(), + sheet_name="Nodes - Dict", + ).query("longitude==longitude") + buses = gpd.GeoDataFrame( + buses, + crs=4326, + geometry=gpd.points_from_xy(buses.longitude, buses.latitude), + ).to_crs(3035) + + buses["bus_id"] = 0 + + # Select bus_id from etrago with shortest distance to TYNDP node + for i, row in buses.iterrows(): + distance = bus_id.set_index("bus_id").geom.distance(row.geometry) + buses.loc[i, "bus_id"] = distance[ + distance == distance.min() + ].index.values[0] + + return buses.set_index("node_id").bus_id + + +def read_LNG_capacities(): + """Read LNG import capacities from Scigrid gas data + + Returns + ------- + IGGIELGN_LNGs: pandas.Series + LNG terminal capacities per foreign country node (in GWh/d) + + """ + lng_file = "datasets/gas_data/data/IGGIELGN_LNGs.csv" + IGGIELGN_LNGs = gpd.read_file(lng_file) + + map_countries_scigrid = { + "BE": "BE00", + "EE": "EE00", + "EL": "GR00", + "ES": "ES00", + "FI": "FI00", + "FR": "FR00", + "GB": "UK00", + "IT": "ITCN", + "LT": "LT00", + "LV": "LV00", + "MT": "MT00", + "NL": "NL00", + "PL": "PL00", + "PT": "PT00", + "SE": "SE01", + } + + conversion_factor = 437.5 # MCM/day to MWh/h + c2 = 24 / 1000 # MWh/h to GWh/d + p_nom = [] + + for index, row in IGGIELGN_LNGs.iterrows(): + param = ast.literal_eval(row["param"]) + p_nom.append( + param["max_cap_store2pipe_M_m3_per_d"] * conversion_factor * c2 + ) + + IGGIELGN_LNGs["LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)"] = p_nom + + IGGIELGN_LNGs.drop( + [ + "uncertainty", + "method", + "param", + "comment", + "tags", + "source_id", + "lat", + "long", + "geometry", + "id", + "name", + "node_id", + ], + axis=1, + inplace=True, + ) + + IGGIELGN_LNGs["Country"] = IGGIELGN_LNGs["country_code"].map( + map_countries_scigrid + ) + IGGIELGN_LNGs = ( + IGGIELGN_LNGs.groupby(["Country"])[ + "LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)" + ] + .sum() + .sort_index() + ) + + return IGGIELGN_LNGs + + +def calc_capacities(): + """Calculates gas production capacities of neighbouring countries + + For each neigbouring country, this function calculates the gas + generation capacity in 2037 using the function + :py:func:`calc_capacity_per_year` for 2030 and 2040 and + interpolates the results. These capacities include LNG import, as + well as conventional and biogas production. + Two conventional gas generators are added for Norway and Russia + interpolating the supply potential values from the TYNPD 2020 + for 2030 and 2040. + + Returns + ------- + grouped_capacities: pandas.DataFrame + Gas production capacities per foreign node + + """ + + sources = config.datasets()["gas_neighbours"]["sources"] + + # insert installed capacities + file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}") + df0 = pd.read_excel( + file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(), + sheet_name="Gas Data", + ) + + df = ( + df0.query( + 'Scenario == "Distributed Energy" &' + ' (Case == "Peak" | Case == "Average") &' + # Case: 2 Week/Average/DF/Peak + ' Category == "Production"' + ) + .drop( + columns=[ + "Generator_ID", + "Climate Year", + "Simulation_ID", + "Node 1", + "Path", + "Direct/Indirect", + "Sector", + "Note", + "Category", + "Scenario", + ] + ) + .set_index("Node/Line") + .sort_index() + ) + + lng = read_LNG_capacities() + df_2030 = calc_capacity_per_year(df, lng, 2030) + df_2040 = calc_capacity_per_year(df, lng, 2040) + + # Conversion GWh/d to MWh/h + conversion_factor = 1000 / 24 + + df_2037 = pd.concat([df_2040, df_2030], axis=1) + df_2037 = df_2037.drop( + columns=[ + "Value_conv_2040", + "Value_conv_2030", + "Value_bio_2040", + "Value_bio_2030", + ] + ) + df_2037["cap_2037"] = (df_2037["CH4_2030"] + df_2037["CH4_2040"]) / 2 + df_2037["e_nom_max"] = ( + ((df_2037["e_nom_max_2030"] + df_2037["e_nom_max_2040"]) / 2) + * conversion_factor + * 8760 + ) + df_2037["share_LNG_2037"] = ( + df_2037["share_LNG_2030"] + df_2037["share_LNG_2040"] + ) / 2 + df_2037["share_conv_pipe_2037"] = ( + df_2037["share_conv_pipe_2030"] + df_2037["share_conv_pipe_2040"] + ) / 2 + df_2037["share_bio_2037"] = ( + df_2037["share_bio_2030"] + df_2037["share_bio_2040"] + ) / 2 + + grouped_capacities = df_2037.drop( + columns=[ + "share_LNG_2030", + "share_LNG_2040", + "share_conv_pipe_2030", + "share_conv_pipe_2040", + "share_bio_2030", + "share_bio_2040", + "CH4_2040", + "CH4_2030", + "e_nom_max_2030", + "e_nom_max_2040", + ] + ).reset_index() + + grouped_capacities["cap_2037"] = ( + grouped_capacities["cap_2037"] * conversion_factor + ) + + # Add generators in Norway and Russia + df_conv = ( + df0.query('Case == "Min" & Category == "Supply Potential"') + .drop( + columns=[ + "Generator_ID", + "Climate Year", + "Simulation_ID", + "Node 1", + "Path", + "Direct/Indirect", + "Sector", + "Note", + "Category", + "Scenario", + "Parameter", + "Case", + ] + ) + .set_index("Node/Line") + .sort_index() + ) + + df_conv_2030 = df_conv[df_conv["Year"] == 2030].rename( + columns={"Value": "Value_2030"} + ) + df_conv_2040 = df_conv[df_conv["Year"] == 2040].rename( + columns={"Value": "Value_2040"} + ) + df_conv_2037 = pd.concat([df_conv_2040, df_conv_2030], axis=1) + + df_conv_2037["cap_2037_2025"] = ( + (df_conv_2037["Value_2030"] + df_conv_2037["Value_2040"]) / 2 + ) * conversion_factor + df_conv_2037["e_nom_max"] = df_conv_2037["cap_2037"] * 8760 + df_conv_2037["share_LNG_2037_2025"] = 0 + df_conv_2037["share_conv_pipe_2037_2025"] = 1 + df_conv_2037["share_bio_2037_2025"] = 0 + + df_conv_2037 = df_conv_2037.drop( + columns=[ + "Year", + "Value_2030", + "Value_2040", + ] + ).reset_index() + df_conv_2037 = df_conv_2037.rename(columns={"Node/Line": "index"}) + grouped_capacities = grouped_capacities.append(df_conv_2037) + + # choose capacities for considered countries + grouped_capacities = grouped_capacities[ + grouped_capacities["index"].str[:2].isin(countries) + ] + return grouped_capacities + + +def calc_capacity_per_year(df, lng, year): + """Calculates gas production capacities for a specified year + + For a specified year and for the foreign country nodes this function + calculates the gas production capacities, considering the gas + (conventional and bio) production capacities from TYNDP data and the + LNG import capacities from Scigrid gas data. + + The columns of the returned dataframe are the following: + * Value_bio_year: biogas production capacity (in GWh/d) + * Value_conv_year: conventional gas production capacity including + LNG imports (in GWh/d) + * CH4_year: total gas production capacity (in GWh/d). This value + is calculated using the peak production value from the TYNDP. + * e_nom_max_year: total gas production capacity representative + for the whole year (in GWh/d). This value is calculated using + the average production value from the TYNDP and will then be + used to limit the energy that can be generated in one year. + * share_LNG_year: share of LGN import capacity in the total gas + production capacity + * share_conv_pipe_year: share of conventional gas extraction + capacity in the total gas production capacity + * share_bio_year: share of biogas production capacity in the + total gas production capacity + + Parameters + ---------- + df : pandas.DataFrame + Gas (conventional and bio) production capacities from TYNDP (in GWh/d) + + lng : pandas.Series + LNG terminal capacities per foreign country node (in GWh/d) + + year : int + Year to calculate gas production capacities for + + Returns + ------- + df_year : pandas.DataFrame + Gas production capacities (in GWh/d) per foreign country node + + """ + df_conv_peak = ( + df[ + (df["Parameter"] == "Conventional") + & (df["Year"] == year) + & (df["Case"] == "Peak") + ] + .rename(columns={"Value": f"Value_conv_{year}_peak"}) + .drop(columns=["Parameter", "Year", "Case"]) + ) + df_conv_average = ( + df[ + (df["Parameter"] == "Conventional") + & (df["Year"] == year) + & (df["Case"] == "Average") + ] + .rename(columns={"Value": f"Value_conv_{year}_average"}) + .drop(columns=["Parameter", "Year", "Case"]) + ) + df_bioch4 = ( + df[ + (df["Parameter"] == "Biomethane") + & (df["Year"] == year) + & (df["Case"] == "Peak") + # Peak and Average have the same values for biogas + # production in 2030 and 2040 + ] + .rename(columns={"Value": f"Value_bio_{year}"}) + .drop(columns=["Parameter", "Year", "Case"]) + ) + + # Some values are duplicated (DE00 in 2030) + df_conv_peak = df_conv_peak[~df_conv_peak.index.duplicated(keep="first")] + df_conv_average = df_conv_average[ + ~df_conv_average.index.duplicated(keep="first") + ] + + df_year = pd.concat( + [df_conv_peak, df_conv_average, df_bioch4, lng], axis=1 + ).fillna(0) + df_year = df_year[ + ~( + (df_year[f"Value_conv_{year}_peak"] == 0) + & (df_year[f"Value_bio_{year}"] == 0) + & (df_year["LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)"] == 0) + ) + ] + df_year[f"Value_conv_{year}"] = ( + df_year[f"Value_conv_{year}_peak"] + + df_year["LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)"] + ) + df_year[f"CH4_{year}"] = ( + df_year[f"Value_conv_{year}"] + df_year[f"Value_bio_{year}"] + ) + df_year[f"e_nom_max_{year}"] = ( + df_year[f"Value_conv_{year}_average"] + + df_year[f"Value_bio_{year}"] + + df_year["LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)"] + ) + df_year[f"share_LNG_{year}"] = ( + df_year["LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)"] + / df_year[f"e_nom_max_{year}"] + ) + df_year[f"share_conv_pipe_{year}"] = ( + df_year[f"Value_conv_{year}_average"] / df_year[f"e_nom_max_{year}"] + ) + df_year[f"share_bio_{year}"] = ( + df_year[f"Value_bio_{year}"] / df_year[f"e_nom_max_{year}"] + ) + + df_year = df_year.drop( + columns=[ + "LNG max_cap_store2pipe_M_m3_per_d (in GWh/d)", + f"Value_conv_{year}_average", + f"Value_conv_{year}_peak", + ] + ) + + return df_year + + +def insert_generators(gen): + """Insert gas generators for foreign countries into the database + + Insert gas generators for foreign countries into the database. + The marginal cost of the methane is calculated as the sum of the + imported LNG cost, the conventional natural gas cost and the + biomethane cost, weighted by their share in the total import/ + production capacity. + LNG gas is considered to be 30% more expensive than the natural gas + transported by pipelines (source: iwd, 2022). + + Parameters + ---------- + gen : pandas.DataFrame + Gas production capacities per foreign node and energy carrier + + Returns + ------- + None + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + targets = config.datasets()["gas_neighbours"]["targets"] + map_buses = get_map_buses() + scn_params = get_sector_parameters("gas", "nep2037_2025") + + # Delete existing data + db.execute_sql( + f""" + DELETE FROM + {targets['generators']['schema']}.{targets['generators']['table']} + WHERE bus IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND scn_name = 'nep2037_2025') + AND scn_name = 'nep2037_2025' + AND carrier = 'CH4'; + """ + ) + + # Set bus_id + gen.loc[gen[gen["index"].isin(map_buses.keys())].index, "index"] = gen.loc[ + gen[gen["index"].isin(map_buses.keys())].index, "index" + ].map(map_buses) + gen.loc[:, "bus"] = ( + get_foreign_gas_bus_id().loc[gen.loc[:, "index"]].values + ) + + # Add missing columns + c = {"scn_name": "nep2037_2025", "carrier": "CH4"} + gen = gen.assign(**c) + + new_id = db.next_etrago_id("generator") + gen["generator_id"] = range(new_id, new_id + len(gen)) + gen["p_nom"] = gen["cap_2037_2025"] + gen["marginal_cost"] = ( + gen["share_LNG_2037_2025"] * scn_params["marginal_cost"]["CH4"] * 1.3 + + gen["share_conv_pipe_2037_2025"] * scn_params["marginal_cost"]["CH4"] + + gen["share_bio_2037_2025"] * scn_params["marginal_cost"]["biogas"] + ) + + # Remove useless columns + gen = gen.drop( + columns=[ + "index", + "share_LNG_2037_2025", + "share_conv_pipe_2037_2025", + "share_bio_2037_2025", + "cap_2037_2025", + ] + ) + + # Insert data to db + gen.to_sql( + targets["generators"]["table"], + db.engine(), + schema=targets["generators"]["schema"], + index=False, + if_exists="append", + ) + + +def calc_global_ch4_demand(Norway_global_demand_1y): + """Calculates global CH4 demands abroad for nep2037_2025 scenario + + The data comes from TYNDP 2020 according to NEP 2021 from the + scenario 'Distributed Energy'; linear interpolates between 2030 + and 2040. + + Returns + ------- + pandas.DataFrame + Global (yearly) CH4 final demand per foreign node + + """ + + sources = config.datasets()["gas_neighbours"]["sources"] + + file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}") + df = pd.read_excel( + file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(), + sheet_name="Gas Data", + ) + + df = ( + df.query( + 'Scenario == "Distributed Energy" & ' + 'Case == "Average" &' + 'Category == "Demand"' + ) + .drop( + columns=[ + "Generator_ID", + "Climate Year", + "Simulation_ID", + "Node 1", + "Path", + "Direct/Indirect", + "Sector", + "Note", + "Category", + "Case", + "Scenario", + ] + ) + .set_index("Node/Line") + ) + + df_2030 = ( + df[(df["Parameter"] == "Final demand") & (df["Year"] == 2030)] + .rename(columns={"Value": "Value_2030"}) + .drop(columns=["Parameter", "Year"]) + ) + + df_2040 = ( + df[(df["Parameter"] == "Final demand") & (df["Year"] == 2040)] + .rename(columns={"Value": "Value_2040"}) + .drop(columns=["Parameter", "Year"]) + ) + + # Conversion GWh/d to MWh/y + conversion_factor = 1000 * 365 + + df_2037 = pd.concat([df_2040, df_2030], axis=1) + df_2037["GlobD_2037_2025"] = ( + (df_2037["Value_2030"] + df_2037["Value_2040"]) / 2 + ) * conversion_factor + df_2037.loc["NOS0"] = [ + 0, + 0, + Norway_global_demand_1y, + ] # Manually add Norway demand + grouped_demands = df_2037.drop( + columns=["Value_2030", "Value_2040"] + ).reset_index() + + # choose demands for considered countries + return grouped_demands[ + grouped_demands["Node/Line"].str[:2].isin(countries) + ] + + +def import_ch4_demandTS(): + """Calculate global CH4 demand in Norway and CH4 demand profile + + Import from the PyPSA-eur-sec run the time series of residential + rural heat per neighbor country. This time series is used to + calculate: + * the global (yearly) heat demand of Norway + (that will be supplied by CH4) + * the normalized CH4 hourly resolved demand profile + + Returns + ------- + Norway_global_demand: Float + Yearly heat demand of Norway in MWh + neighbor_loads_t: pandas.DataFrame + Normalized CH4 hourly resolved demand profiles per neighbor country + + """ + + cwd = Path(".") + target_file = ( + cwd + / "data_bundle_egon_data" + / "pypsa_eur_sec" + / "2022-07-26-egondata-integration" + / "postnetworks" + / "elec_s_37_lv2.0__Co2L0-1H-T-H-B-I-dist1_2050.nc" + ) + + network = pypsa.Network(str(target_file)) + + # Set country tag for all buses + network.buses.country = network.buses.index.str[:2] + neighbors = network.buses[network.buses.country != "DE"] + neighbors = neighbors[ + (neighbors["country"].isin(countries)) + & (neighbors["carrier"] == "residential rural heat") + ].drop_duplicates(subset="country") + + neighbor_loads = network.loads[network.loads.bus.isin(neighbors.index)] + neighbor_loads_t_index = neighbor_loads.index[ + neighbor_loads.index.isin(network.loads_t.p_set.columns) + ] + neighbor_loads_t = network.loads_t["p_set"][neighbor_loads_t_index] + Norway_global_demand = neighbor_loads_t[ + "NO3 0 residential rural heat" + ].sum() + + for i in neighbor_loads_t.columns: + neighbor_loads_t[i] = neighbor_loads_t[i] / neighbor_loads_t[i].sum() + + return Norway_global_demand, neighbor_loads_t + + +def insert_ch4_demand(global_demand, normalized_ch4_demandTS): + """Insert CH4 demands abroad into the database for nep2037 + + Parameters + ---------- + global_demand : pandas.DataFrame + Global CH4 demand per foreign node in 1 year + gas_demandTS : pandas.DataFrame + Normalized time series of the demand per foreign country + + Returns + ------- + None + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + targets = config.datasets()["gas_neighbours"]["targets"] + map_buses = get_map_buses() + + scn_name = "nep2037_2025" + carrier = "CH4" + + # Delete existing data + db.execute_sql( + f""" + DELETE FROM + { + targets['load_time series']['schema'] + }.{ + targets['load_time series']['table'] + } + WHERE "load_id" IN ( + SELECT load_id FROM + {targets['loads']['schema']}.{targets['loads']['table']} + WHERE bus IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' + AND carrier = '{carrier}' + ); + """ + ) + + db.execute_sql( + f""" + DELETE FROM + {targets['loads']['schema']}.{targets['loads']['table']} + WHERE bus IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' + AND carrier = '{carrier}' + """ + ) + + # Set bus_id + global_demand.loc[ + global_demand[global_demand["Node/Line"].isin(map_buses.keys())].index, + "Node/Line", + ] = global_demand.loc[ + global_demand[global_demand["Node/Line"].isin(map_buses.keys())].index, + "Node/Line", + ].map( + map_buses + ) + global_demand.loc[:, "bus"] = ( + get_foreign_gas_bus_id().loc[global_demand.loc[:, "Node/Line"]].values + ) + + # Add missing columns + c = {"scn_name": scn_name, "carrier": carrier} + global_demand = global_demand.assign(**c) + + new_id = db.next_etrago_id("load") + global_demand["load_id"] = range(new_id, new_id + len(global_demand)) + + ch4_demand_TS = global_demand.copy() + # Remove useless columns + global_demand = global_demand.drop(columns=["Node/Line", "GlobD_2037_2025"]) + + # Insert data to db + global_demand.to_sql( + targets["loads"]["table"], + db.engine(), + schema=targets["loads"]["schema"], + index=False, + if_exists="append", + ) + + # Insert time series + ch4_demand_TS["Node/Line"] = ch4_demand_TS["Node/Line"].replace( + ["UK00"], "GB" + ) + + p_set = [] + for index, row in ch4_demand_TS.iterrows(): + normalized_TS_df = normalized_ch4_demandTS.loc[ + :, + normalized_ch4_demandTS.columns.str.contains(row["Node/Line"][:2]), + ] + p_set.append( + ( + normalized_TS_df[normalized_TS_df.columns[0]] + * row["GlobD_2037_2025"] + ).tolist() + ) + + ch4_demand_TS["p_set"] = p_set + ch4_demand_TS["temp_id"] = 1 + ch4_demand_TS = ch4_demand_TS.drop( + columns=["Node/Line", "GlobD_2037_2025", "bus", "carrier"] + ) + + # Insert data to DB + ch4_demand_TS.to_sql( + targets["load_time series"]["table"], + db.engine(), + schema=targets["load_time series"]["schema"], + index=False, + if_exists="append", + ) + + +def calc_ch4_storage_capacities(): + """Calculate CH4 storage capacities for neighboring countries + + Returns + ------- + ch4_storage_capacities: pandas.DataFrame + Methane gas storage capacities per country in MWh + + """ + target_file = ( + Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Storages.csv" + ) + + ch4_storage_capacities = pd.read_csv( + target_file, + delimiter=";", + decimal=".", + usecols=["country_code", "param"], + ) + + ch4_storage_capacities = ch4_storage_capacities[ + ch4_storage_capacities["country_code"].isin(countries) + ] + + map_countries_scigrid = { + "AT": "AT00", + "BE": "BE00", + "CZ": "CZ00", + "DK": "DKE1", + "EE": "EE00", + "EL": "GR00", + "ES": "ES00", + "FI": "FI00", + "FR": "FR00", + "GB": "UK00", + "IT": "ITCN", + "LT": "LT00", + "LV": "LV00", + "MT": "MT00", + "NL": "NL00", + "PL": "PL00", + "PT": "PT00", + "SE": "SE01", + } + + # Define new columns + max_workingGas_M_m3 = [] + end_year = [] + + for index, row in ch4_storage_capacities.iterrows(): + param = ast.literal_eval(row["param"]) + end_year.append(param["end_year"]) + max_workingGas_M_m3.append(param["max_workingGas_M_m3"]) + + end_year = [float("inf") if x is None else x for x in end_year] + ch4_storage_capacities = ch4_storage_capacities.assign(end_year=end_year) + ch4_storage_capacities = ch4_storage_capacities[ + ch4_storage_capacities["end_year"] >= 2037 + ] + + # Calculate e_nom + conv_factor = ( + 10830 # M_m3 to MWh - gross calorific value = 39 MJ/m3 (eurogas.org) + ) + ch4_storage_capacities["e_nom"] = [ + conv_factor * i for i in max_workingGas_M_m3 + ] + + ch4_storage_capacities.drop( + ["param", "end_year"], + axis=1, + inplace=True, + ) + + ch4_storage_capacities["Country"] = ch4_storage_capacities[ + "country_code" + ].map(map_countries_scigrid) + ch4_storage_capacities = ch4_storage_capacities.groupby( + ["country_code"] + ).agg( + { + "e_nom": "sum", + "Country": "first", + }, + ) + + ch4_storage_capacities = ch4_storage_capacities.drop(["RU"]) + ch4_storage_capacities.loc[:, "bus"] = ( + get_foreign_gas_bus_id() + .loc[ch4_storage_capacities.loc[:, "Country"]] + .values + ) + + return ch4_storage_capacities + + +def insert_storage(ch4_storage_capacities): + """Insert CH4 storage capacities into the database for nep2037_2025 + + Parameters + ---------- + ch4_storage_capacities : pandas.DataFrame + Methane gas storage capacities per country in MWh + + Returns + ------- + None + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + targets = config.datasets()["gas_neighbours"]["targets"] + + # Clean table + db.execute_sql( + f""" + DELETE FROM {targets['stores']['schema']}.{targets['stores']['table']} + WHERE "carrier" = 'CH4' + AND scn_name = 'nep2037_2025' + AND bus IN ( + SELECT bus_id + FROM {sources['buses']['schema']}.{sources['buses']['table']} + WHERE scn_name = 'nep2037_2025' + AND country != 'DE' + ); + """ + ) + # Add missing columns + c = {"scn_name": "nep2037_2025", "carrier": "CH4"} + ch4_storage_capacities = ch4_storage_capacities.assign(**c) + + new_id = db.next_etrago_id("store") + ch4_storage_capacities["store_id"] = range( + new_id, new_id + len(ch4_storage_capacities) + ) + + ch4_storage_capacities.drop( + ["Country"], + axis=1, + inplace=True, + ) + + ch4_storage_capacities = ch4_storage_capacities.reset_index(drop=True) + # Insert data to db + ch4_storage_capacities.to_sql( + targets["stores"]["table"], + db.engine(), + schema=targets["stores"]["schema"], + index=False, + if_exists="append", + ) + + +def calc_global_power_to_h2_demand(): + """Calculate H2 demand abroad for nep2037_2025 scenario + + Calculates global power demand abroad linked to H2 production. + The data comes from TYNDP 2020 according to NEP 2021 from the + scenario 'Distributed Energy'; linear interpolate between 2030 + and 2040. + + Returns + ------- + global_power_to_h2_demand : pandas.DataFrame + Global hourly power-to-h2 demand per foreign node + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + + file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}") + df = pd.read_excel( + file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(), + sheet_name="Gas Data", + ) + + df = ( + df.query( + 'Scenario == "Distributed Energy" & ' + 'Case == "Average" &' + 'Parameter == "P2H2"' + ) + .drop( + columns=[ + "Generator_ID", + "Climate Year", + "Simulation_ID", + "Node 1", + "Path", + "Direct/Indirect", + "Sector", + "Note", + "Category", + "Case", + "Scenario", + "Parameter", + ] + ) + .set_index("Node/Line") + ) + + df_2030 = ( + df[df["Year"] == 2030] + .rename(columns={"Value": "Value_2030"}) + .drop(columns=["Year"]) + ) + df_2040 = ( + df[df["Year"] == 2040] + .rename(columns={"Value": "Value_2040"}) + .drop(columns=["Year"]) + ) + + # Conversion GWh/d to MWh/h + conversion_factor = 1000 / 24 + + df_2037 = pd.concat([df_2040, df_2030], axis=1) + df_2037["GlobD_2035"] = ( + (df_2037["Value_2030"] + df_2037["Value_2040"]) / 2 + ) * conversion_factor + + global_power_to_h2_demand = df_2037.drop( + columns=["Value_2030", "Value_2040"] + ) + + # choose demands for considered countries + global_power_to_h2_demand = global_power_to_h2_demand[ + (global_power_to_h2_demand.index.str[:2].isin(countries)) + & (global_power_to_h2_demand["GlobD_2037_2025"] != 0) + ] + + # Split in two the demands for DK and UK + global_power_to_h2_demand.loc["DKW1"] = ( + global_power_to_h2_demand.loc["DKE1"] / 2 + ) + global_power_to_h2_demand.loc["DKE1"] = ( + global_power_to_h2_demand.loc["DKE1"] / 2 + ) + global_power_to_h2_demand.loc["UKNI"] = ( + global_power_to_h2_demand.loc["UK00"] / 2 + ) + global_power_to_h2_demand.loc["UK00"] = ( + global_power_to_h2_demand.loc["UK00"] / 2 + ) + global_power_to_h2_demand = global_power_to_h2_demand.reset_index() + + return global_power_to_h2_demand + + +def insert_power_to_h2_demand(global_power_to_h2_demand): + """Insert H2 demands into database for nep2037_2025 + + Parameters + ---------- + global_power_to_h2_demand : pandas.DataFrame + Global hourly power-to-h2 demand per foreign node + + Returns + ------- + None + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + targets = config.datasets()["gas_neighbours"]["targets"] + map_buses = get_map_buses() + + scn_name = "nep2037_2025" + carrier = "H2_for_industry" + + db.execute_sql( + f""" + DELETE FROM + {targets['loads']['schema']}.{targets['loads']['table']} + WHERE bus IN ( + SELECT bus_id FROM + {sources['buses']['schema']}.{sources['buses']['table']} + WHERE country != 'DE' + AND scn_name = '{scn_name}') + AND scn_name = '{scn_name}' + AND carrier = '{carrier}' + """ + ) + + # Set bus_id + global_power_to_h2_demand.loc[ + global_power_to_h2_demand[ + global_power_to_h2_demand["Node/Line"].isin(map_buses.keys()) + ].index, + "Node/Line", + ] = global_power_to_h2_demand.loc[ + global_power_to_h2_demand[ + global_power_to_h2_demand["Node/Line"].isin(map_buses.keys()) + ].index, + "Node/Line", + ].map( + map_buses + ) + global_power_to_h2_demand.loc[:, "bus"] = ( + get_foreign_bus_id() + .loc[global_power_to_h2_demand.loc[:, "Node/Line"]] + .values + ) + + # Add missing columns + c = {"scn_name": scn_name, "carrier": carrier} + global_power_to_h2_demand = global_power_to_h2_demand.assign(**c) + + new_id = db.next_etrago_id("load") + global_power_to_h2_demand["load_id"] = range( + new_id, new_id + len(global_power_to_h2_demand) + ) + + global_power_to_h2_demand = global_power_to_h2_demand.rename( + columns={"GlobD_2037_2025": "p_set"} + ) + + # Remove useless columns + global_power_to_h2_demand = global_power_to_h2_demand.drop( + columns=["Node/Line"] + ) + + # Insert data to db + global_power_to_h2_demand.to_sql( + targets["loads"]["table"], + db.engine(), + schema=targets["loads"]["schema"], + index=False, + if_exists="append", + ) + + +def calculate_ch4_grid_capacities(): + """Calculates CH4 grid capacities for foreign countries based on TYNDP-data + + Returns + ------- + Neighbouring_pipe_capacities_list : pandas.DataFrame + Table containing the CH4 grid capacity for each foreign + country + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + + # Download file + basename = "ENTSOG_TYNDP_2020_Annex_C2_Capacities_per_country.xlsx" + url = "https://www.entsog.eu/sites/default/files/2021-07/" + basename + target_file = Path(".") / "datasets" / "gas_data" / basename + + urlretrieve(url, target_file) + map_pipelines = { + "NORDSTREAM": "RU00", + "NORDSTREAM 2": "RU00", + "OPAL": "DE", + "YAMAL (BY)": "RU00", + "Denmark": "DKE1", + "Belgium": "BE00", + "Netherlands": "NL00", + "Norway": "NOM1", + "Switzerland": "CH00", + "Poland": "PL00", + "United Kingdom": "UK00", + "Germany": "DE", + "Austria": "AT00", + "France": "FR00", + "Czechia": "CZ00", + "Russia": "RU00", + "Luxemburg": "LUB1", + } + + grid_countries = [ + "NORDSTREAM", + "NORDSTREAM 2", + "OPAL", + "YAMAL (BY)", + "Denmark", + "Belgium", + "Netherlands", + "Norway", + "Switzerland", + "Poland", + "United Kingdom", + "Germany", + "Austria", + "France", + "Czechia", + "Russia", + "Luxemburg", + ] + + # Read-in data from csv-file + pipe_capacities_list = pd.read_excel( + target_file, + sheet_name="Transmission Peak Capacity", + skiprows=range(4), + ) + pipe_capacities_list = pipe_capacities_list[ + ["To Country", "Unnamed: 3", "From Country", 2037] + ].rename( + columns={ + "Unnamed: 3": "Scenario", + "To Country": "To_Country", + "From Country": "From_Country", + } + ) + pipe_capacities_list["To_Country"] = pd.Series( + pipe_capacities_list["To_Country"] + ).fillna(method="ffill") + pipe_capacities_list["From_Country"] = pd.Series( + pipe_capacities_list["From_Country"] + ).fillna(method="ffill") + pipe_capacities_list = pipe_capacities_list[ + pipe_capacities_list["Scenario"] == "Advanced" + ].drop(columns={"Scenario"}) + pipe_capacities_list = pipe_capacities_list[ + ( + (pipe_capacities_list["To_Country"].isin(grid_countries)) + & (pipe_capacities_list["From_Country"].isin(grid_countries)) + ) + & (pipe_capacities_list[2037] != 0) + ] + pipe_capacities_list["To_Country"] = pipe_capacities_list[ + "To_Country" + ].map(map_pipelines) + pipe_capacities_list["From_Country"] = pipe_capacities_list[ + "From_Country" + ].map(map_pipelines) + pipe_capacities_list["countrycombination"] = pipe_capacities_list[ + ["To_Country", "From_Country"] + ].apply( + lambda x: tuple(sorted([str(x.To_Country), str(x.From_Country)])), + axis=1, + ) + + pipeline_strategies = { + "To_Country": "first", + "From_Country": "first", + 2037: sum, + } + + pipe_capacities_list = pipe_capacities_list.groupby( + ["countrycombination"] + ).agg(pipeline_strategies) + + # Add manually DK-SE and AT-CH pipes (Scigrid gas data) + pipe_capacities_list.loc["(DKE1, SE02)"] = ["DKE1", "SE02", 651] + pipe_capacities_list.loc["(AT00, CH00)"] = ["AT00", "CH00", 651] + + # Conversion GWh/d to MWh/h + pipe_capacities_list["p_nom"] = pipe_capacities_list[2037] * (1000 / 24) + + # Border crossing CH4 pipelines between foreign countries + + Neighbouring_pipe_capacities_list = pipe_capacities_list[ + (pipe_capacities_list["To_Country"] != "DE") + & (pipe_capacities_list["From_Country"] != "DE") + ].reset_index() + + Neighbouring_pipe_capacities_list.loc[:, "bus0"] = ( + get_foreign_gas_bus_id() + .loc[Neighbouring_pipe_capacities_list.loc[:, "To_Country"]] + .values + ) + Neighbouring_pipe_capacities_list.loc[:, "bus1"] = ( + get_foreign_gas_bus_id() + .loc[Neighbouring_pipe_capacities_list.loc[:, "From_Country"]] + .values + ) + + # Adjust columns + Neighbouring_pipe_capacities_list = Neighbouring_pipe_capacities_list.drop( + columns=[ + "To_Country", + "From_Country", + "countrycombination", + 2035, + ] + ) + + new_id = db.next_etrago_id("link") + Neighbouring_pipe_capacities_list["link_id"] = range( + new_id, new_id + len(Neighbouring_pipe_capacities_list) + ) + + # Border crossing CH4 pipelines between DE and neighbouring countries + DE_pipe_capacities_list = pipe_capacities_list[ + (pipe_capacities_list["To_Country"] == "DE") + | (pipe_capacities_list["From_Country"] == "DE") + ].reset_index() + + dict_cross_pipes_DE = { + ("AT00", "DE"): "AT", + ("BE00", "DE"): "BE", + ("CH00", "DE"): "CH", + ("CZ00", "DE"): "CZ", + ("DE", "DKE1"): "DK", + ("DE", "FR00"): "FR", + ("DE", "LUB1"): "LU", + ("DE", "NL00"): "NL", + ("DE", "NOM1"): "NO", + ("DE", "PL00"): "PL", + ("DE", "RU00"): "RU", + } + + DE_pipe_capacities_list["country_code"] = DE_pipe_capacities_list[ + "countrycombination" + ].map(dict_cross_pipes_DE) + DE_pipe_capacities_list = DE_pipe_capacities_list.set_index("country_code") + + schema = sources["buses"]["schema"] + table = sources["buses"]["table"] + for country_code in [e for e in countries if e not in ("GB", "SE", "UK")]: + # Select cross-bording links + cap_DE = db.select_dataframe( + f"""SELECT link_id, bus0, bus1 + FROM {sources['links']['schema']}.{sources['links']['table']} + WHERE scn_name = 'nep2037_2025' + AND carrier = 'CH4' + AND (("bus0" IN ( + SELECT bus_id FROM {schema}.{table} + WHERE country = 'DE' + AND carrier = 'CH4' + AND scn_name = 'nep2037_2025') + AND "bus1" IN (SELECT bus_id FROM {schema}.{table} + WHERE country = '{country_code}' + AND carrier = 'CH4' + AND scn_name = 'nep2037_2025') + ) + OR ("bus0" IN ( + SELECT bus_id FROM {schema}.{table} + WHERE country = '{country_code}' + AND carrier = 'CH4' + AND scn_name = 'nep2037_2025') + AND "bus1" IN (SELECT bus_id FROM {schema}.{table} + WHERE country = 'DE' + AND carrier = 'CH4' + AND scn_name = 'nep2037_2025')) + ) + ;""" + ) + + cap_DE["p_nom"] = DE_pipe_capacities_list.at[ + country_code, "p_nom" + ] / len(cap_DE.index) + Neighbouring_pipe_capacities_list = ( + Neighbouring_pipe_capacities_list.append(cap_DE) + ) + + # Add topo, geom and length + bus_geom = db.select_geodataframe( + """SELECT bus_id, geom + FROM grid.egon_etrago_bus + WHERE scn_name = 'nep2037_2025' + AND carrier = 'CH4' + """, + epsg=4326, + ).set_index("bus_id") + + coordinates_bus0 = [] + coordinates_bus1 = [] + + for index, row in Neighbouring_pipe_capacities_list.iterrows(): + coordinates_bus0.append(bus_geom["geom"].loc[int(row["bus0"])]) + coordinates_bus1.append(bus_geom["geom"].loc[int(row["bus1"])]) + + Neighbouring_pipe_capacities_list["coordinates_bus0"] = coordinates_bus0 + Neighbouring_pipe_capacities_list["coordinates_bus1"] = coordinates_bus1 + + Neighbouring_pipe_capacities_list[ + "topo" + ] = Neighbouring_pipe_capacities_list.apply( + lambda row: LineString( + [row["coordinates_bus0"], row["coordinates_bus1"]] + ), + axis=1, + ) + Neighbouring_pipe_capacities_list[ + "geom" + ] = Neighbouring_pipe_capacities_list.apply( + lambda row: MultiLineString([row["topo"]]), axis=1 + ) + Neighbouring_pipe_capacities_list[ + "length" + ] = Neighbouring_pipe_capacities_list.apply( + lambda row: row["topo"].length, axis=1 + ) + + # Remove useless columns + Neighbouring_pipe_capacities_list = Neighbouring_pipe_capacities_list.drop( + columns=[ + "coordinates_bus0", + "coordinates_bus1", + ] + ) + + # Add missing columns + c = {"scn_name": "nep2037_2025", "carrier": "CH4", "p_min_pu": -1.0} + Neighbouring_pipe_capacities_list = ( + Neighbouring_pipe_capacities_list.assign(**c) + ) + + Neighbouring_pipe_capacities_list = ( + Neighbouring_pipe_capacities_list.set_geometry("geom", crs=4326) + ) + + return Neighbouring_pipe_capacities_list + + +def tyndp_gas_generation(): + """Insert data from TYNDP 2020 according to NEP 2021 + Scenario 'Distributed Energy'; linear interpolate between 2030 and 2040 + + Returns + ------- + None + """ + capacities = calc_capacities() + insert_generators(capacities) + + ch4_storage_capacities = calc_ch4_storage_capacities() + insert_storage(ch4_storage_capacities) + + +def tyndp_gas_demand(): + """Insert gas demands abroad for nep2037_2025 + + Insert CH4 and H2 demands abroad for nep2037_2025 by executing the + following steps: + * CH4 + * Calculation of the global CH4 demand in Norway and the + CH4 demand profile by executing the function + :py:func:`import_ch4_demandTS` + * Calculation of the global CH4 demands by executing the + function :py:func:`calc_global_ch4_demand` + * Insertion of the CH4 loads and their associated time + series in the database by executing the function + :py:func:`insert_ch4_demand` + * H2 + * Calculation of the global power demand abroad linked + to H2 production by executing the function + :py:func:`calc_global_power_to_h2_demand` + * Insertion of these loads in the database by executing the + function :py:func:`insert_power_to_h2_demand` + + Returns + ------- + None + + """ + Norway_global_demand_1y, normalized_ch4_demandTS = import_ch4_demandTS() + global_ch4_demand = calc_global_ch4_demand(Norway_global_demand_1y) + insert_ch4_demand(global_ch4_demand, normalized_ch4_demandTS) + + global_power_to_h2_demand = calc_global_power_to_h2_demand() + insert_power_to_h2_demand(global_power_to_h2_demand) + + +def grid(): + """Insert data from TYNDP 2020 according to NEP 2021 + Scenario 'Distributed Energy; linear interpolate between 2030 and 2040 + + Returns + ------- + None + """ + Neighbouring_pipe_capacities_list = calculate_ch4_grid_capacities() + insert_gas_grid_capacities( + Neighbouring_pipe_capacities_list, scn_name="nep2037_2025" + ) + + +def calculate_ocgt_capacities(): + """Calculate gas turbine capacities abroad for nep2037_2025 + + Calculate gas turbine capacities abroad for nep2037_2025 based on TYNDP + 2020, scenario "Distributed Energy"; interpolated between 2030 and 2040 + + Returns + ------- + df_ocgt: pandas.DataFrame + Gas turbine capacities per foreign node + + """ + sources = config.datasets()["gas_neighbours"]["sources"] + + # insert installed capacities + file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}") + df = pd.read_excel( + file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(), + sheet_name="Capacity", + ) + + df_ocgt = df[ + [ + "Node/Line", + "Scenario", + "Climate Year", + "Generator_ID", + "Year", + "Value", + ] + ] + df_ocgt = df_ocgt[ + (df_ocgt["Scenario"] == "Distributed Energy") + & (df_ocgt["Climate Year"] == 1984) + ] + df_ocgt = df_ocgt[df_ocgt["Generator_ID"].str.contains("Gas")] + df_ocgt = df_ocgt[df_ocgt["Year"].isin([2030, 2040])] + + df_ocgt = ( + df_ocgt.groupby(["Node/Line", "Year"])["Value"].sum().reset_index() + ) + df_ocgt = df_ocgt.groupby([df_ocgt["Node/Line"], "Year"]).sum() + df_ocgt = df_ocgt.groupby("Node/Line")["Value"].mean() + df_ocgt = pd.DataFrame(df_ocgt, columns=["Value"]).rename( + columns={"Value": "p_nom"} + ) + + # Choose capacities for considered countries + df_ocgt = df_ocgt[df_ocgt.index.str[:2].isin(countries)] + + # Attribute bus0 and bus1 + df_ocgt["bus0"] = get_foreign_gas_bus_id()[df_ocgt.index] + df_ocgt["bus1"] = get_foreign_bus_id()[df_ocgt.index] + df_ocgt = df_ocgt.groupby(by=["bus0", "bus1"], as_index=False).sum() + + return df_ocgt + +def insert_ocgt_abroad(): + """Insert gas turbine capacities abroad for nep2037_2025 in the database + + Parameters + ---------- + df_ocgt: pandas.DataFrame + Gas turbine capacities per foreign node + + Returns + ------- + None + + """ + scn_name = "nep2037_2025" + carrier = "OCGT" + + # Connect to local database + engine = db.engine() + + df_ocgt = calculate_ocgt_capacities() + + df_ocgt["p_nom_extendable"] = False + df_ocgt["carrier"] = carrier + df_ocgt["scn_name"] = scn_name + + buses = tuple( + db.select_dataframe( + f"""SELECT bus_id FROM grid.egon_etrago_bus + WHERE scn_name = '{scn_name}' AND country != 'DE'; + """ + )["bus_id"] + ) + + # Delete old entries + db.execute_sql( + f""" + DELETE FROM grid.egon_etrago_link WHERE "carrier" = '{carrier}' + AND scn_name = '{scn_name}' + AND bus0 IN {buses} AND bus1 IN {buses}; + """ + ) + + # read carrier information from scnario parameter data + scn_params = get_sector_parameters("gas", scn_name) + df_ocgt["efficiency"] = scn_params["efficiency"][carrier] + df_ocgt["marginal_cost"] = ( + scn_params["marginal_cost"][carrier] + / scn_params["efficiency"][carrier] + ) + + # Adjust p_nom + df_ocgt["p_nom"] = df_ocgt["p_nom"] / scn_params["efficiency"][carrier] + + # Select next id value + new_id = db.next_etrago_id("link") + df_ocgt["link_id"] = range(new_id, new_id + len(df_ocgt)) + + # Insert data to db + df_ocgt.to_sql( + "egon_etrago_link", + engine, + schema="grid", + index=False, + if_exists="append", + ) diff --git a/src/egon/data/datasets/heat_demand_timeseries/__init__.py b/src/egon/data/datasets/heat_demand_timeseries/__init__.py index 2f8ce6050..77e32c117 100644 --- a/src/egon/data/datasets/heat_demand_timeseries/__init__.py +++ b/src/egon/data/datasets/heat_demand_timeseries/__init__.py @@ -341,9 +341,9 @@ def create_district_heating_profile_python_like(scenario="eGon2035"): WHERE scenario = '{scenario}' AND area_id = '{area}' ) b ON a.zensus_population_id = b.zensus_population_id , - + UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp - + """ ) @@ -384,7 +384,7 @@ def create_district_heating_profile_python_like(scenario="eGon2035"): assert ( abs(diff) < 0.04 - ), f"""Deviation of residential heat demand time + ), f"""Deviation of residential heat demand time series for district heating grid {str(area)} is {diff}""" if abs(diff) > 0.03: @@ -895,6 +895,8 @@ def individual_heating_per_mv_grid_tables(method="python"): bind=engine, checkfirst=True ) +def individual_heating_per_mv_grid_2037_2025(method="python"): + create_individual_heating_profile_python_like("nep2037_2025") def individual_heating_per_mv_grid_2035(method="python"): create_individual_heating_profile_python_like("eGon2035") @@ -914,6 +916,7 @@ def individual_heating_per_mv_grid(method="python"): bind=engine, checkfirst=True ) + create_individual_heating_profile_python_like("nep2037_2025") create_individual_heating_profile_python_like("eGon2035") create_individual_heating_profile_python_like("eGon100RE") @@ -939,7 +942,7 @@ def individual_heating_per_mv_grid(method="python"): ) for index, row in ids.iterrows(): - for scenario in ["eGon2035", "eGon100RE"]: + for scenario in ["nep2037_2025", "eGon2035", "eGon100RE"]: series = create_individual_heat_per_mv_grid( scenario, row.bus_id ) diff --git a/src/egon/data/datasets/heat_supply/__init__.py b/src/egon/data/datasets/heat_supply/__init__.py index f42ab473c..fc175a7d5 100644 --- a/src/egon/data/datasets/heat_supply/__init__.py +++ b/src/egon/data/datasets/heat_supply/__init__.py @@ -174,7 +174,7 @@ def individual_heating(): WHERE scenario = '{scenario}' """ ) - if scenario == "eGon2035": + if scenario == "eGon2035" or scenario == "nep2037_2025": distribution_level = "federal_states" else: distribution_level = "national" diff --git a/src/egon/data/datasets/heat_supply/district_heating.py b/src/egon/data/datasets/heat_supply/district_heating.py index 15d0f3dfe..ab691078c 100644 --- a/src/egon/data/datasets/heat_supply/district_heating.py +++ b/src/egon/data/datasets/heat_supply/district_heating.py @@ -373,7 +373,7 @@ def cascade_heat_supply(scenario, plotting=True): # Plot results per district heating area if plotting: - plot_heat_supply(resulting_capacities) + plot_heat_supply(resulting_capacities, scenario) return gpd.GeoDataFrame( resulting_capacities, @@ -471,10 +471,10 @@ def backup_resistive_heaters(scenario): return df -def plot_heat_supply(resulting_capacities): +def plot_heat_supply(resulting_capacities, scenario): from matplotlib import pyplot as plt - district_heating_areas = select_district_heating_areas("eGon2035") + district_heating_areas = select_district_heating_areas(scenario) for c in ["CHP", "solar_thermal_collector", "geo_thermal", "heat_pump"]: district_heating_areas[c] = ( diff --git a/src/egon/data/datasets/heat_supply/individual_heating.py b/src/egon/data/datasets/heat_supply/individual_heating.py index 8c889fa0d..5e8a5dabc 100644 --- a/src/egon/data/datasets/heat_supply/individual_heating.py +++ b/src/egon/data/datasets/heat_supply/individual_heating.py @@ -36,7 +36,8 @@ :func:`delete_heat_peak_loads_100RE`. The desaggregation of heat pump capcacities to individual buildings takes place in two -separate datasets: 'HeatPumps2035' for eGon2035 scenario and 'HeatPumps2050' for +separate datasets: 'HeatPumps2035' for eGon2035, 'HeatPumps2037' for nep2037_2025 +scenario and 'HeatPumps2050' for eGon100RE. It is done separately because for one reason in case of the eGon100RE scenario the minimum required heat pump capacity per building can directly be determined using the diff --git a/src/egon/data/datasets/hydrogen_etrago/__init__.py b/src/egon/data/datasets/hydrogen_etrago/__init__.py index e5144f2d0..df72d1b16 100755 --- a/src/egon/data/datasets/hydrogen_etrago/__init__.py +++ b/src/egon/data/datasets/hydrogen_etrago/__init__.py @@ -35,7 +35,7 @@ class HydrogenBusEtrago(Dataset): Insert the H2 buses into the database for Germany Insert the H2 buses in Germany into the database for the scenarios - eGon2035 and eGon100RE by executing successively the functions + nep2037_2025, eGon2035 and eGon100RE by executing successively the functions :py:func:`calculate_and_map_saltcavern_storage_potential `, :py:func:`insert_hydrogen_buses ` and :py:func:`insert_hydrogen_buses_eGon100RE `. @@ -114,7 +114,7 @@ class HydrogenPowerLinkEtrago(Dataset): Insert the electrolysis and the fuel cells into the database Insert the the electrolysis and the fuel cell links in Germany into - the database for the scenarios eGon2035 and eGon100RE by executing + the database for the scenarios nep2037_2025, eGon2035 and eGon100RE by executing successively the functions :py:func:`insert_power_to_h2_to_power ` and :py:func:`insert_power_to_h2_to_power_eGon100RE `. @@ -150,9 +150,9 @@ class HydrogenMethaneLinkEtrago(Dataset): """ Insert the methanisation, feed in and SMR into the database - Insert the the methanisation, feed in (only in eGon2035) and Steam + Insert the the methanisation, feed in (only in eGon2035,nep2037_2025) and Steam Methane Reaction (SMR) links in Germany into the database for the - scenarios eGon2035 and eGon100RE by executing successively the + scenarios eGon2035, nep2037_2025 and eGon100RE by executing successively the functions :py:func:`insert_h2_to_ch4_to_h2 ` and :py:func:`insert_h2_to_ch4_eGon100RE `. @@ -196,6 +196,8 @@ class HydrogenGridEtrago(Dataset): * :py:class:`GasNodesAndPipes ` * :py:class:`SubstationVoronoi ` * :py:class:`GasAreaseGon2035 ` + * :py:class:`GasAreaseGon2037_2025 + ` * :py:class:`PypsaEurSec ` * :py:class:`HydrogenBusEtrago ` diff --git a/src/egon/data/datasets/hydrogen_etrago/bus.py b/src/egon/data/datasets/hydrogen_etrago/bus.py index 332bde221..6797c8c14 100755 --- a/src/egon/data/datasets/hydrogen_etrago/bus.py +++ b/src/egon/data/datasets/hydrogen_etrago/bus.py @@ -51,7 +51,10 @@ def insert_hydrogen_buses(): """ s = config.settings()["egon-data"]["--scenarios"] scn = [] - if "eGon2035" in s: + + if "nep2037_2025" in s: + scn.append("nep2037_2025") + elif "eGon2035" in s: scn.append("eGon2035") if "eGon100RE" in s: scn.append("eGon100RE") @@ -330,11 +333,11 @@ def insert_H2_buses_from_CH4_grid(gdf, carrier, target, scn_name): Target schema and table information. scn_name : str Name of the scenario. - + Returns ------- None - + """ # Connect to local database engine = db.engine() diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index c955e0f38..e898287a0 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -84,7 +84,7 @@ def insert_h2_to_ch4_to_h2(): technology = [methanation, SMR] links_names = ["H2_to_CH4", "CH4_to_H2"] - if scn_name == "eGon2035": + if scn_name == "eGon2035" or scn_name == "nep2037_2025": feed_in = methanation.copy() pipeline_capacities = db.select_dataframe( f""" diff --git a/src/egon/data/datasets/hydrogen_etrago/storage.py b/src/egon/data/datasets/hydrogen_etrago/storage.py index 09c670498..8bf9d80b0 100755 --- a/src/egon/data/datasets/hydrogen_etrago/storage.py +++ b/src/egon/data/datasets/hydrogen_etrago/storage.py @@ -25,7 +25,7 @@ def insert_H2_overground_storage(): Insert H2_overground stores into the database. Insert extendable H2_overground stores (steel tanks) at each H2 - bus. + bus. Returns ------- @@ -35,14 +35,16 @@ def insert_H2_overground_storage(): # The targets of etrago_hydrogen also serve as source here ಠ_ಠ sources = config.datasets()["etrago_hydrogen"]["sources"] targets = config.datasets()["etrago_hydrogen"]["targets"] - + s = config.settings()["egon-data"]["--scenarios"] scn = [] if "eGon2035" in s: scn.append("eGon2035") + if "nep2037_2025" in s: + scn.append("nep2037_2025") if "eGon100RE" in s: scn.append("eGon100RE") - + for scn_name in scn: # Place storage at every H2 bus storages = db.select_geodataframe( @@ -53,24 +55,24 @@ def insert_H2_overground_storage(): AND scn_name = '{scn_name}' AND country = 'DE'""", index_col="bus_id", ) - + carrier = "H2_overground" # Add missing column storages["bus"] = storages.index storages["carrier"] = carrier - + # Does e_nom_extenable = True render e_nom useless? storages["e_nom"] = 0 storages["e_nom_extendable"] = True - + # read carrier information from scnario parameter data scn_params = get_sector_parameters("gas", scn_name) storages["capital_cost"] = scn_params["capital_cost"][carrier] storages["lifetime"] = scn_params["lifetime"][carrier] - + # Remove useless columns storages.drop(columns=["geom"], inplace=True) - + # Clean table db.execute_sql( f""" @@ -81,12 +83,12 @@ def insert_H2_overground_storage(): ); """ ) - + # Select next id value new_id = db.next_etrago_id("store") storages["store_id"] = range(new_id, new_id + len(storages)) storages = storages.reset_index(drop=True) - + # Insert data to db storages.to_sql( targets["hydrogen_stores"]["table"], @@ -103,7 +105,7 @@ def insert_H2_saltcavern_storage(): Insert extendable H2_underground stores (saltcavern potentials) at every H2_saltcavern bus. - + Returns ------- None @@ -118,9 +120,11 @@ def insert_H2_saltcavern_storage(): scn = [] if "eGon2035" in s: scn.append("eGon2035") + if "nep2037_2025" in s: + scn.append("nep2037_2025") if "eGon100RE" in s: scn.append("eGon100RE") - + for scn_name in scn: storage_potentials = db.select_geodataframe( f""" @@ -129,7 +133,7 @@ def insert_H2_saltcavern_storage(): {sources['saltcavern_data']['table']}""", geom_col="geometry", ) - + # Place storage at every H2 bus from the H2 AC saltcavern map H2_AC_bus_map = db.select_dataframe( f""" @@ -137,44 +141,44 @@ def insert_H2_saltcavern_storage(): FROM {sources['H2_AC_map']['schema']}. {sources['H2_AC_map']['table']}""" ) - + storage_potentials["storage_potential"] = ( storage_potentials["area_fraction"] * storage_potentials["potential"] ) - + storage_potentials[ "summed_potential_per_bus" ] = storage_potentials.groupby("bus_id")["storage_potential"].transform( "sum" ) - + storages = storage_potentials[ ["summed_potential_per_bus", "bus_id"] ].copy() storages.drop_duplicates("bus_id", keep="last", inplace=True) - + # map AC buses in potetial data to respective H2 buses storages = storages.merge( H2_AC_bus_map, left_on="bus_id", right_on="bus_AC" ).reindex(columns=["bus_H2", "summed_potential_per_bus", "scn_name"]) - + # rename columns storages.rename( columns={"bus_H2": "bus", "summed_potential_per_bus": "e_nom_max"}, inplace=True, ) - + # add missing columns carrier = "H2_underground" storages["carrier"] = carrier storages["e_nom"] = 0 storages["e_nom_extendable"] = True - + # read carrier information from scnario parameter data scn_params = get_sector_parameters("gas", scn_name) storages["capital_cost"] = scn_params["capital_cost"][carrier] storages["lifetime"] = scn_params["lifetime"][carrier] - + # Clean table db.execute_sql( f""" @@ -185,12 +189,12 @@ def insert_H2_saltcavern_storage(): ); """ ) - + # Select next id value new_id = db.next_etrago_id("store") storages["store_id"] = range(new_id, new_id + len(storages)) storages = storages.reset_index(drop=True) - + # # Insert data to db storages.to_sql( targets["hydrogen_stores"]["table"], @@ -400,11 +404,11 @@ def calculate_and_map_saltcavern_storage_potential(): def write_saltcavern_potential(): """Write saltcavern potentials into the database - + Returns ------- None - + """ potential_areas = calculate_and_map_saltcavern_storage_potential() diff --git a/src/egon/data/datasets/industrial_gas_demand.py b/src/egon/data/datasets/industrial_gas_demand.py index 509a76f8a..0acfcd91e 100755 --- a/src/egon/data/datasets/industrial_gas_demand.py +++ b/src/egon/data/datasets/industrial_gas_demand.py @@ -91,6 +91,38 @@ def __init__(self, dependencies): tasks=(insert_industrial_gas_demand_egon2035), ) +class IndustrialGasDemandeGon2037_2025(Dataset): + """Insert the hourly resolved industrial gas demands into the database for + nep2037_2025 + + Insert the industrial methane and hydrogen demands and their + associated time series for the scenario nep2037_2025 by executing the + function :py:func:`insert_industrial_gas_demand_nep2037_2025`. + + *Dependencies* + * :py:class:`GasAreasnep2037_2025 ` + * :py:class:`GasNodesAndPipes ` + * :py:class:`HydrogenBusEtrago ` + * :py:class:`IndustrialGasDemand ` + + *Resulting tables* + * :py:class:`grid.egon_etrago_load ` is extended + * :py:class:`grid.egon_etrago_load_timeseries ` is extended + + """ + + #: + name: str = "IndustrialGasDemandnep2037_2025" + #: + version: str = "0.0.3" + + def __init__(self, dependencies): + super().__init__( + name=self.name, + version=self.version, + dependencies=dependencies, + tasks=(insert_industrial_gas_demand_nep2037_2025), + ) class IndustrialGasDemandeGon100RE(Dataset): """Insert the hourly resolved industrial gas demands into the database for eGon100RE @@ -130,7 +162,7 @@ def read_industrial_demand(scn_name, carrier): This function reads the methane or hydrogen industrial demand time series previously downloaded in :py:func:`download_industrial_gas_demand` for - the scenarios eGon2035 or eGon100RE. + the scenarios eGon2035, nep2037_2025 or eGon100RE. Parameters ---------- @@ -387,11 +419,11 @@ def insert_new_entries(industrial_gas_demand, scn_name): return industrial_gas_demand -def insert_industrial_gas_demand_egon2035(): - """Insert industrial gas demands into the database for eGon2035 +def insert_industrial_gas_demand_nep2037_2025(): + """Insert industrial gas demands into the database for nep2037_2025 Insert the industrial CH4 and H2 demands and their associated time - series into the database for the eGon2035 scenario. The data + series into the database for the nep2037_2025 scenario. The data previously downloaded in :py:func:`download_industrial_gas_demand` is adjusted by executing the following steps: @@ -408,8 +440,8 @@ def insert_industrial_gas_demand_egon2035(): None """ - if "eGon2035" in config.settings()["egon-data"]["--scenarios"]: - scn_name = "eGon2035" + if "nep2037_2025" in config.settings()["egon-data"]["--scenarios"]: + scn_name = "nep2037_2025" delete_old_entries(scn_name) industrial_gas_demand = pd.concat( @@ -439,10 +471,44 @@ def insert_industrial_gas_demand_egon2035(): insert_industrial_gas_demand_time_series(industrial_gas_demand) else: print( - """eGon2035 is not part of the scenario list. This task is not + """nep2037_2025 is not part of the scenario list. This task is not executed""" ) + if "nep2037_2025" in config.settings()["egon-data"]["--scenarios"]: + scn_name = "nep2037_2025" + delete_old_entries(scn_name) + + industrial_gas_demand = pd.concat( + [ + read_and_process_demand( + scn_name=scn_name, + carrier="CH4_for_industry", + grid_carrier="CH4", + ), + read_and_process_demand( + scn_name=scn_name, + carrier="H2_for_industry", + grid_carrier="H2", + ), + ] + ) + + industrial_gas_demand = ( + industrial_gas_demand.groupby(["bus", "carrier"])["p_set"] + .apply(lambda x: [sum(y) for y in zip(*x)]) + .reset_index(drop=False) + ) + + industrial_gas_demand = insert_new_entries( + industrial_gas_demand, scn_name + ) + insert_industrial_gas_demand_time_series(industrial_gas_demand) + else: + print( + """nep2037_2025 is not part of the scenario list. This task is not + executed""" + ) def insert_industrial_gas_demand_egon100RE(): """Insert industrial gas demands into the database for eGon100RE @@ -656,7 +722,7 @@ def download_industrial_gas_demand(): "http://opendata.ffe.de:3000/opendata?id_opendata=eq.66&&year=eq." ) - for scn_name in ["eGon2035", "eGon100RE"]: + for scn_name in ["eGon2035", "nep2037_2025", "eGon100RE"]: year = str( get_sector_parameters("global", scn_name)["population_year"] ) @@ -678,7 +744,7 @@ def download_industrial_gas_demand(): logger.warning( """ Due to temporal problems in the FFE platform, data for the scenarios - eGon2035 and eGon100RE are imported lately from csv files. Data for + eGon2035, nep2037_2025 and eGon100RE are imported lately from csv files. Data for other scenarios is unfortunately unavailable. """ ) diff --git a/src/egon/data/datasets/loadarea/loadareas_add_demand_cts.sql b/src/egon/data/datasets/loadarea/loadareas_add_demand_cts.sql index 238faacf3..e8036a865 100644 --- a/src/egon/data/datasets/loadarea/loadareas_add_demand_cts.sql +++ b/src/egon/data/datasets/loadarea/loadareas_add_demand_cts.sql @@ -67,6 +67,66 @@ UPDATE demand.egon_loadarea AS t1 ) AS t2 WHERE t1.id = t2.id; +---------------------------- +-- Scenario: nep2037_2025 -- +---------------------------- + +-- Add CTS consumption +UPDATE demand.egon_loadarea AS t1 + SET sector_consumption_cts_2037_2025 = t2.demand + FROM ( + SELECT a.id AS id, + SUM(b.demand)::float AS demand + FROM demand.egon_loadarea AS a, + ( + SELECT + dem.demand AS demand, + census.geom_point AS geom_point + FROM + demand.egon_demandregio_zensus_electricity as dem, + society.destatis_zensus_population_per_ha AS census + WHERE + dem.scenario = 'nep2037_2025' AND + dem.sector = 'service' AND + dem.zensus_population_id = census.id + ) AS b + WHERE a.geom && b.geom_point AND + ST_CONTAINS(a.geom, b.geom_point) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + +-- Add CTS peak load +UPDATE demand.egon_loadarea AS t1 + SET sector_peakload_cts_2037_2025 = t2.peak_load_in_mw + FROM ( + SELECT a.id AS id, + SUM(b.peak_load_in_w)/1000000::float AS peak_load_in_mw + FROM demand.egon_loadarea AS a, + ( + SELECT + peak.peak_load_in_w AS peak_load_in_w, + bld.geom_point AS geom_point + FROM + demand.egon_building_electricity_peak_loads as peak, + ( + SELECT "id"::integer, geom_point + FROM openstreetmap.osm_buildings_synthetic + UNION + SELECT "id"::integer, geom_point + FROM openstreetmap.osm_buildings_filtered + ) AS bld + WHERE + peak.scenario = 'nep2037_2025' AND + peak.sector = 'cts' AND + peak.building_id = bld.id + ) AS b + WHERE a.geom && b.geom_point AND + ST_CONTAINS(a.geom, b.geom_point) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + ------------------------- -- Scenario: eGon100RE -- ------------------------- diff --git a/src/egon/data/datasets/loadarea/loadareas_add_demand_hh.sql b/src/egon/data/datasets/loadarea/loadareas_add_demand_hh.sql index 828c31d86..8dfa60920 100644 --- a/src/egon/data/datasets/loadarea/loadareas_add_demand_hh.sql +++ b/src/egon/data/datasets/loadarea/loadareas_add_demand_hh.sql @@ -67,6 +67,66 @@ UPDATE demand.egon_loadarea AS t1 ) AS t2 WHERE t1.id = t2.id; +---------------------------- +-- Scenario: nep2037_2025 -- +---------------------------- + +-- Add residential consumption +UPDATE demand.egon_loadarea AS t1 + SET sector_consumption_residential_2037_2025 = t2.demand + FROM ( + SELECT a.id AS id, + SUM(b.demand)::float AS demand + FROM demand.egon_loadarea AS a, + ( + SELECT + dem.demand AS demand, + census.geom_point AS geom_point + FROM + demand.egon_demandregio_zensus_electricity as dem, + society.destatis_zensus_population_per_ha AS census + WHERE + dem.scenario = 'nep2037_2025' AND + dem.sector = 'residential' AND + dem.zensus_population_id = census.id + ) AS b + WHERE a.geom && b.geom_point AND + ST_CONTAINS(a.geom, b.geom_point) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + +-- Add residential peak load +UPDATE demand.egon_loadarea AS t1 + SET sector_peakload_residential_2037_2025 = t2.peak_load_in_mw + FROM ( + SELECT a.id AS id, + SUM(b.peak_load_in_w)/1000000::float AS peak_load_in_mw + FROM demand.egon_loadarea AS a, + ( + SELECT + peak.peak_load_in_w AS peak_load_in_w, + bld.geom_point AS geom_point + FROM + demand.egon_building_electricity_peak_loads as peak, + ( + SELECT "id"::integer, geom_point + FROM openstreetmap.osm_buildings_synthetic + UNION + SELECT "id"::integer, geom_point + FROM openstreetmap.osm_buildings_filtered + ) AS bld + WHERE + peak.scenario = 'nep2037_2025' AND + peak.sector = 'residential' AND + peak.building_id = bld.id + ) AS b + WHERE a.geom && b.geom_point AND + ST_CONTAINS(a.geom, b.geom_point) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + ------------------------- -- Scenario: eGon100RE -- ------------------------- diff --git a/src/egon/data/datasets/loadarea/loadareas_add_demand_ind.sql b/src/egon/data/datasets/loadarea/loadareas_add_demand_ind.sql index 219589f96..a894c9792 100644 --- a/src/egon/data/datasets/loadarea/loadareas_add_demand_ind.sql +++ b/src/egon/data/datasets/loadarea/loadareas_add_demand_ind.sql @@ -137,6 +137,75 @@ UPDATE demand.egon_loadarea AS t1 ) AS t2 WHERE t1.id = t2.id; +---------------------------- +-- Scenario: nep2037_2025 -- +---------------------------- + +-- Add industrial consumption and peak load +-- 1) Industry from OSM landuse areas +UPDATE demand.egon_loadarea AS t1 + SET + sector_peakload_industrial_2037_2025 = t2.peak_load, + sector_consumption_industrial_2037_2025 = t2.demand + FROM ( + SELECT a.id AS id, + SUM(b.demand)::float AS demand, + SUM(b.peak_load)::float AS peak_load + FROM demand.egon_loadarea AS a, + ( + SELECT + sum(ind_osm.demand) as demand, + sum(ind_osm.peak_load) as peak_load, + ST_PointOnSurface(landuse.geom) AS geom_surfacepoint + FROM + openstreetmap.osm_landuse as landuse, + demand.egon_osm_ind_load_curves_individual as ind_osm + WHERE + ind_osm.scn_name = 'nep2037_2025' AND + ind_osm.voltage_level in (4,5,6,7) AND + ind_osm.osm_id = landuse.id + GROUP BY landuse.id + ) AS b + WHERE a.geom && b.geom_surfacepoint AND + ST_CONTAINS(a.geom, b.geom_surfacepoint) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + +-- 2) Industry from industrial sites +UPDATE demand.egon_loadarea AS t1 + SET + sector_peakload_industrial_2037_2025 = sector_peakload_industrial_2037_2025 + t2 + .peak_load, + sector_consumption_industrial_2037_2025 = + sector_consumption_industrial_2037_2025 + + + t2.demand + FROM ( + SELECT a.id AS id, + SUM(b.demand)::float AS demand, + SUM(b.peak_load)::float AS peak_load + FROM demand.egon_loadarea AS a, + ( + SELECT + ind_sites.id, + ind_loads.demand, + ind_loads.peak_load, + ST_TRANSFORM(ind_sites.geom, 3035) as geom + FROM + demand.egon_industrial_sites as ind_sites, + demand.egon_sites_ind_load_curves_individual as ind_loads + WHERE + ind_loads.scn_name = 'nep2037_2025' AND + ind_loads.voltage_level in (4,5,6,7) AND + ind_loads.site_id = ind_sites.id + ) AS b + WHERE a.geom && b.geom AND + ST_CONTAINS(a.geom, b.geom) + GROUP BY a.id + ) AS t2 + WHERE t1.id = t2.id; + ------------------------- -- Scenario: eGon100RE -- ------------------------- diff --git a/src/egon/data/datasets/low_flex_scenario/__init__.py b/src/egon/data/datasets/low_flex_scenario/__init__.py index 25be3ad54..5e81d0430 100644 --- a/src/egon/data/datasets/low_flex_scenario/__init__.py +++ b/src/egon/data/datasets/low_flex_scenario/__init__.py @@ -12,12 +12,9 @@ class LowFlexScenario(Dataset): - def __init__(self, dependencies): - super().__init__( - name="low_flex_scenario", - version="0.0.1", - dependencies=dependencies, - tasks=( + def __init__(self, dependencies, name = "low_flex_scenario", version = "0.0.1"): + if tasks is None + tasks = ( { PostgresOperator( task_id="low_flex_eGon2035", @@ -28,5 +25,10 @@ def __init__(self, dependencies): autocommit=True, ), }, - ), + ) + super().__init__( + name=name, + version=version, + dependencies=dependencies, + tasks=tasks, ) diff --git a/src/egon/data/datasets/low_flex_scenario/low_flex_nep2037_2025.sql b/src/egon/data/datasets/low_flex_scenario/low_flex_nep2037_2025.sql new file mode 100644 index 000000000..70d174170 --- /dev/null +++ b/src/egon/data/datasets/low_flex_scenario/low_flex_nep2037_2025.sql @@ -0,0 +1,406 @@ +/* +Low flex scenario +Create a low flex scenario based on scenario 'nep2037_2025' by copying this scenario and +neglecting all flexibilities provided by non-electrical sectors. +__copyright__ = "Hochschule Flensburg" +__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" +__url__ = "https://github.com/openego/data_processing/blob/master/LICENSE" +__author__ = "Alonsoju95, IlkaCu" +*/ + +-- Copy relevant buses and bus time series +DELETE FROM grid.egon_etrago_bus WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_bus_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_bus + SELECT + 'nep2037_2025_lowflex' as scn_name, + bus_id, + v_nom, + type, + carrier, + v_mag_pu_set, + v_mag_pu_min, + v_mag_pu_max, + x, + y, + geom, + country + FROM grid.egon_etrago_bus + WHERE scn_name='nep2037_2025' + AND carrier NOT IN + ('dsm', + 'rural_heat_store', + 'central_heat_store', + 'H2_saltcavern', + 'Li_ion'); + +INSERT INTO grid.egon_etrago_bus_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + bus_id, + v_mag_pu_set + FROM grid.egon_etrago_bus_timeseries + WHERE scn_name='nep2037_2025' + AND bus_id IN + (SELECT bus_id + FROM grid.egon_etrago_bus + WHERE scn_name = 'nep2037_2025_lowflex'); + +-- Copy relevant generators including time series +DELETE FROM grid.egon_etrago_generator WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_generator_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_generator + SELECT + 'nep2037_2025_lowflex' as scn_name, + generator_id, + bus, + control, + type, + carrier, + p_nom, + p_nom_extendable, + p_nom_min, + p_nom_max, + p_min_pu, + p_max_pu, + p_set, + q_set, + sign, + marginal_cost, + build_year, + lifetime, + capital_cost, + efficiency, + committable, + start_up_cost, + shut_down_cost, + min_up_time, + min_down_time, + up_time_before, + down_time_before, + ramp_limit_up, + ramp_limit_down, + ramp_limit_start_up, + ramp_limit_shut_down, + e_nom_max + FROM grid.egon_etrago_generator + WHERE scn_name='nep2037_2025'; + +INSERT INTO grid.egon_etrago_generator_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + generator_id, + temp_id, + p_set, + q_set, + p_min_pu, + p_max_pu + FROM grid.egon_etrago_generator_timeseries + WHERE scn_name='nep2037_2025'; + +-- Copy lines including time series without copying s_max_pu +DELETE FROM grid.egon_etrago_line WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_line_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_line + SELECT + 'nep2037_2025_lowflex' as scn_name, + line_id, + bus0, + bus1, + type, + carrier, + x, + r, + g, + b, + s_nom, + s_nom_extendable, + s_nom_min, + s_nom_max, + s_max_pu, + build_year, + lifetime, + capital_cost, + length, + cables, + terrain_factor, + num_parallel, + v_ang_min, + v_ang_max, + v_nom, + geom, + topo + FROM grid.egon_etrago_line + WHERE scn_name='nep2037_2025'; + +INSERT INTO grid.egon_etrago_line_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + line_id, + temp_id, + NULL as s_max_pu + FROM grid.egon_etrago_line_timeseries + WHERE scn_name='nep2037_2025'; + +-- Copy relevant link components including time series +DELETE FROM grid.egon_etrago_link WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_link_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_link + SELECT + 'nep2037_2025_lowflex' as scn_name, + link_id, + bus0, + bus1, + type, + carrier, + efficiency, + build_year, + lifetime, + p_nom, + p_nom_extendable, + p_nom_min, + p_nom_max, + p_min_pu, + p_max_pu, + p_set, + capital_cost, + marginal_cost, + length, + terrain_factor, + geom, + topo + FROM grid.egon_etrago_link + WHERE scn_name='nep2037_2025' + AND carrier NOT IN + ( + 'dsm', + 'rural_heat_store_charger', + 'rural_heat_store_discharger', + 'central_heat_store_charger', + 'central_heat_store_discharger', + 'BEV_charger' + ) + AND link_id NOT IN ( + SELECT link_id FROM grid.egon_etrago_link + WHERE scn_name = 'nep2037_2025' + AND carrier IN ('H2_to_power', 'power_to_H2') + AND (bus0 IN (SELECT bus_id + FROM grid.egon_etrago_bus + WHERE scn_name='nep2037_2025' + AND carrier= 'H2_saltcavern') + OR bus1 IN (SELECT bus_id + FROM grid.egon_etrago_bus + WHERE scn_name='nep2037_2025' + AND carrier= 'H2_saltcavern'))); + +INSERT INTO grid.egon_etrago_link_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + link_id, + temp_id, + p_set, + p_min_pu, + p_max_pu, + efficiency, + marginal_cost + FROM grid.egon_etrago_link_timeseries + WHERE scn_name='nep2037_2025' + AND link_id IN + ( + SELECT link_id + FROM grid.egon_etrago_link + WHERE scn_name='nep2037_2025_lowflex' + ); + +-- Copy relevant load components including time series except for emobility (MIT) which +-- have been created in egon.data.datasets.emobility.motorized_individual_travel.model_timeseries +DELETE FROM grid.egon_etrago_load_timeseries WHERE scn_name='nep2037_2025_lowflex' +AND load_id NOT IN ( + SELECT load_id FROM grid.egon_etrago_load WHERE scn_name='nep2037_2025_lowflex' AND carrier = 'land_transport_EV' +); +DELETE FROM grid.egon_etrago_load WHERE scn_name='nep2037_2025_lowflex' AND carrier != 'land_transport_EV'; + +INSERT INTO grid.egon_etrago_load + SELECT + 'nep2037_2025_lowflex' as scn_name, + load_id, + bus, + type, + carrier, + p_set, + q_set, + sign + FROM grid.egon_etrago_load + WHERE scn_name='nep2037_2025' + AND carrier != 'land_transport_EV'; + +INSERT INTO grid.egon_etrago_load_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + load_id, + temp_id, + p_set, + q_set + FROM grid.egon_etrago_load_timeseries + WHERE scn_name='nep2037_2025' + AND load_id IN ( + SELECT load_id + FROM grid.egon_etrago_load + WHERE scn_name = 'nep2037_2025_lowflex' + AND carrier != 'land_transport_EV') + ; + +-- Copy relevant storage components including time series +DELETE FROM grid.egon_etrago_storage WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_storage_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_storage + SELECT + 'nep2037_2025_lowflex' as scn_name, + storage_id, + bus, + control, + type, + carrier, + p_nom, + p_nom_extendable, + p_nom_min, + p_nom_max, + p_min_pu, + p_max_pu, + p_set, + q_set, + sign, + marginal_cost, + capital_cost, + build_year, + lifetime, + state_of_charge_initial, + cyclic_state_of_charge, + state_of_charge_set, + max_hours, + efficiency_store, + efficiency_dispatch, + standing_loss, + inflow + FROM grid.egon_etrago_storage + WHERE scn_name='nep2037_2025'; + +INSERT INTO grid.egon_etrago_storage_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + storage_id, + temp_id, + p_set, + q_set, + p_min_pu, + p_max_pu, + state_of_charge_set, + inflow, + marginal_cost + FROM grid.egon_etrago_storage_timeseries + WHERE scn_name='nep2037_2025'; + + -- Copy relevant store components including time series +DELETE FROM grid.egon_etrago_store WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_store_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_store + SELECT + 'nep2037_2025_lowflex' as scn_name, + store_id, + bus, + type, + carrier, + e_nom, + e_nom_extendable, + e_nom_min, + e_nom_max, + e_min_pu, + e_max_pu, + p_set, + q_set, + e_initial, + e_cyclic, + sign, + marginal_cost, + capital_cost, + standing_loss, + build_year, + lifetime + FROM grid.egon_etrago_store + WHERE scn_name='nep2037_2025' + AND carrier NOT IN + ('dsm', + 'rural_heat_store', + 'central_heat_store', + 'H2_underground', + 'battery_storage'); + +INSERT INTO grid.egon_etrago_store_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + store_id, + temp_id, + p_set, + q_set, + e_min_pu, + e_max_pu, + marginal_cost + FROM grid.egon_etrago_store_timeseries + WHERE scn_name='nep2037_2025' + AND store_id IN + (SELECT store_id + FROM grid.egon_etrago_store + WHERE scn_name='nep2037_2025_lowflex' + ); + + +-- Copy relevant transformers including time series +DELETE FROM grid.egon_etrago_transformer WHERE scn_name='nep2037_2025_lowflex'; +DELETE FROM grid.egon_etrago_transformer_timeseries WHERE scn_name='nep2037_2025_lowflex'; + +INSERT INTO grid.egon_etrago_transformer + SELECT + 'nep2037_2025_lowflex' as scn_name, + trafo_id, + bus0, + bus1, + type, + model, + x, + r, + g, + b, + s_nom, + s_nom_extendable, + s_nom_min, + s_nom_max, + s_max_pu, + tap_ratio, + tap_side, + tap_position, + phase_shift, + build_year, + lifetime, + v_ang_min, + v_ang_max, + capital_cost, + num_parallel, + geom, + topo + FROM grid.egon_etrago_transformer + WHERE scn_name='nep2037_2025'; + +INSERT INTO grid.egon_etrago_transformer_timeseries + SELECT + 'nep2037_2025_lowflex' as scn_name, + trafo_id, + temp_id, + s_max_pu + FROM grid.egon_etrago_transformer_timeseries + WHERE scn_name='nep2037_2025'; diff --git a/src/egon/data/datasets/osmtgmod/__init__.py b/src/egon/data/datasets/osmtgmod/__init__.py index c537dc88b..9115a3d2e 100644 --- a/src/egon/data/datasets/osmtgmod/__init__.py +++ b/src/egon/data/datasets/osmtgmod/__init__.py @@ -552,7 +552,8 @@ def to_pypsa(): """ ) - # for scenario_name in ["'eGon2035'", "'eGon100RE'", "'status2019'"]: + # for scenario_name in ["nep2037_2025", "'eGon2035'", "'eGon100RE'", + # "'status2019'"]: scenario_list = egon.data.config.settings()["egon-data"]["--scenarios"] scenario_list = [ f"'{scn}'" if not scn[1] == "'" else scn for scn in scenario_list diff --git a/src/egon/data/datasets/power_etrago/__init__.py b/src/egon/data/datasets/power_etrago/__init__.py index 1d7d53e78..4d4acb161 100755 --- a/src/egon/data/datasets/power_etrago/__init__.py +++ b/src/egon/data/datasets/power_etrago/__init__.py @@ -12,8 +12,10 @@ class OpenCycleGasTurbineEtrago(Dataset): *Dependencies* * :py:class:`GasAreaseGon2035 ` + * :py:class:`GasAreasnep2037_2025 + ` * :py:class:`PowerPlants ` - + *Resulting tables* * :py:class:`grid.egon_etrago_link ` is extended diff --git a/src/egon/data/datasets/power_plants/__init__.py b/src/egon/data/datasets/power_plants/__init__.py index ffb16de25..824df4c4c 100755 --- a/src/egon/data/datasets/power_plants/__init__.py +++ b/src/egon/data/datasets/power_plants/__init__.py @@ -259,8 +259,9 @@ def insert_biomass_plants(scenario): ) ] - # Scaling will be done per federal state in case of eGon2035 scenario. - if scenario == "eGon2035": + # Scaling will be done per federal state in case of eGon2035 and + # nep2037_2025 scenario. + if scenario == "eGon2035" or scenario == "nep2037_2025": level = "federal_state" else: level = "country" @@ -341,7 +342,7 @@ def insert_hydro_plants(scenario): ) continue - elif scenario == "eGon2035": + elif scenario == "eGon2035" or scenario == "nep2037_2025": target = select_target(carrier, scenario) # import data for MaStR @@ -364,8 +365,9 @@ def insert_hydro_plants(scenario): ) ] - # Scaling will be done per federal state in case of eGon2035 scenario. - if scenario == "eGon2035": + # Scaling will be done per federal state in case of eGon2035 and nep2037_2025 + # scenario. + if scenario == "eGon2035" or scenario == "nep2037_2025": level = "federal_state" else: level = "country" @@ -595,12 +597,15 @@ def insert_hydro_biomass(): f""" DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']} WHERE carrier IN ('biomass', 'reservoir', 'run_of_river') - AND scenario IN ('eGon2035', 'eGon100RE') + AND scenario IN ('nep2037_2025', 'eGon2035', 'eGon100RE') """ ) s = egon.data.config.settings()["egon-data"]["--scenarios"] scenarios = [] + if "nep2037_2025" in s: + scenarios.append("nep2037_2025") + insert_biomass_plants("nep2037_2025") if "eGon2035" in s: scenarios.append("eGon2035") insert_biomass_plants("eGon2035") @@ -775,11 +780,202 @@ def allocate_conventional_non_chp_power_plants(): session.add(entry) session.commit() +def allocate_conventional_non_chp_power_plants(): + # This function is only designed to work for the eGon2035 and nep2037_2025 scenario + if ( + "eGon2035" + not in egon.data.config.settings()["egon-data"]["--scenarios"] and + "nep2037_2025" + not in egon.data.config.settings()["egon-data"]["--scenarios"] + ): + return + + potential_scenarios = ['eGon2035', 'nep2037_2025'] + + carrier = ["oil", "gas"] + + cfg = egon.data.config.datasets()["power_plants"] + + scenarios = potential_scenarios[potential_scenarios is in + egon.data.config.settings()[ + "egon-data"][ + "--scenarios"]] + + # Delete existing plants in the target table + for i in scenarios + + if i == 'eGon2035' + year = '2035' + elif i == 'nep2035_2025' + year = '2037' + + db.execute_sql( + f""" + DELETE FROM {cfg ['target']['schema']}.{cfg ['target']['table']} + WHERE carrier IN ('gas', 'oil') + AND scenario='{i}'; + """ + ) + + for carrier in carrier: + nep = select_nep_power_plants(carrier, i) + + if nep.empty: + print(f"DataFrame from NEP for carrier {carrier} is empty for " + f"scenario {i}!") + + else: + mastr = select_no_chp_combustion_mastr(carrier) + + # Assign voltage level to MaStR + mastr["voltage_level"] = assign_voltage_level( + mastr.rename({"el_capacity": "Nettonennleistung"}, axis=1), + cfg, + WORKING_DIR_MASTR_OLD, + ) + + # Initalize DataFrame for matching power plants + matched = gpd.GeoDataFrame( + columns=[ + "carrier", + "el_capacity", + "scenario", + "geometry", + "MaStRNummer", + "source", + "voltage_level", + ] + ) + + # Match combustion plants of a certain carrier from NEP list + # using PLZ and capacity + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + buffer_capacity=0.1, + consider_carrier=False, + i + ) + + # Match plants from NEP list using city and capacity + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + buffer_capacity=0.1, + consider_carrier=False, + consider_location="city", + i + ) + + # Match plants from NEP list using plz, + # neglecting the capacity + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + consider_location="plz", + consider_carrier=False, + consider_capacity=False, + i + ) + + # Match plants from NEP list using city, + # neglecting the capacity + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + consider_location="city", + consider_carrier=False, + consider_capacity=False, + i + ) + + # Match remaining plants from NEP using the federal state + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + buffer_capacity=0.1, + consider_location="federal_state", + consider_carrier=False, + i + ) + + # Match remaining plants from NEP using the federal state + matched, mastr, nep = match_nep_no_chp( + nep, + mastr, + matched, + buffer_capacity=0.7, + consider_location="federal_state", + consider_carrier=False, + i + ) + + print(f"{matched.el_capacity.sum()} MW of {carrier} matched") + print(f"{getattr(nep, f"c{year}_capacity").sum()} MW of {carrier} not " + f"matched") + + + matched.crs = "EPSG:4326" + + # Assign bus_id + # Load grid district polygons + mv_grid_districts = db.select_geodataframe( + f""" + SELECT * FROM {cfg['sources']['egon_mv_grid_district']} + """, + epsg=4326, + ) + + ehv_grid_districts = db.select_geodataframe( + f""" + SELECT * FROM {cfg['sources']['ehv_voronoi']} + """, + epsg=4326, + ) + + # Perform spatial joins for plants in ehv and hv level seperately + power_plants_hv = gpd.sjoin( + matched[matched.voltage_level >= 3], + mv_grid_districts[["bus_id", "geom"]], + how="left", + ).drop(columns=["index_right"]) + power_plants_ehv = gpd.sjoin( + matched[matched.voltage_level < 3], + ehv_grid_districts[["bus_id", "geom"]], + how="left", + ).drop(columns=["index_right"]) + + # Combine both dataframes + power_plants = pd.concat([power_plants_hv, power_plants_ehv]) + + # Insert into target table + session = sessionmaker(bind=db.engine())() + for i, row in power_plants.iterrows(): + entry = EgonPowerPlants( + sources={"el_capacity": row.source}, + source_id={"MastrNummer": row.MaStRNummer}, + carrier=row.carrier, + el_capacity=row.el_capacity, + voltage_level=row.voltage_level, + bus_id=row.bus_id, + scenario=row.scenario, + geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})", + ) + session.add(entry) + session.commit() def allocate_other_power_plants(): - # This function is only designed to work for the eGon2035 scenario + # This function is only designed to work for the eGon2035 and the nep2037_2025 + # scenario if ( "eGon2035" + not in egon.data.config.settings()["egon-data"]["--scenarios"] and + "nep2037_2025" not in egon.data.config.settings()["egon-data"]["--scenarios"] ): return @@ -795,154 +991,162 @@ def allocate_other_power_plants(): """ ) - # Define scenario, carrier 'others' is only present in 'eGon2035' - scenario = "eGon2035" + potential_scenarios = ['eGon2035', 'nep2037_2025'] - # Select target values for carrier 'others' - target = db.select_dataframe( - f""" - SELECT sum(capacity) as capacity, carrier, scenario_name, nuts - FROM {cfg['sources']['capacities']} - WHERE scenario_name = '{scenario}' - AND carrier = 'others' - GROUP BY carrier, nuts, scenario_name; - """ - ) + cfg = egon.data.config.datasets()["power_plants"] - # Assign name of federal state - - map_states = { - "DE1": "BadenWuerttemberg", - "DEA": "NordrheinWestfalen", - "DE7": "Hessen", - "DE4": "Brandenburg", - "DE5": "Bremen", - "DEB": "RheinlandPfalz", - "DEE": "SachsenAnhalt", - "DEF": "SchleswigHolstein", - "DE8": "MecklenburgVorpommern", - "DEG": "Thueringen", - "DE9": "Niedersachsen", - "DED": "Sachsen", - "DE6": "Hamburg", - "DEC": "Saarland", - "DE3": "Berlin", - "DE2": "Bayern", - } + scenarios = potential_scenarios[potential_scenarios is in + egon.data.config.settings()[ + "egon-data"][ + "--scenarios"]] - target = ( - target.replace({"nuts": map_states}) - .rename(columns={"nuts": "Bundesland"}) - .set_index("Bundesland") - ) - target = target.capacity + for i in scenarios: - # Select 'non chp' power plants from mastr table - mastr_combustion = select_no_chp_combustion_mastr("others") + # Select target values for carrier 'others' + target = db.select_dataframe( + f""" + SELECT sum(capacity) as capacity, carrier, scenario_name, nuts + FROM {cfg['sources']['capacities']} + WHERE scenario_name = '{i}' + AND carrier = 'others' + GROUP BY carrier, nuts, scenario_name; + """ + ) - # Rename columns - mastr_combustion = mastr_combustion.rename( - columns={ - "carrier": "Energietraeger", - "plz": "Postleitzahl", - "city": "Ort", - "federal_state": "Bundesland", - "el_capacity": "Nettonennleistung", + # Assign name of federal state + + map_states = { + "DE1": "BadenWuerttemberg", + "DEA": "NordrheinWestfalen", + "DE7": "Hessen", + "DE4": "Brandenburg", + "DE5": "Bremen", + "DEB": "RheinlandPfalz", + "DEE": "SachsenAnhalt", + "DEF": "SchleswigHolstein", + "DE8": "MecklenburgVorpommern", + "DEG": "Thueringen", + "DE9": "Niedersachsen", + "DED": "Sachsen", + "DE6": "Hamburg", + "DEC": "Saarland", + "DE3": "Berlin", + "DE2": "Bayern", } - ) - - # Select power plants representing carrier 'others' from MaStR files - mastr_sludge = pd.read_csv( - WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"] - ).query( - """EinheitBetriebsstatus=='InBetrieb'and Energietraeger=='Klärschlamm'""" # noqa: E501 - ) - mastr_geothermal = pd.read_csv( - WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"] - ).query( - "EinheitBetriebsstatus=='InBetrieb' and Energietraeger=='Geothermie' " - "and Technologie == 'ORCOrganicRankineCycleAnlage'" - ) - mastr_sg = pd.concat([mastr_sludge, mastr_geothermal]) + target = ( + target.replace({"nuts": map_states}) + .rename(columns={"nuts": "Bundesland"}) + .set_index("Bundesland") + ) + target = target.capacity + + # Select 'non chp' power plants from mastr table + mastr_combustion = select_no_chp_combustion_mastr("others") + + # Rename columns + mastr_combustion = mastr_combustion.rename( + columns={ + "carrier": "Energietraeger", + "plz": "Postleitzahl", + "city": "Ort", + "federal_state": "Bundesland", + "el_capacity": "Nettonennleistung", + } + ) - # Insert geometry column - mastr_sg = mastr_sg[~(mastr_sg["Laengengrad"].isnull())] - mastr_sg = gpd.GeoDataFrame( - mastr_sg, - geometry=gpd.points_from_xy( - mastr_sg["Laengengrad"], mastr_sg["Breitengrad"], crs=4326 - ), - ) + # Select power plants representing carrier 'others' from MaStR files + mastr_sludge = pd.read_csv( + WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"] + ).query( + """EinheitBetriebsstatus=='InBetrieb'and Energietraeger=='Klärschlamm'""" # noqa: E501 + ) + mastr_geothermal = pd.read_csv( + WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"] + ).query( + "EinheitBetriebsstatus=='InBetrieb' and Energietraeger=='Geothermie' " + "and Technologie == 'ORCOrganicRankineCycleAnlage'" + ) - # Exclude columns which are not essential - mastr_sg = mastr_sg.filter( - [ - "EinheitMastrNummer", - "Nettonennleistung", - "geometry", - "Energietraeger", - "Postleitzahl", - "Ort", - "Bundesland", - ], - axis=1, - ) - # Rename carrier - mastr_sg.Energietraeger = "others" + mastr_sg = pd.concat([mastr_sludge, mastr_geothermal]) - # Change data type - mastr_sg["Postleitzahl"] = mastr_sg["Postleitzahl"].astype(int) + # Insert geometry column + mastr_sg = mastr_sg[~(mastr_sg["Laengengrad"].isnull())] + mastr_sg = gpd.GeoDataFrame( + mastr_sg, + geometry=gpd.points_from_xy( + mastr_sg["Laengengrad"], mastr_sg["Breitengrad"], crs=4326 + ), + ) - # Capacity in MW - mastr_sg.loc[:, "Nettonennleistung"] *= 1e-3 + # Exclude columns which are not essential + mastr_sg = mastr_sg.filter( + [ + "EinheitMastrNummer", + "Nettonennleistung", + "geometry", + "Energietraeger", + "Postleitzahl", + "Ort", + "Bundesland", + ], + axis=1, + ) + # Rename carrier + mastr_sg.Energietraeger = "others" - # Merge different sources to one df - mastr_others = pd.concat([mastr_sg, mastr_combustion]).reset_index() + # Change data type + mastr_sg["Postleitzahl"] = mastr_sg["Postleitzahl"].astype(int) - # Delete entries outside Schleswig-Holstein for test mode - if boundary == "Schleswig-Holstein": - mastr_others = mastr_others[ - mastr_others["Bundesland"] == "SchleswigHolstein" - ] + # Capacity in MW + mastr_sg.loc[:, "Nettonennleistung"] *= 1e-3 - # Scale capacities prox to now to meet target values - mastr_prox = scale_prox2now(mastr_others, target, level="federal_state") + # Merge different sources to one df + mastr_others = pd.concat([mastr_sg, mastr_combustion]).reset_index() - # Assign voltage_level based on scaled capacity - mastr_prox["voltage_level"] = np.nan - mastr_prox["voltage_level"] = assign_voltage_level_by_capacity(mastr_prox) + # Delete entries outside Schleswig-Holstein for test mode + if boundary == "Schleswig-Holstein": + mastr_others = mastr_others[ + mastr_others["Bundesland"] == "SchleswigHolstein" + ] - # Rename columns - mastr_prox = mastr_prox.rename( - columns={ - "Energietraeger": "carrier", - "Postleitzahl": "plz", - "Ort": "city", - "Bundesland": "federal_state", - "Nettonennleistung": "el_capacity", - } - ) + # Scale capacities prox to now to meet target values + mastr_prox = scale_prox2now(mastr_others, target, level="federal_state") + + # Assign voltage_level based on scaled capacity + mastr_prox["voltage_level"] = np.nan + mastr_prox["voltage_level"] = assign_voltage_level_by_capacity(mastr_prox) + + # Rename columns + mastr_prox = mastr_prox.rename( + columns={ + "Energietraeger": "carrier", + "Postleitzahl": "plz", + "Ort": "city", + "Bundesland": "federal_state", + "Nettonennleistung": "el_capacity", + } + ) - # Assign bus_id - mastr_prox = assign_bus_id(mastr_prox, cfg) - mastr_prox = mastr_prox.set_crs(4326, allow_override=True) + # Assign bus_id + mastr_prox = assign_bus_id(mastr_prox, cfg) + mastr_prox = mastr_prox.set_crs(4326, allow_override=True) - # Insert into target table - session = sessionmaker(bind=db.engine())() - for i, row in mastr_prox.iterrows(): - entry = EgonPowerPlants( - sources=row.el_capacity, - source_id={"MastrNummer": row.EinheitMastrNummer}, - carrier=row.carrier, - el_capacity=row.el_capacity, - voltage_level=row.voltage_level, - bus_id=row.bus_id, - scenario=scenario, - geom=f"SRID=4326; {row.geometry}", - ) - session.add(entry) - session.commit() + # Insert into target table + session = sessionmaker(bind=db.engine())() + for i, row in mastr_prox.iterrows(): + entry = EgonPowerPlants( + sources=row.el_capacity, + source_id={"MastrNummer": row.EinheitMastrNummer}, + carrier=row.carrier, + el_capacity=row.el_capacity, + voltage_level=row.voltage_level, + bus_id=row.bus_id, + scenario=scenario, + geom=f"SRID=4326; {row.geometry}", + ) + session.add(entry) + session.commit() def discard_not_available_generators(gen, max_date): @@ -1335,7 +1539,8 @@ def log_insert_capacity(df, tech): ) if ( - "eGon2035" in egon.data.config.settings()["egon-data"]["--scenarios"] + "nep2037_2025" in egon.data.config.settings()["egon-data"]["--scenarios"] + or "eGon2035" in egon.data.config.settings()["egon-data"]["--scenarios"] or "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"] ): tasks = tasks + ( diff --git a/src/egon/data/datasets/power_plants/conventional.py b/src/egon/data/datasets/power_plants/conventional.py index 41226730f..f8d2a7179 100644 --- a/src/egon/data/datasets/power_plants/conventional.py +++ b/src/egon/data/datasets/power_plants/conventional.py @@ -10,7 +10,7 @@ import egon.data.config -def select_nep_power_plants(carrier): +def select_nep_power_plants(carrier, scenario): """Select power plants with location from NEP's list of power plants Parameters @@ -24,18 +24,24 @@ def select_nep_power_plants(carrier): Waste power plants from NEP list """ + + if scenario == 'eGon2035' + year = '2035' + elif scenario == 'nep2035_2025' + year = '2037' + cfg = egon.data.config.datasets()["power_plants"] # Select plants with geolocation from list of conventional power plants nep = db.select_dataframe( f""" SELECT bnetza_id, name, carrier, capacity, postcode, city, - federal_state, c2035_capacity + federal_state, c{year}_capacity FROM {cfg['sources']['nep_conv']} WHERE carrier = '{carrier}' AND chp = 'Nein' - AND c2035_chp = 'Nein' - AND c2035_capacity > 0 + AND c{year}_chp = 'Nein' + AND c{year}_capacity > 0 AND postcode != 'None'; """ ) @@ -97,6 +103,7 @@ def match_nep_no_chp( consider_location="plz", consider_carrier=True, consider_capacity=True, + scenario ): """Match Power plants (no CHP) from MaStR to list of power plants from NEP @@ -122,6 +129,11 @@ def match_nep_no_chp( """ + if i == 'eGon2035' + year = '2035' + elif i == 'nep2037_2025' + year = '2037' + list_federal_states = pd.Series( { "Hamburg": "HH", @@ -199,8 +211,8 @@ def match_nep_no_chp( 1 ), "carrier": ET, - "el_capacity": row.c2035_capacity, - "scenario": "eGon2035", + "el_capacity": getattr(row, f"c{year}_capacity"), + "scenario": f"eGon{year}", "geometry": selected.geometry.head(1), "voltage_level": selected.voltage_level.head( 1 diff --git a/src/egon/data/datasets/power_plants/pv_ground_mounted.py b/src/egon/data/datasets/power_plants/pv_ground_mounted.py index a6b4ba5b7..4884977e0 100644 --- a/src/egon/data/datasets/power_plants/pv_ground_mounted.py +++ b/src/egon/data/datasets/power_plants/pv_ground_mounted.py @@ -981,11 +981,147 @@ def run_methodology( if len(distr_i) > 0: pv_per_distr = pd.concat([pv_per_distr, distr_i]) + # initialize final dataframe + pv_rora_nep2037_2025 = gpd.GeoDataFrame() + pv_agri_nep2037_2025 = gpd.GeoDataFrame() + pv_exist_nep2037_2025 = gpd.GeoDataFrame() + pv_per_distr_nep2037_2025 = gpd.GeoDataFrame() + + if ( + "nep2037_2025" + in egon.data.config.settings()["egon-data"]["--scenarios"] + ): + ### + print(" ") + print("scenario: nep2037_2025") + print(" ") + + # German states + sql = "SELECT geometry as geom, nuts FROM boundaries.vg250_lan" + states = gpd.GeoDataFrame.from_postgis(sql, con) + + # assumption for target value of installed capacity + sql = ( + "SELECT capacity,scenario_name,nuts FROM " + "supply.egon_scenario_capacities WHERE carrier='solar'" + ) + target = pd.read_sql(sql, con) + target = target[target["scenario_name"] == "nep2037_2025"] + nuts = np.unique(target["nuts"]) + + # prepare selection per state + rora = rora.set_geometry("centroid") + agri = agri.set_geometry("centroid") + potentials_rora = potentials_rora.set_geometry("geom") + potentials_agri = potentials_agri.set_geometry("geom") + + # check target value per state + for i in nuts: + target_power = ( + target[target["nuts"] == i]["capacity"].iloc[0] * 1000 + ) + + ### + land = target[target["nuts"] == i]["nuts"].iloc[0] + print(" ") + print("Bundesland (NUTS): " + land) + print("target power: " + str(target_power / 1000) + " MW") + + # select state + state = states[states["nuts"] == i] + state = state.to_crs(3035) + + # select PVs in state + rora_i = gpd.sjoin(rora, state) + agri_i = gpd.sjoin(agri, state) + exist_i = gpd.sjoin(exist, state) + rora_i.drop("index_right", axis=1, inplace=True) + agri_i.drop("index_right", axis=1, inplace=True) + exist_i.drop("index_right", axis=1, inplace=True) + rora_i.drop_duplicates(inplace=True) + agri_i.drop_duplicates(inplace=True) + exist_i.drop_duplicates(inplace=True) + + # select potential areas in state + potentials_rora_i = gpd.sjoin(potentials_rora, state) + potentials_agri_i = gpd.sjoin(potentials_agri, state) + potentials_rora_i.drop("index_right", axis=1, inplace=True) + potentials_agri_i.drop("index_right", axis=1, inplace=True) + potentials_rora_i.drop_duplicates(inplace=True) + potentials_agri_i.drop_duplicates(inplace=True) + + # check target value and adapt installed capacity if necessary + rora_i, agri_i, exist_i, distr_i = check_target( + rora_i, + agri_i, + exist_i, + potentials_rora_i, + potentials_agri_i, + target_power, + pow_per_area, + con, + ) + + if len(distr_i) > 0: + distr_i["nuts"] = target[target["nuts"] == i]["nuts"].iloc[ + 0 + ] + + # ### examination of built PV parks per state + rora_i_mv = rora_i[rora_i["voltage_level"] == 5] + rora_i_hv = rora_i[rora_i["voltage_level"] == 4] + agri_i_mv = agri_i[agri_i["voltage_level"] == 5] + agri_i_hv = agri_i[agri_i["voltage_level"] == 4] + print( + "nep2037_2025: Examination of voltage level per federal state:" + ) + print("a) PVs on potential areas Road & Railway: ") + print( + "Total installed capacity: " + + str(rora_i["installed capacity in kW"].sum() / 1000) + + " MW" + ) + print("Number of PV farms: " + str(len(rora_i))) + print(" - thereof MV: " + str(len(rora_i_mv))) + print(" - thereof HV: " + str(len(rora_i_hv))) + print("b) PVs on potential areas Agriculture: ") + print( + "Total installed capacity: " + + str(agri_i["installed capacity in kW"].sum() / 1000) + + " MW" + ) + print("Number of PV farms: " + str(len(agri_i))) + print(" - thereof MV: " + str(len(agri_i_mv))) + print(" - dthereof HV: " + str(len(agri_i_hv))) + print("c) Existing PVs not in potential areas: ") + print("Number of PV farms: " + str(len(exist_i))) + print("d) PVs on additional potential areas per MV-District: ") + if len(distr_i) > 0: + distr_i_mv = distr_i[distr_i["voltage_level"] == 5] + distr_i_hv = distr_i[distr_i["voltage_level"] == 4] + print( + "Total installed capacity: " + + str(distr_i["installed capacity in kW"].sum() / 1000) + + " MW" + ) + print("Number of PV farms: " + str(len(distr_i))) + print(" - thereof MV: " + str(len(distr_i_mv))) + print(" - thereof HV: " + str(len(distr_i_hv))) + else: + print(" -> No additional expansion necessary") + print(" ") + + pv_rora_nep2037_2025 = pv_rora_nep2037_2025.append(rora_i) + pv_agri_nep2037_2025 = pv_agri_nep2037_2025.append(agri_i) + pv_exist_nep2037_2025 = pv_exist_nep2037_2025.append(exist_i) + if len(distr_i) > 0: + pv_per_distr_nep2037_2025 = pd.concat([pv_per_distr_nep2037_2025, distr_i]) + if ( "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"] ): - # 2) scenario: eGon100RE + # 3) scenario: eGon100RE # assumption for target value of installed capacity in Germany per # scenario @@ -1073,7 +1209,56 @@ def run_methodology( ) plt.savefig("pv_per_distr_map_eGon2035.png", dpi=300) - # 2) eGon100RE + # 2) nep2037_2025 + + # get MV grid districts + sql = "SELECT bus_id, geom FROM grid.egon_mv_grid_district" + distr = gpd.GeoDataFrame.from_postgis(sql, con) + distr = distr.set_index("bus_id") + + # assign pv_per_distr-power to districts + distr["capacity"] = pd.Series() + for index, row in distr.iterrows(): + if index in np.unique(pv_per_distr_nep2037_2025["grid_district"]): + pv = pv_per_distr_nep2037_2025[pv_per_distr_nep2037_2025["grid_district"] == index] + x = pv["installed capacity in kW"].iloc[0] + distr["capacity"].loc[index] = x + else: + distr["capacity"].loc[index] = 0 + distr["capacity"] = distr["capacity"] / 1000 + + # add pv_rora- and pv_agri-power to district + pv_rora_nep2037_2025 = pv_rora_nep2037_2025.set_geometry("centroid") + pv_agri_nep2037_2025 = pv_agri_nep2037_2025.set_geometry("centroid") + overlay_rora = gpd.sjoin(pv_rora_nep2037_2025, distr) + overlay_agri = gpd.sjoin(pv_agri_nep2037_2025, distr) + + for index, row in distr.iterrows(): + o_rora = overlay_rora[overlay_rora["index_right"] == index] + o_agri = overlay_agri[overlay_agri["index_right"] == index] + cap_rora = o_rora["installed capacity in kW"].sum() / 1000 + cap_agri = o_agri["installed capacity in kW"].sum() / 1000 + distr["capacity"].loc[index] = ( + distr["capacity"].loc[index] + cap_rora + cap_agri + ) + + from matplotlib import pyplot as plt + + fig, ax = plt.subplots(1, 1) + distr.boundary.plot(linewidth=0.2, ax=ax, color="black") + distr.plot( + ax=ax, + column="capacity", + cmap="magma_r", + legend=True, + legend_kwds={ + "label": "Installed capacity in MW", + "orientation": "vertical", + }, + ) + plt.savefig("pv_per_distr_map__nep2037_2025.png", dpi=300) + + # 3) eGon100RE # get MV grid districts sql = "SELECT bus_id, geom FROM grid.egon_mv_grid_district" @@ -1139,6 +1324,10 @@ def run_methodology( pv_agri, pv_exist, pv_per_distr, + pv_rora_nep2037_2025, + pv_agri_nep2037_2025, + pv_exist_nep2037_2025, + pv_per_distr_nep2037_2025, pv_rora_100RE, pv_agri_100RE, pv_exist_100RE, @@ -1310,6 +1499,22 @@ def insert_pv_parks( else: pv_parks = gpd.GeoDataFrame() + if "nep2037_2025" in egon.data.config.settings()["egon-data"]["--scenarios"]: + if ( + pv_rora["installed capacity in kW"].sum() > 0 + or pv_agri_nep2037_2025["installed capacity in kW"].sum() > 0 + or pv_per_distr_nep2037_2025["installed capacity in kW"].sum() > 0 + or pv_exist_nep2037_2025["installed capacity in kW"].sum() > 0 + ): + pv_parks_nep2037_2025 = insert_pv_parks( + pv_rora_nep2037_2025, pv_agri_nep2037_2025, pv_exist_nep2037_2025, pv_per_distr_nep2037_2025, "nep2037_2025" + ) + + else: + pv_parks_nep2037_2025 = gpd.GeoDataFrame() + else: + pv_parks_nep2037_2025 = gpd.GeoDataFrame() + if "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"]: if ( pv_rora_100RE["installed capacity in kW"].sum() > 0 @@ -1330,4 +1535,4 @@ def insert_pv_parks( else: pv_parks_100RE = gpd.GeoDataFrame() - return pv_parks, pv_parks_100RE + return pv_parks, pv_parks_nep2037_2025, pv_parks_100RE diff --git a/src/egon/data/datasets/power_plants/pv_rooftop.py b/src/egon/data/datasets/power_plants/pv_rooftop.py index f10918f48..5f9fb58a5 100644 --- a/src/egon/data/datasets/power_plants/pv_rooftop.py +++ b/src/egon/data/datasets/power_plants/pv_rooftop.py @@ -36,6 +36,10 @@ def pv_rooftop_per_mv_grid(): pv_rooftop_per_mv_grid_and_scenario( scenario="eGon2035", level="federal_state" ) + if "nep2037_2025" in s: + pv_rooftop_per_mv_grid_and_scenario( + scenario="nep2037_2025", level="federal_state" + ) if "eGon100RE" in s: pv_rooftop_per_mv_grid_and_scenario( scenario="eGon100RE", level="national" @@ -170,7 +174,7 @@ def pv_rooftop_per_mv_grid_and_scenario(scenario, level): dataset = config.settings()["egon-data"]["--dataset-boundary"] - if dataset == "Schleswig-Holstein": + if dataset == "Schleswig-Holstein" and scenario == "eGon2035": sources_scn = config.datasets()["scenario_input"]["sources"] path = Path( @@ -192,6 +196,28 @@ def pv_rooftop_per_mv_grid_and_scenario(scenario, level): target *= share + if dataset == "Schleswig-Holstein" and scenario == "nep2037_2025": + sources_scn = config.datasets()["scenario_input"]["sources"] + + path = Path( + f"./data_bundle_egon_data/nep2037_version2025/" + f"{sources_scn['nep2037_2025']['capacities']}" + ).resolve() + + total_2037 = ( + pd.read_excel( + path, + sheet_name="1.Entwurf_NEP2037_V2025", + index_col="Unnamed: 0", + ).at["PV (Aufdach)", "Summe"] + * 1000 + ) + sh_2037 = scenario_data(scenario="nep2037_2025").capacity.sum() + + share = sh_2037 / total_2037 + + target *= share + demand["share_country"] = demand.demand / demand.demand.sum() demand.set_index("bus_id", inplace=True) diff --git a/src/egon/data/datasets/power_plants/pv_rooftop_buildings.py b/src/egon/data/datasets/power_plants/pv_rooftop_buildings.py index 66e16623d..6ced3b8c1 100644 --- a/src/egon/data/datasets/power_plants/pv_rooftop_buildings.py +++ b/src/egon/data/datasets/power_plants/pv_rooftop_buildings.py @@ -1,6 +1,6 @@ """ Distribute MaStR PV rooftop capacities to OSM and synthetic buildings. Generate -new PV rooftop generators for scenarios eGon2035 and eGon100RE. +new PV rooftop generators for scenarios eGon2035, nep2037_2025 and eGon100RE. See documentation section :ref:`pv-rooftop-ref` for more information. @@ -70,6 +70,7 @@ "status2019": pd.Timestamp("2020-01-01", tz="UTC"), "status2023": pd.Timestamp("2024-01-01", tz="UTC"), "eGon2035": pd.Timestamp("2035-01-01", tz="UTC"), + "nep2037_2025": pd.Timestamp("2037-01-01", tz="UTC"), "eGon100RE": pd.Timestamp("2050-01-01", tz="UTC"), } PV_ROOFTOP_LIFETIME = pd.Timedelta(20 * 365, unit="D") diff --git a/src/egon/data/datasets/power_plants/wind_farms.py b/src/egon/data/datasets/power_plants/wind_farms.py index 0c32c1543..fcc014d46 100644 --- a/src/egon/data/datasets/power_plants/wind_farms.py +++ b/src/egon/data/datasets/power_plants/wind_farms.py @@ -93,7 +93,8 @@ def insert(): target_power_df["scenario_name"] != "eGon100RE" ] - if "eGon2035" in target_power_df["scenario_name"].values: + if ("eGon2035" in target_power_df["scenario_name"].values or "nep2037_2025" in + target_power_df["scenario_name"].values): # Fit wind farms scenarions for each one of the states for bundesland in target_power_df.index: state_wf = gpd.clip(wf_areas, target_power_df.at[bundesland, "geom"]) diff --git a/src/egon/data/datasets/power_plants/wind_offshore.py b/src/egon/data/datasets/power_plants/wind_offshore.py index 96b87b9a2..fca8731bc 100644 --- a/src/egon/data/datasets/power_plants/wind_offshore.py +++ b/src/egon/data/datasets/power_plants/wind_offshore.py @@ -16,7 +16,7 @@ def map_id_bus(scenario): "source" ]["url"] - if scenario in ["eGon2035", "eGon100RE"]: + if scenario in ["eGon2035", "nep2037_2025", "eGon100RE"]: id_bus = { "Büttel": "136034396", "Suchraum Gemeinden Ibbenbüren/Mettingen/Westerkappeln": "114319248", @@ -196,6 +196,26 @@ def insert(): offshore.dropna(subset=["Netzverknuepfungspunkt"], inplace=True) offshore.rename(columns={"C 2035": "el_capacity"}, inplace=True) + elif scenario == "nep2037_2025": + offshore_path = ( + Path(".") + / "data_bundle_egon_data" + / "nep2037_version2025" + / cfg["sources"]["nep_2037"] + ) + + offshore = pd.read_excel( + offshore_path, + sheet_name="Wind_Offshore_NEP", + usecols=[ + "Netzverknuepfungspunkt", + "Spannungsebene in kV", + "C 2037", + ], + ) + offshore.dropna(subset=["Netzverknuepfungspunkt"], inplace=True) + offshore.rename(columns={"C 2037": "el_capacity"}, inplace=True) + elif scenario == "eGon100RE": offshore_path = ( Path(".") @@ -287,7 +307,7 @@ def insert(): offshore.dropna(subset=["bus_id"], inplace=True) # Overwrite geom for status2019 parks - if scenario in ["eGon2035", "eGon100RE"]: + if scenario in ["eGon2035", "nep2037_2025","eGon100RE"]: offshore["Name ONEP/NEP"] = offshore["Netzverknuepfungspunkt"].map( assign_ONEP_areas() ) diff --git a/src/egon/data/datasets/renewable_feedin.py b/src/egon/data/datasets/renewable_feedin.py index 2858c77cb..82909ddbc 100644 --- a/src/egon/data/datasets/renewable_feedin.py +++ b/src/egon/data/datasets/renewable_feedin.py @@ -337,7 +337,7 @@ def feedin_per_turbine(): return gdf -def wind(): +def wind(scn): """Insert feed-in timeseries for wind onshore turbines to database Returns @@ -360,7 +360,7 @@ def wind(): ["E-141", "E-126"] ] - weather_year = get_sector_parameters("global", "eGon2035")["weather_year"] + weather_year = get_sector_parameters("global", scn)["weather_year"] df = pd.DataFrame( index=weather_cells.index, diff --git a/src/egon/data/datasets/sanity_checks.py b/src/egon/data/datasets/sanity_checks.py index a51c2dc3f..df4a551d4 100755 --- a/src/egon/data/datasets/sanity_checks.py +++ b/src/egon/data/datasets/sanity_checks.py @@ -1,5 +1,5 @@ """ -This module does sanity checks for both the eGon2035 and the eGon100RE scenario +This module does sanity checks for the eGon2035, nep2037_2025 and the eGon100RE scenario separately where a percentage error is given to showcase difference in output and input values. Please note that there are missing input technologies in the supply tables. @@ -87,8 +87,10 @@ def __init__(self, dependencies): version=self.version, dependencies=dependencies, tasks={ - etrago_eGon2035_electricity, - etrago_eGon2035_heat, + etrago_electricity('eGon2035'), + etrago_heat('eGon2035'), + etrago_electricity('nep2037_2025'), + etrago_heat('nep2037_2025'), residential_electricity_annual_sum, residential_electricity_hh_refinement, cts_electricity_demand_share, @@ -96,18 +98,20 @@ def __init__(self, dependencies): sanitycheck_emobility_mit, sanitycheck_pv_rooftop_buildings, sanitycheck_home_batteries, - etrago_eGon2035_gas_DE, - etrago_eGon2035_gas_abroad, + etrago_gas_DE('eGon2035'), + etrago_gas_abroad('eGon2035'), + etrago_gas_DE('nep2037_2025'), + etrago_gas_abroad('nep2037_2025'), sanitycheck_dsm, }, ) -def etrago_eGon2035_electricity(): +def etrago_electricity(scn): """Execute basic sanity checks. Returns print statements as sanity checks for the electricity sector in - the eGon2035 scenario. + the eGon2035 or nep2037_2025 scenario. Parameters ---------- @@ -118,7 +122,11 @@ def etrago_eGon2035_electricity(): None """ - scn = "eGon2035" + if scn == "eGon2035" + year = "2035" + elif scn == "nep2037_2025" + year = "2037" + # Section to check generator capacities logger.info(f"Sanity checks for scenario {scn}") @@ -147,12 +155,13 @@ def etrago_eGon2035_electricity(): FROM grid.egon_etrago_generator WHERE bus IN ( SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon2035' + WHERE scn_name = '%s' AND country = 'DE') AND carrier IN ('biomass', 'industrial_biomass_CHP', 'central_biomass_CHP') GROUP BY (scn_name); """, + params = [scn], warning=False, ) @@ -166,10 +175,11 @@ def etrago_eGon2035_electricity(): AND bus IN (SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon2035' + WHERE scn_name = '%s' AND country = 'DE') GROUP BY (scn_name); """, + params=[scn], warning=False, ) @@ -239,10 +249,11 @@ def etrago_eGon2035_electricity(): AND bus IN (SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon2035' + WHERE scn_name = '%s' AND country = 'DE') GROUP BY (scn_name); """, + params=[scn], warning=False, ) @@ -307,14 +318,15 @@ def etrago_eGon2035_electricity(): ON (a.load_id = b.load_id) JOIN grid.egon_etrago_bus c ON (a.bus=c.bus_id) - AND b.scn_name = 'eGon2035' - AND a.scn_name = 'eGon2035' + AND b.scn_name = '%s' + AND a.scn_name = '%s' AND a.carrier = 'AC' - AND c.scn_name= 'eGon2035' + AND c.scn_name= '%s' AND c.country='DE' GROUP BY (a.scn_name, a.carrier); """, + params=[scn, scn, scn], warning=False, )["load_twh"].values[0] @@ -322,21 +334,23 @@ def etrago_eGon2035_electricity(): """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind FROM demand.egon_demandregio_cts_ind - WHERE scenario= 'eGon2035' - AND year IN ('2035') + WHERE scenario= '%s' + AND year IN ('%s') GROUP BY (scenario); """, + params=[scn, year], warning=False, )["demand_mw_regio_cts_ind"].values[0] input_hh = db.select_dataframe( """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh FROM demand.egon_demandregio_hh - WHERE scenario= 'eGon2035' - AND year IN ('2035') + WHERE scenario= '%s' + AND year IN ('%s') GROUP BY (scenario); """, + params=[scn, year], warning=False, )["demand_mw_regio_hh"].values[0] @@ -346,8 +360,7 @@ def etrago_eGon2035_electricity(): print(f"electricity demand: {e} %") - -def etrago_eGon2035_heat(): +def etrago_heat(scn): """Execute basic sanity checks. Returns print statements as sanity checks for the heat sector in @@ -365,7 +378,6 @@ def etrago_eGon2035_heat(): # Check input and output values for the carriers "others", # "reservoir", "run_of_river" and "oil" - scn = "eGon2035" # Section to check generator capacities print(f"Sanity checks for scenario {scn}") @@ -385,22 +397,24 @@ def etrago_eGon2035_heat(): ON (a.load_id = b.load_id) JOIN grid.egon_etrago_bus c ON (a.bus=c.bus_id) - AND b.scn_name = 'eGon2035' - AND a.scn_name = 'eGon2035' - AND c.scn_name= 'eGon2035' + AND b.scn_name = '%s' + AND a.scn_name = '%s' + AND c.scn_name= '%s' AND c.country='DE' AND a.carrier IN ('rural_heat', 'central_heat') GROUP BY (a.scn_name); """, + params = [scn, scn, scn], warning=False, )["load_twh"].values[0] input_heat_demand = db.select_dataframe( """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat FROM demand.egon_peta_heat - WHERE scenario= 'eGon2035' + WHERE scenario= '%s' GROUP BY (scenario); """, + params=[scn], warning=False, )["demand_mw_peta_heat"].values[0] @@ -423,9 +437,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw FROM supply.egon_scenario_capacities WHERE carrier= 'urban_central_heat_pump' - AND scenario_name IN ('eGon2035') + AND scenario_name IN ('%s) GROUP BY (carrier); """, + params = [scn], warning=False, )["urban_central_heat_pump_mw"].values[0] @@ -433,9 +448,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw FROM grid.egon_etrago_link WHERE carrier= 'central_heat_pump' - AND scn_name IN ('eGon2035') + AND scn_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["central_heat_pump_mw"].values[0] @@ -451,9 +467,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw FROM supply.egon_scenario_capacities WHERE carrier= 'residential_rural_heat_pump' - AND scenario_name IN ('eGon2035') + AND scenario_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["residential_heat_pump_mw"].values[0] @@ -461,9 +478,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw FROM grid.egon_etrago_link WHERE carrier= 'rural_heat_pump' - AND scn_name IN ('eGon2035') + AND scn_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["rural_heat_pump_mw"].values[0] @@ -483,9 +501,10 @@ def etrago_eGon2035_heat(): SUM(capacity::numeric) as Urban_central_resistive_heater_MW FROM supply.egon_scenario_capacities WHERE carrier= 'urban_central_resistive_heater' - AND scenario_name IN ('eGon2035') + AND scenario_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["urban_central_resistive_heater_mw"].values[0] @@ -493,9 +512,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW FROM grid.egon_etrago_link WHERE carrier= 'central_resistive_heater' - AND scn_name IN ('eGon2035') + AND scn_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["central_resistive_heater_mw"].values[0] @@ -516,9 +536,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw FROM supply.egon_scenario_capacities WHERE carrier= 'urban_central_solar_thermal_collector' - AND scenario_name IN ('eGon2035') + AND scenario_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["solar_thermal_collector_mw"].values[0] @@ -526,9 +547,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw FROM grid.egon_etrago_generator WHERE carrier= 'solar_thermal_collector' - AND scn_name IN ('eGon2035') + AND scn_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["solar_thermal_collector_mw"].values[0] @@ -548,9 +570,10 @@ def etrago_eGon2035_heat(): SUM(capacity::numeric) as Urban_central_geo_thermal_MW FROM supply.egon_scenario_capacities WHERE carrier= 'urban_central_geo_thermal' - AND scenario_name IN ('eGon2035') + AND scenario_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["urban_central_geo_thermal_mw"].values[0] @@ -558,9 +581,10 @@ def etrago_eGon2035_heat(): """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW FROM grid.egon_etrago_generator WHERE carrier= 'geo_thermal' - AND scn_name IN ('eGon2035') + AND scn_name IN ('%s') GROUP BY (carrier); """, + params=[scn], warning=False, )["geo_thermal_mw"].values[0] @@ -737,7 +761,7 @@ def egon_power_plants_pv_roof_building(): len(merge_df.loc[merge_df.building_area.isna()]) == 0 ), f"{len(merge_df.loc[merge_df.building_area.isna()])} != 0" - scenarios = ["status_quo", "eGon2035"] + scenarios = ["status_quo", "eGon2035", "nep2037_2025"] base_path = Path(egon.data.__path__[0]).resolve() @@ -772,7 +796,7 @@ def egon_power_plants_pv_roof_building(): ) for scenario in SCENARIOS: - if scenario == "eGon2035": + if scenario == "eGon2035" or scenario == "nep2037_2025": assert isclose( scenario_data(scenario=scenario).capacity.sum(), merge_df.loc[merge_df.scenario == scenario].capacity.sum(), @@ -781,6 +805,7 @@ def egon_power_plants_pv_roof_building(): f"{scenario_data(scenario=scenario).capacity.sum()} != " f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}" ) + elif scenario == "eGon100RE": sources = config.datasets()["solar_rooftop"]["sources"] @@ -833,7 +858,8 @@ def egon_power_plants_pv_roof_building(): def sanitycheck_emobility_mit(): """Execute sanity checks for eMobility: motorized individual travel - Checks data integrity for eGon2035, eGon2035_lowflex and eGon100RE scenario + Checks data integrity for eGon2035, eGon2035_lowflex, nep2037_2025, + nep2037_2025_lowflex and eGon100RE scenario using assertions: 1. Allocated EV numbers and EVs allocated to grid districts 2. Trip data (original inout data from simBEV) @@ -1270,13 +1296,13 @@ def check_model_data(): ), ) - def check_model_data_lowflex_eGon2035(): + def check_model_data_lowflex(scn): # TODO: Add eGon100RE_lowflex print("") - print("SCENARIO: eGon2035_lowflex") + print(f"SCENARIO: {scn}_lowflex") # Compare driving load and charging load - print(" Loading eGon2035 model timeseries: driving load...") + print(f" Loading {scn} model timeseries: driving load...") with db.session_scope() as session: query = ( session.query( @@ -1289,8 +1315,8 @@ def check_model_data_lowflex_eGon2035(): ) .filter( EgonPfHvLoad.carrier == "land_transport_EV", - EgonPfHvLoad.scn_name == "eGon2035", - EgonPfHvLoadTimeseries.scn_name == "eGon2035", + EgonPfHvLoad.scn_name == scn, + EgonPfHvLoadTimeseries.scn_name == scn, ) ) model_driving_load = pd.read_sql( @@ -1299,7 +1325,7 @@ def check_model_data_lowflex_eGon2035(): driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0) print( - " Loading eGon2035_lowflex model timeseries: dumb charging " + f" Loading {scn}_lowflex model timeseries: dumb charging " "load..." ) with db.session_scope() as session: @@ -1314,8 +1340,8 @@ def check_model_data_lowflex_eGon2035(): ) .filter( EgonPfHvLoad.carrier == "land_transport_EV", - EgonPfHvLoad.scn_name == "eGon2035_lowflex", - EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex", + EgonPfHvLoad.scn_name == f"{scn}_lowflex", + EgonPfHvLoadTimeseries.scn_name == f"{scn}_lowflex", ) ) model_charging_load_lowflex = pd.read_sql( @@ -1328,9 +1354,9 @@ def check_model_data_lowflex_eGon2035(): # Ratio of driving and charging load should be 0.9 due to charging # efficiency print(" Compare cumulative loads...") - print(f" Driving load (eGon2035): {driving_load.sum() / 1e6} TWh") + print(f" Driving load ({scn}): {driving_load.sum() / 1e6} TWh") print( - f" Dumb charging load (eGon2035_lowflex): " + f" Dumb charging load ({scn}_lowflex): " f"{charging_load.sum() / 1e6} TWh" ) driving_load_theoretical = ( @@ -1341,18 +1367,19 @@ def check_model_data_lowflex_eGon2035(): driving_load_theoretical, rtol=0.01, err_msg=( - f"The driving load (eGon2035) deviates by more than 1% " + f"The driving load ({scn}) deviates by more than 1% " f"from the theoretical driving load calculated from charging " - f"load (eGon2035_lowflex) with an efficiency of " + f"load ({scn}_lowflex) with an efficiency of " f"{float(meta_run_config.eta_cp)}." ), ) + print("=====================================================") print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===") print("=====================================================") - for scenario_name in ["eGon2035", "eGon100RE"]: + for scenario_name in ["eGon2035", "nep2037_2025", "eGon100RE"]: scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name] print("") @@ -1382,7 +1409,8 @@ def check_model_data_lowflex_eGon2035(): check_model_data() print("") - check_model_data_lowflex_eGon2035() + check_model_data_lowflex("eGon2035") + check_model_data_lowflex("nep2037_2025") print("=====================================================") @@ -1457,6 +1485,11 @@ def sanity_check_gas_buses(scn): "H2_grid": "H2_feedin", "H2_saltcavern": "power_to_H2", }, + "nep2037_2025": { + "CH4": "CH4", + "H2_grid": "H2_feedin", + "H2_saltcavern": "power_to_H2", + }, # "eGon100RE": { # "CH4": "CH4", # "H2_grid": "H2_retrofit", @@ -1538,7 +1571,7 @@ def sanity_check_CH4_stores(scn): in the database (for one scenario) and * the sum of: * the capacity the gas grid allocated to CH4 (total capacity - in eGon2035 and capacity reduced the share of the grid + in eGon2035, and nep2037_2025 and capacity reduced the share of the grid allocated to H2 in eGon100RE) * the total capacity of the CH4 stores in Germany (source: GIE) @@ -1565,6 +1598,8 @@ def sanity_check_CH4_stores(scn): if scn == "eGon2035": grid_cap = 130000 + elif scn == "nep2037_2025": + grid_cap = 130000 elif scn == "eGon100RE": grid_cap = 13000 * ( 1 @@ -1653,7 +1688,7 @@ def sanity_check_gas_one_port(scn): Name of the scenario """ - if scn == "eGon2035": + if scn == "eGon2035" or scn == "nep2037_2025": # Loads ## CH4_for_industry Germany isolated_one_port_c = db.select_dataframe( @@ -1854,7 +1889,7 @@ def sanity_check_CH4_grid(scn): ] p_nom_total = sum(gas_grid_germany["p_nom"].to_list()) - if scn == "eGon2035": + if scn == "eGon2035" or scn == "nep2037_2025": input_gas_grid = p_nom_total if scn == "eGon100RE": input_gas_grid = p_nom_total * ( @@ -1927,11 +1962,11 @@ def sanity_check_gas_links(scn): logger.info(link_with_missing_bus) -def etrago_eGon2035_gas_DE(): - """Execute basic sanity checks for the gas sector in eGon2035 +def etrago_gas_DE(scn): + """Execute basic sanity checks for the gas sector in eGon2035 or nep2037_2025 Returns print statements as sanity checks for the gas sector in - the eGon2035 scenario for the following components in Germany: + the eGon2035 or nep2037_2025 scenario for the following components in Germany: * Buses: with the function :py:func:`sanity_check_gas_buses` * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry' the deviation is calculated between the sum of the loads in the @@ -1956,7 +1991,6 @@ def etrago_eGon2035_gas_DE(): :py:func:`sanity_check_CH4_grid` """ - scn = "eGon2035" if TESTMODE_OFF: logger.info(f"Gas sanity checks for scenario {scn}") @@ -1994,7 +2028,7 @@ def etrago_eGon2035_gas_DE(): )["load_twh"].values[0] input_gas_demand = pd.read_json( - path / (carrier + "_eGon2035.json") + path / (carrier + scn + ".json") ) input_gas_demand = input_gas_demand.loc[:, ["id_region", "value"]] input_gas_demand.set_index("id_region", inplace=True) @@ -2105,11 +2139,12 @@ def etrago_eGon2035_gas_DE(): print("Testmode is on, skipping sanity check.") -def etrago_eGon2035_gas_abroad(): - """Execute basic sanity checks for the gas sector in eGon2035 abroad + +def etrago_gas_abroad(scn): + """Execute basic sanity checks for the gas sector in eGon2035 or nep2037_2025 abroad Returns print statements as sanity checks for the gas sector in - the eGon2035 scenario for the following components in Germany: + the eGon2035 or nep2037_2025 scenario for the following components in Germany: * Buses * Loads: for the carriers 'CH4' and 'H2_for_industry' the deviation is calculated between the sum of the loads in the @@ -2124,7 +2159,10 @@ def etrago_eGon2035_gas_abroad(): grid pipelines. """ - scn = "eGon2035" + if scn == "eGon2035" + year = "2035" + elif scn == "nep2037_2025" + year = "2037" if TESTMODE_OFF: logger.info(f"Gas sanity checks abroad for scenario {scn}") @@ -2134,7 +2172,7 @@ def etrago_eGon2035_gas_abroad(): # Are gas buses isolated? corresponding_carriers = { - "eGon2035": { + scn: { "CH4": "CH4", }, # "eGon100RE": { @@ -2178,7 +2216,7 @@ def etrago_eGon2035_gas_abroad(): input_CH4_demand_abroad = calc_global_ch4_demand( Norway_global_demand_1y ) - input_CH4_demand = input_CH4_demand_abroad["GlobD_2035"].sum() + input_CH4_demand = input_CH4_demand_abroad[f"GlobD_{year}"].sum() ## CH4 output_CH4_demand = db.select_dataframe( @@ -2210,7 +2248,7 @@ def etrago_eGon2035_gas_abroad(): ## H2_for_industry input_power_to_h2_demand_abroad = calc_global_power_to_h2_demand() - input_H2_demand = input_power_to_h2_demand_abroad["GlobD_2035"].sum() + input_H2_demand = input_power_to_h2_demand_abroad[f"GlobD_{year}"].sum() output_H2_demand = db.select_dataframe( f"""SELECT SUM(p_set::numeric) as p_set_abroad @@ -2239,7 +2277,7 @@ def etrago_eGon2035_gas_abroad(): # Generators logger.info("GENERATORS ") CH4_gen = calc_capacities() - input_CH4_gen = CH4_gen["cap_2035"].sum() + input_CH4_gen = CH4_gen[f"cap_{year}"].sum() output_CH4_gen = db.select_dataframe( f"""SELECT SUM(p_nom::numeric) as p_nom_abroad @@ -2341,7 +2379,7 @@ def sanitycheck_dsm(): def df_from_series(s: pd.Series): return pd.DataFrame.from_dict(dict(zip(s.index, s.values))) - for scenario in ["eGon2035", "eGon100RE"]: + for scenario in ["eGon2035", "nep2037_2025", "eGon100RE"]: # p_min and p_max sql = f""" SELECT link_id, bus0 as bus, p_nom FROM grid.egon_etrago_link diff --git a/src/egon/data/datasets/scenario_capacities_2037_2025.py b/src/egon/data/datasets/scenario_capacities_2037_2025.py new file mode 100755 index 000000000..6d166c96b --- /dev/null +++ b/src/egon/data/datasets/scenario_capacities_2037_2025.py @@ -0,0 +1,1006 @@ +"""The central module containing all code dealing with importing data from +Netzentwicklungsplan 2037, Version 2025, Szenario C +""" + +from pathlib import Path +import datetime +import json +import time + +from sqlalchemy import Column, Float, Integer, String +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import numpy as np +import pandas as pd +import yaml + +from egon.data import config, db +from egon.data.datasets import Dataset, wrapped_partial +from egon.data.metadata import ( + context, + generate_resource_fields_from_sqla_model, + license_ccby, + meta_metadata, + sources, +) + +Base = declarative_base() + + +class EgonScenarioCapacities(Base): + __tablename__ = "egon_scenario_capacities" + __table_args__ = {"schema": "supply"} + index = Column(Integer, primary_key=True) + component = Column(String(25)) + carrier = Column(String(50)) + capacity = Column(Float) + nuts = Column(String(12)) + scenario_name = Column(String(50)) + + +class NEP2025ConvPowerPlants(Base): + __tablename__ = "nep_2025_conventional_powerplants" + __table_args__ = {"schema": "supply"} + index = Column(String(50), primary_key=True) + bnetza_id = Column(String(50)) + name = Column(String(100)) + name_unit = Column(String(50)) + carrier_nep = Column(String(50)) + carrier = Column(String(12)) + chp = Column(String(12)) + postcode = Column(String(12)) + city = Column(String(50)) + federal_state = Column(String(12)) + commissioned = Column(String(12)) + status = Column(String(50)) + capacity = Column(Float) + a2037_chp = Column(String(12)) + a2037_capacity = Column(Float) + b2037_chp = Column(String(12)) + b2037_capacity = Column(Float) + c2037_chp = Column(String(12)) + c2037_capacity = Column(Float) + b2045_chp = Column(String(12)) + b2045_capacity = Column(Float) + + +def create_table(): + """Create input tables for scenario setup + + Returns + ------- + None. + + """ + + engine = db.engine() + db.execute_sql("CREATE SCHEMA IF NOT EXISTS supply;") + EgonScenarioCapacities.__table__.drop(bind=engine, checkfirst=True) + NEP2025ConvPowerPlantsonvPowerPlants.__table__.drop(bind=engine, checkfirst=True) + EgonScenarioCapacities.__table__.create(bind=engine, checkfirst=True) + NEP2025ConvPowerPlants.__table__.create(bind=engine, checkfirst=True) + + +def nuts_mapping(): + nuts_mapping = { + "BW": "DE1", + "NW": "DEA", + "HE": "DE7", + "BB": "DE4", + "HB": "DE5", + "RP": "DEB", + "ST": "DEE", + "SH": "DEF", + "MV": "DE8", + "TH": "DEG", + "NI": "DE9", + "SN": "DED", + "HH": "DE6", + "SL": "DEC", + "BE": "DE3", + "BY": "DE2", + } + + return nuts_mapping + + +def insert_capacities_status_quo(scenario: str) -> None: + """Insert capacity of rural heat pumps for status quo + + Returns + ------- + None. + + """ + + targets = config.datasets()["scenario_input"]["targets"] + + # Delete rows if already exist + db.execute_sql( + f""" + DELETE FROM + {targets['scenario_capacities']['schema']}. + {targets['scenario_capacities']['table']} + WHERE scenario_name = '{scenario}' + """ + ) + + rural_heat_capacity = { + # Rural heat capacity for 2019 according to NEP 2035, version 2021 + "status2019": 1e6 * 5e-3, + # Rural heat capacity for 2023 according to NEP 2037, version 2023 + # 1.2 Mio. for 2020 + # https://www.netzentwicklungsplan.de/sites/default/files/2023-07/ + # NEP_2037_2045_V2023_2_Entwurf_Teil1_1.pdf#page=25 + # and 3 kW per heat pump + # https://www.netzentwicklungsplan.de/sites/default/files/2022-11/ + # NEP_2035_V2021_2_Entwurf_Teil1.pdf#page=33 + # plus 0.15 Mio. 2021 and 0.24 Mio. in 2022 + # https://www.enercity.de/magazin/unsere-welt/waermepumpen-boom + # plus 0.2 Mio. in H1 2023 -> Assumption 2023: 2 * 0.2 Mio = 0.4 Mio. + "status2023": (1.2 + 0.15 + 0.24 + 0.4) * 1e6 * 3e-3, + }[scenario] + + if config.settings()["egon-data"]["--dataset-boundary"] != "Everything": + rural_heat_capacity *= population_share() + + db.execute_sql( + f""" + INSERT INTO + {targets['scenario_capacities']['schema']}. + {targets['scenario_capacities']['table']} + (component, carrier, capacity, nuts, scenario_name) + VALUES ( + 'link', + 'residential_rural_heat_pump', + {rural_heat_capacity}, + 'DE', + '{scenario}' + ) + """ + ) + + # Include small storages for scenario2019 + small_storages = { + # MW for Germany + "status2019": 600, + # 1.3 GW in 2020/2021 + # https://www.netzentwicklungsplan.de/sites/default/files/2023-07/ + # NEP_2037_2045_V2023_2_Entwurf_Teil1_1.pdf#page=25 + # Installed quantity 2020: 272,000 + # Installed quantity 2023: 1,197,000 + # https://www.photovoltaik.eu/solarspeicher/ + # bsw-speicherkapazitaet-von-heimspeichern-2023-verdoppelt + "status2023": 1300 * 1197 / 272, + }[scenario] + + db.execute_sql( + f""" + INSERT INTO + {targets['scenario_capacities']['schema']}. + {targets['scenario_capacities']['table']} + (component, carrier, capacity, nuts, scenario_name) + VALUES ( + 'storage_units', + 'battery', + {small_storages}, + 'DE', + '{scenario}' + ) + """ + ) + + +def insert_capacities_per_federal_state_nep(): + """Inserts installed capacities per federal state accordning to + NEP 2037 (version 2025), scenario 2037 C + + Returns + ------- + None. + + """ + + sources = config.datasets()["scenario_input"]["sources"] + targets = config.datasets()["scenario_input"]["targets"] + + # Connect to local database + engine = db.engine() + + # Delete rows if already exist + db.execute_sql( + f""" + DELETE FROM + {targets['scenario_capacities']['schema']}. + {targets['scenario_capacities']['table']} + WHERE scenario_name = 'nep2037_2025' + AND nuts != 'DE' + """ + ) + + # read-in installed capacities per federal state of germany + target_file = ( + Path(".") + / "data_bundle_egon_data" + / "nep2037_version2025" + / sources["nep2037_2025"]["capacities"] + ) + + df = pd.read_excel( + target_file, + sheet_name="1.Entwurf_NEP2037_V2025", + index_col="Unnamed: 0", + ) + + df_draft = pd.read_excel( + target_file, + sheet_name="Entwurf_des_Szenariorahmens", + index_col="Unnamed: 0", + ) + + # Import data on wind offshore capacities + df_windoff = pd.read_excel( + target_file, + sheet_name="WInd_Offshore_NEP", + ).dropna(subset=["Bundesland", "Netzverknuepfungspunkt"]) + + # Remove trailing whitespace from column Bundesland + df_windoff["Bundesland"] = df_windoff["Bundesland"].str.strip() + + # Group and sum capacities per federal state + df_windoff_fs = ( + df_windoff[["Bundesland", "C 2037"]].groupby(["Bundesland"]).sum() + ) + + # List federal state with an assigned wind offshore capacity + index_list = list(df_windoff_fs.index.values) + + # Overwrite capacities in df_windoff with more accurate values from + # df_windoff_fs + + for state in index_list: + df.at["Wind offshore", state] = ( + df_windoff_fs.at[state, "C 2037"] / 1000 + ) + + # sort NEP-carriers: + rename_carrier = { + "Wind onshore": "wind_onshore", + "Wind offshore": "wind_offshore", + "sonstige Konventionelle": "others", + "Speicherwasser": "reservoir", + "Laufwasser": "run_of_river", + "Biomasse": "biomass", + "Erdgas": "gas", + "Kuppelgas": "gas", + "PV (Aufdach)": "solar_rooftop", + "PV (Freiflaeche)": "solar", + "Pumpspeicher": "pumped_hydro", + "sonstige EE": "others", + "Oel": "oil", + "Haushaltswaermepumpen": "residential_rural_heat_pump", + "KWK < 10 MW": "small_chp", + } + # 'Elektromobilitaet gesamt': 'transport', + # 'Elektromobilitaet privat': 'transport'} + + # nuts1 to federal state in Germany + map_nuts = pd.read_sql( + f""" + SELECT DISTINCT ON (nuts) gen, nuts + FROM {sources['boundaries']['schema']}.{sources['boundaries']['table']} + """, + engine, + index_col="gen", + ) + + insert_data = pd.DataFrame() + + scaled_carriers = [ + "Haushaltswaermepumpen", + "PV (Aufdach)", + "PV (Freiflaeche)", + ] + + for bl in map_nuts.index: + data = pd.DataFrame(df[bl]) + + # if distribution to federal states is not provided, + # use data from draft of scenario report + for c in scaled_carriers: + data.loc[c, bl] = ( + df_draft.loc[c, bl] + / df_draft.loc[c, "Summe"] + * df.loc[c, "Summe"] + ) + + # split hydro into run of river and reservoir + # according to draft of scenario report + if data.loc["Lauf- und Speicherwasser", bl] > 0: + for c in ["Speicherwasser", "Laufwasser"]: + data.loc[c, bl] = ( + data.loc["Lauf- und Speicherwasser", bl] + * df_draft.loc[c, bl] + / df_draft.loc[["Speicherwasser", "Laufwasser"], bl].sum() + ) + + data["carrier"] = data.index.map(rename_carrier) + data = data.groupby(data.carrier)[bl].sum().reset_index() + data["component"] = "generator" + data["nuts"] = map_nuts.nuts[bl] + data["scenario_name"] = "nep2037_2025" + + # According to NEP, each heatpump has 5kW_el installed capacity + # source: Entwurf des Szenariorahmens NEP 2035, version 2021, page 47 + data.loc[data.carrier == "residential_rural_heat_pump", bl] *= 5e-6 + data.loc[ + data.carrier == "residential_rural_heat_pump", "component" + ] = "link" + + data = data.rename(columns={bl: "capacity"}) + + # convert GW to MW + data.capacity *= 1e3 + + insert_data = pd.concat([insert_data, data]) + + # Get aggregated capacities from nep's power plant list for certain carrier + + carriers = ["oil", "other_non_renewable", "pumped_hydro"] + + capacities_list = aggr_nep_capacities(carriers) + + # Filter by carrier + updated = insert_data[insert_data["carrier"].isin(carriers)] + + # Merge to replace capacities for carriers "oil", "other_non_renewable" and + # "pumped_hydro" + updated = ( + updated.merge(capacities_list, on=["carrier", "nuts"], how="left") + .fillna(0) + .drop(["capacity"], axis=1) + .rename(columns={"c2037_capacity": "capacity"}) + ) + + # Remove updated entries from df + original = insert_data[~insert_data["carrier"].isin(carriers)] + + # Join dfs + insert_data = pd.concat([original, updated]) + + # Insert data to db + insert_data.to_sql( + targets["scenario_capacities"]["table"], + engine, + schema=targets["scenario_capacities"]["schema"], + if_exists="append", + index=insert_data.index, + ) + + # Add district heating data accordning to energy and full load hours + district_heating_input() + + +def population_share(): + """Calulate share of population in testmode + + Returns + ------- + float + Share of population in testmode + + """ + + sources = config.datasets()["scenario_input"]["sources"] + + return ( + pd.read_sql( + f""" + SELECT SUM(population) + FROM {sources['zensus_population']['schema']}. + {sources['zensus_population']['table']} + WHERE population>0 + """, + con=db.engine(), + )["sum"][0] + / 80324282 + ) + + +def aggr_nep_capacities(carriers): + """Aggregates capacities from NEP power plants list by carrier and federal + state + + Returns + ------- + pandas.Dataframe + Dataframe with capacities per federal state and carrier + + """ + # Get list of power plants from nep + nep_capacities = insert_nep_list_powerplants(export=False)[ + ["federal_state", "carrier", "c2037_capacity"] + ] + + # Sum up capacities per federal state and carrier + capacities_list = ( + nep_capacities.groupby(["federal_state", "carrier"])["c2037_capacity"] + .sum() + .to_frame() + .reset_index() + ) + + # Neglect entries with carriers not in argument + capacities_list = capacities_list[capacities_list.carrier.isin(carriers)] + + # Include NUTS code + capacities_list["nuts"] = capacities_list.federal_state.map(nuts_mapping()) + + # Drop entries for foreign plants with nan values and federal_state column + capacities_list = capacities_list.dropna(subset=["nuts"]).drop( + columns=["federal_state"] + ) + + return capacities_list + + +def map_carrier(): + """Map carriers from NEP and Marktstammdatenregister to carriers from eGon + + Returns + ------- + pandas.Series + List of mapped carriers + + """ + return pd.Series( + data={ + "Abfall": "others", + "Erdgas": "gas", + "Sonstige\nEnergieträger": "others", + "Steinkohle": "coal", + "Kuppelgase": "gas", + "Mineralöl-\nprodukte": "oil", + "Braunkohle": "lignite", + "Waerme": "others", + "Mineraloelprodukte": "oil", + "Mineralölprodukte": "oil", + "NichtBiogenerAbfall": "others", + "nicht biogener Abfall": "others", + "AndereGase": "gas", + "andere Gase": "gas", + "Sonstige_Energietraeger": "others", + "Kernenergie": "nuclear", + "Pumpspeicher": "pumped_hydro", + "Mineralöl-\nProdukte": "oil", + "Biomasse": "biomass", + } + ) + + +def insert_nep_list_powerplants(export=True): + """Insert list of conventional powerplants attached to the approval + of the scenario report by BNetzA + + Parameters + ---------- + export : bool + Choose if nep list should be exported to the data + base. The default is True. + If export=False a data frame will be returned + + Returns + ------- + kw_liste_nep : pandas.DataFrame + List of conventional power plants from nep if export=False + """ + + sources = config.datasets()["scenario_input"]["sources"] + targets = config.datasets()["scenario_input"]["targets"] + + # Connect to local database + engine = db.engine() + + # Read-in data from csv-file + target_file = ( + Path(".") + / "data_bundle_egon_data" + / "nep2037_version2025" + / sources["nep2037_2025"]["list_conv_pp"] + ) + + kw_liste_nep = pd.read_csv(target_file, delimiter=";", decimal=",") + + # Adjust column names + kw_liste_nep = kw_liste_nep.rename( + columns={ + "BNetzA-ID": "bnetza_id", + "Kraftwerksname": "name", + "Blockname": "name_unit", + "Energieträger": "carrier_nep", + "KWK\nJa/Nein": "chp", + "PLZ": "postcode", + "Ort": "city", + "Bundesland/\nLand": "federal_state", + "Inbetrieb-\nnahmejahr": "commissioned", + "Status": "status", + "el. Leistung\n06.02.2020": "capacity", + "A 2037:\nKWK-Ersatz": "a2037_chp", + "A 2037:\nLeistung": "a2037_capacity", + "B 2037\nKWK-Ersatz": "b2037_chp", + "B 2037:\nLeistung": "b2037_capacity", + "C 2037:\nKWK-Ersatz": "c2037_chp", + "C 2037:\nLeistung": "c2037_capacity", + "B 2045:\nKWK-Ersatz": "b2045_chp", + "B 2045:\nLeistung": "b2045_capacity", + } + ) + + # Cut data to federal state if in testmode + boundary = config.settings()["egon-data"]["--dataset-boundary"] + if boundary != "Everything": + map_states = { + "Baden-Württemberg": "BW", + "Nordrhein-Westfalen": "NW", + "Hessen": "HE", + "Brandenburg": "BB", + "Bremen": "HB", + "Rheinland-Pfalz": "RP", + "Sachsen-Anhalt": "ST", + "Schleswig-Holstein": "SH", + "Mecklenburg-Vorpommern": "MV", + "Thüringen": "TH", + "Niedersachsen": "NI", + "Sachsen": "SN", + "Hamburg": "HH", + "Saarland": "SL", + "Berlin": "BE", + "Bayern": "BY", + } + + kw_liste_nep = kw_liste_nep[ + kw_liste_nep.federal_state.isin([map_states[boundary], np.nan]) + ] + + for col in [ + "capacity", + "a2037_capacity", + "b2037_capacity", + "c2037_capacity", + "b2045_capacity", + ]: + kw_liste_nep.loc[ + kw_liste_nep[kw_liste_nep.federal_state.isnull()].index, col + ] *= population_share() + + kw_liste_nep["carrier"] = map_carrier()[kw_liste_nep.carrier_nep].values + + if export is True: + # Insert data to db + kw_liste_nep.to_sql( + targets["nep_conventional_powerplants"]["table"], + engine, + schema=targets["nep_conventional_powerplants"]["schema"], + if_exists="replace", + ) + else: + return kw_liste_nep + + +def district_heating_input(): + """Imports data for district heating networks in Germany + + Returns + ------- + None. + + """ + + sources = config.datasets()["scenario_input"]["sources"] + + # import data to dataframe + file = ( + Path(".") + / "data_bundle_egon_data" + / "nep2037_version2025" + / sources["nep2037_2025"]["capacities"] + ) + df = pd.read_excel( + file, sheet_name="Kurzstudie_KWK", dtype={"Wert": float} + ) + df.set_index(["Energietraeger", "Name"], inplace=True) + + # Scale values to population share in testmode + if config.settings()["egon-data"]["--dataset-boundary"] != "Everything": + df.loc[ + pd.IndexSlice[:, "Fernwaermeerzeugung"], "Wert" + ] *= population_share() + + # Connect to database + engine = db.engine() + session = sessionmaker(bind=engine)() + + # insert heatpumps and resistive heater as link + for c in ["Grosswaermepumpe", "Elektrodenheizkessel"]: + entry = EgonScenarioCapacities( + component="link", + scenario_name="nep2037_2025", + nuts="DE", + carrier="urban_central_" + + ("heat_pump" if c == "Grosswaermepumpe" else "resistive_heater"), + capacity=df.loc[(c, "Fernwaermeerzeugung"), "Wert"] + * 1e6 + / df.loc[(c, "Volllaststunden"), "Wert"] + / df.loc[(c, "Wirkungsgrad"), "Wert"], + ) + + session.add(entry) + + # insert solar- and geothermal as generator + for c in ["Geothermie", "Solarthermie"]: + entry = EgonScenarioCapacities( + component="generator", + scenario_name="nep2037_2025", + nuts="DE", + carrier="urban_central_" + + ( + "solar_thermal_collector" + if c == "Solarthermie" + else "geo_thermal" + ), + capacity=df.loc[(c, "Fernwaermeerzeugung"), "Wert"] + * 1e6 + / df.loc[(c, "Volllaststunden"), "Wert"], + ) + + session.add(entry) + + session.commit() + + +def insert_data_nep(): + """Overall function for importing scenario input data for nep2037_2025 scenario + + Returns + ------- + None. + + """ + + insert_nep_list_powerplants(export=True) + + insert_capacities_per_federal_state_nep() + + +def eGon100_capacities(): + """Inserts installed capacities for the eGon100 scenario + + Returns + ------- + None. + + """ + + sources = config.datasets()["scenario_input"]["sources"] + targets = config.datasets()["scenario_input"]["targets"] + + # read-in installed capacities + cwd = Path(".") + + if config.settings()["egon-data"]["--run-pypsa-eur"]: + filepath = cwd / "run-pypsa-eur" + pypsa_eur_repos = filepath / "pypsa-eur" + # Read YAML file + pes_egonconfig = pypsa_eur_repos / "config" / "config.yaml" + with open(pes_egonconfig, "r") as stream: + data_config = yaml.safe_load(stream) + + target_file = ( + pypsa_eur_repos + / "results" + / data_config["run"]["name"] + / "csvs" + / sources["nep2037_2025"]["capacities"] + ) + + else: + target_file = ( + cwd + / "data_bundle_powerd_data" + / "pypsa_eur" + / "2024-08-02-egondata-integration" + / "csvs" + / sources["nep2037_2025"]["capacities"] + ) + + df = pd.read_csv(target_file, skiprows=5) + df.columns = ["component", "country", "carrier", "p_nom"] + + df.set_index("carrier", inplace=True) + + df = df[df.country.str[:2] == "DE"] + + # Drop country column + df.drop("country", axis=1, inplace=True) + + # Drop copmponents which will be optimized in eGo + unused_carrier = [ + "BEV_charger", + "DAC", + "H2 Electrolysis", + "electricity distribution grid", + "home battery charger", + "home battery discharger", + "H2", + "Li_ion", + "home battery", + "residential rural water tanks charger", + "residential rural water tanks discharger", + "services rural water tanks charger", + "services rural water tanks discharger", + "residential rural water tanks", + "services rural water tanks", + "urban central water tanks", + "urban central water tanks charger", + "urban central water tanks discharger", + "H2 Fuel Cell", + ] + + df = df[~df.index.isin(unused_carrier)] + + df.index = df.index.str.replace(" ", "_") + + # Aggregate offshore wind + df = pd.concat( + [ + df, + pd.DataFrame( + index=["wind_offshore"], + data={ + "p_nom": (df.p_nom["offwind-ac"] + df.p_nom["offwind-dc"]), + "component": df.component["offwind-ac"], + }, + ), + ] + ) + df = df.drop(["offwind-ac", "offwind-dc"]) + + # Aggregate technologies with and without carbon_capture (CC) + for carrier in ["SMR", "urban_central_gas_CHP"]: + df.p_nom[carrier] += df.p_nom[f"{carrier}_CC"] + df = df.drop([f"{carrier}_CC"]) + + # Aggregate residential and services rural heat supply + for merge_carrier in [ + "rural_resistive_heater", + "rural_ground_heat_pump", + "rural_gas_boiler", + "rural_solar_thermal", + ]: + if f"residential_{merge_carrier}" in df.index: + df = pd.concat( + [ + df, + pd.DataFrame( + index=[merge_carrier], + data={ + "p_nom": ( + df.p_nom[f"residential_{merge_carrier}"] + + df.p_nom[f"services_{merge_carrier}"] + ), + "component": df.component[ + f"residential_{merge_carrier}" + ], + }, + ), + ] + ) + df = df.drop( + [f"residential_{merge_carrier}", f"services_{merge_carrier}"] + ) + + # Rename carriers + df.rename( + { + "onwind": "wind_onshore", + "ror": "run_of_river", + "PHS": "pumped_hydro", + "OCGT": "gas", + "rural_ground_heat_pump": "residential_rural_heat_pump", + "urban_central_air_heat_pump": "urban_central_heat_pump", + "urban_central_solar_thermal": ( + "urban_central_solar_thermal_collector" + ), + }, + inplace=True, + ) + + # Reset index + df = df.reset_index() + + # Rename columns + df.rename( + {"p_nom": "capacity", "index": "carrier"}, axis="columns", inplace=True + ) + + df["scenario_name"] = "eGon100RE" + df["nuts"] = "DE" + + db.execute_sql( + f""" + DELETE FROM + {targets['scenario_capacities']['schema']}.{targets['scenario_capacities']['table']} + WHERE scenario_name='eGon100RE' + """ + ) + + df.to_sql( + targets["scenario_capacities"]["table"], + schema=targets["scenario_capacities"]["schema"], + con=db.engine(), + if_exists="append", + index=False, + ) + + +def add_metadata(): + """Add metdata to supply.egon_scenario_capacities + + Returns + ------- + None. + + """ + + # Import column names and datatypes + fields = pd.DataFrame( + generate_resource_fields_from_sqla_model(EgonScenarioCapacities) + ).set_index("name") + + # Set descriptions and units + fields.loc["index", "description"] = "Index" + fields.loc[ + "component", "description" + ] = "Name of representative PyPSA component" + fields.loc["carrier", "description"] = "Name of carrier" + fields.loc["capacity", "description"] = "Installed capacity" + fields.loc["capacity", "unit"] = "MW" + fields.loc[ + "nuts", "description" + ] = "NUTS region, either federal state or Germany" + fields.loc[ + "scenario_name", "description" + ] = "Name of corresponding eGon scenario" + + # Reformat pandas.DataFrame to dict + fields = fields.reset_index().to_dict(orient="records") + + meta = { + "name": "supply.egon_scenario_capacities", + "title": "eGon scenario capacities", + "id": "WILL_BE_SET_AT_PUBLICATION", + "description": ( + "Installed capacities of scenarios used in the eGon project" + ), + "language": ["de-DE"], + "publicationDate": datetime.date.today().isoformat(), + "context": context(), + "spatial": { + "location": None, + "extent": "Germany", + "resolution": None, + }, + "sources": [ + sources()["nep2025"], + sources()["vg250"], + sources()["zensus"], + sources()["egon-data"], + ], + "licenses": [ + license_ccby( + "© Übertragungsnetzbetreiber; " + "© Bundesamt für Kartographie und Geodäsie 2020 (Daten verändert); " + "© Statistische Ämter des Bundes und der Länder 2014; " + "© Jonathan Amme, Clara Büttner, Ilka Cußmann, Julian Endres, Carlos Epia, Stephan Günther, Ulf Müller, Amélia Nadal, Guido Pleßmann, Francesco Witte", + ) + ], + "contributors": [ + { + "title": "Clara Büttner", + "email": "http://github.com/ClaraBuettner", + "date": time.strftime("%Y-%m-%d"), + "object": None, + "comment": "Imported data", + }, + ], + "resources": [ + { + "profile": "tabular-data-resource", + "name": "supply.egon_scenario_capacities", + "path": None, + "format": "PostgreSQL", + "encoding": "UTF-8", + "schema": { + "fields": fields, + "primaryKey": ["index"], + "foreignKeys": [], + }, + "dialect": {"delimiter": None, "decimalSeparator": "."}, + } + ], + "metaMetadata": meta_metadata(), + } + + # Create json dump + meta_json = "'" + json.dumps(meta) + "'" + + # Add metadata as a comment to the table + db.submit_comment( + meta_json, + EgonScenarioCapacities.__table__.schema, + EgonScenarioCapacities.__table__.name, + ) + +tasks = (create_table,) + +scenarios = config.settings()["egon-data"]["--scenarios"] + +status_quo = False + +for scenario in scenarios: + if "status" in scenario: + tasks += ( + wrapped_partial( + insert_capacities_status_quo, scenario=scenario, + postfix=f"_{scenario[-2:]}" + ), + ) + status_quo = True + +if status_quo or ("nep2037_2025" in scenarios): + tasks += (insert_data_nep,) + +if "eGon100RE" in scenarios: + tasks += (eGon100_capacities,) + +tasks += (add_metadata,) + + +class ScenarioCapacities(Dataset): + """ + Create and fill table with installed generation capacities in Germany + + This dataset creates and fills a table with the installed generation capacities in + Germany in a lower spatial resolution (either per federal state or on national level). + This data is coming from external sources (e.g. German grid developement plan for scenario nep2037_2025). + The table is in downstream datasets used to define target values for the installed capacities. + + + *Dependencies* + * :py:func:`Setup ` + * :py:class:`PypsaEurSec ` + * :py:class:`Vg250 ` + * :py:class:`DataBundle ` + * :py:class:`ZensusPopulation ` + + + *Resulting tables* + * :py:class:`supply.egon_scenario_capacities ` is created and filled + * :py:class:`supply.egon_nep_2025_conventional_powerplants + ` is created and filled + + """ + + #: + name: str = "ScenarioCapacities" + #: + version: str = "0.0.14" + + def __init__(self, dependencies): + super().__init__( + name=self.name, + version=self.version, + dependencies=dependencies, + tasks=tasks, + ) + diff --git a/src/egon/data/datasets/scenario_parameters/__init__.py b/src/egon/data/datasets/scenario_parameters/__init__.py index 2ea5ed4a8..3b0778b14 100755 --- a/src/egon/data/datasets/scenario_parameters/__init__.py +++ b/src/egon/data/datasets/scenario_parameters/__init__.py @@ -48,12 +48,15 @@ def create_table(): def get_scenario_year(scenario_name): """Derives scenarios year from scenario name. Scenario - eGon100RE is an exception as year is not in the name.""" + eGon100RE is an exception as year is not in the name. And nep2037_2025 since end + number is the data release date""" try: year = int(scenario_name[-4:]) except ValueError as e: if e.args[0] == "invalid literal for int() with base 10: '00RE'": year = 2050 # eGon100RE + elif scenario_name == "nep2037_2025" + year = 2037 else: raise ValueError("The names of the scenarios do not end with the year!") return year @@ -72,6 +75,31 @@ def insert_scenarios(): session = sessionmaker(bind=db.engine())() + # Scenario nep2037_2025 + nep2037_2025 = EgonScenario(name="nep2037_2025") + + nep2037_2025.description = """ + The scenario nep2037_2025 is based on scenario C 2037 of the + Netzentwicklungsplan Strom 2037, Version 2025. + Scenario C 2037 is characretized by an ambitious expansion of + renewable energies and a higher share of sector coupling. + Analogous to the Netzentwicklungsplan, the countries bordering germany + are modeled based on Ten-Year Network Development Plan, Version 2020. + """ + nep2037_2025.global_parameters = parameters.global_settings(nep2037_2025.name) + + nep2037_2025.electricity_parameters = parameters.electricity(nep2037_2025.name) + + nep2037_2025.gas_parameters = parameters.gas(nep2037_2025.name) + + nep2037_2025.heat_parameters = parameters.heat(nep2037_2025.name) + + nep2037_2025.mobility_parameters = parameters.mobility(nep2037_2025.name) + + session.add(nep2037_2025) + + session.commit() + # Scenario eGon2035 egon2035 = EgonScenario(name="eGon2035") @@ -221,6 +249,15 @@ def get_sector_parameters(sector, scenario=None): else: values = pd.concat( [ + pd.DataFrame( + db.select_dataframe( + f""" + SELECT {sector}_parameters as val + FROM scenario.egon_scenario_parameters + WHERE name='nep2037_2025'""" + ).val[0], + index=["nep2037_2025"], + ), pd.DataFrame( db.select_dataframe( f""" diff --git a/src/egon/data/datasets/scenario_parameters/parameters.py b/src/egon/data/datasets/scenario_parameters/parameters.py index 62955531a..aa44f0495 100755 --- a/src/egon/data/datasets/scenario_parameters/parameters.py +++ b/src/egon/data/datasets/scenario_parameters/parameters.py @@ -69,53 +69,56 @@ def global_settings(scenario): List of global parameters """ - if scenario == "nep2037.2025": + + if scenario == "eGon2035": parameters = { "weather_year": 2011, - "population_year": 2037, - "fuel_costs": { # Szenarionrahmen zum NEP 2025, Version 2025, 1. Entwurf, - "oil": 21.3, # [EUR/MWh] - "gas": 15.2, # [EUR/MWh] - "coal": 6.1, # [EUR/MWh] - "lignite": 6.4, # [EUR/MWh] + "population_year": 2035, + "fuel_costs": { + # Netzentwicklungsplan Strom 2035, Version 2021, 1. Entwurf, p. 39, table 6 + "oil": 73.8, # [EUR/MWh] + "gas": 25.6, # [EUR/MWh] + "coal": 20.2, # [EUR/MWh] + "lignite": 4.0, # [EUR/MWh] "nuclear": 1.7, # [EUR/MWh] "biomass": 40, # Dummyvalue, ToDo: Find a suitable source }, "co2_costs": 76.5, # [EUR/t_CO2] - "co2_emissions": { # Szenarionrahmen zum NEP 2025, Version 2025, 1. Entwurf, + "co2_emissions": { + # Netzentwicklungsplan Strom 2035, Version 2021, 1. Entwurf, p. 40, table 8 "waste": 0.165, # [t_CO2/MW_th] "lignite": 0.393, # [t_CO2/MW_th] "gas": 0.201, # [t_CO2/MW_th] "nuclear": 0.0, # [t_CO2/MW_th] - "oil": 0.286, # [t_CO2/MW_th] - "coal": 0.377, # [t_CO2/MW_th] + "oil": 0.288, # [t_CO2/MW_th] + "coal": 0.335, # [t_CO2/MW_th] "other_non_renewable": 0.268, # [t_CO2/MW_th] - # hydrogen: 0 }, "interest_rate": 0.05, # [p.u.] } - elif scenario == "eGon2035": + elif scenario == "nep2037_2025": parameters = { "weather_year": 2011, - "population_year": 2035, - "fuel_costs": { # Netzentwicklungsplan Strom 2035, Version 2021, 1. Entwurf, p. 39, table 6 - "oil": 73.8, # [EUR/MWh] - "gas": 25.6, # [EUR/MWh] - "coal": 20.2, # [EUR/MWh] - "lignite": 4.0, # [EUR/MWh] + "population_year": 2037, + "fuel_costs": { # Szenarionrahmen zum NEP 2037, Version 2025, 1. Entwurf, + "oil": 21.3, # [EUR/MWh] + "gas": 15.2, # [EUR/MWh] + "coal": 6.1, # [EUR/MWh] + "lignite": 6.4, # [EUR/MWh] "nuclear": 1.7, # [EUR/MWh] "biomass": 40, # Dummyvalue, ToDo: Find a suitable source }, - "co2_costs": 76.5, # [EUR/t_CO2] - "co2_emissions": { # Netzentwicklungsplan Strom 2035, Version 2021, 1. Entwurf, p. 40, table 8 + "co2_costs": 176.2, # [EUR/t_CO2] + "co2_emissions": { # Szenarionrahmen zum NEP 2025, Version 2025, 1. Entwurf, "waste": 0.165, # [t_CO2/MW_th] "lignite": 0.393, # [t_CO2/MW_th] "gas": 0.201, # [t_CO2/MW_th] "nuclear": 0.0, # [t_CO2/MW_th] - "oil": 0.288, # [t_CO2/MW_th] - "coal": 0.335, # [t_CO2/MW_th] - "other_non_renewable": 0.268, # [t_CO2/MW_th] + "oil": 0.286, # [t_CO2/MW_th] + "coal": 0.377, # [t_CO2/MW_th] + "other_non_renewable": 0.268, # [t_CO2/MW_th] NEP 2035 + # hydrogen: 0 }, "interest_rate": 0.05, # [p.u.] } @@ -225,7 +228,7 @@ def electricity(scenario): List of parameters of electricity sector """ - if scenario == "eGon2037.2025": + if scenario == "nep2037_2025": costs = read_csv(2037) @@ -283,35 +286,36 @@ def electricity(scenario): } # Insert overnight investment costs - # Source for eHV grid costs: Netzentwicklungsplan Strom 2035, Version 2021, 2. Entwurf - # Source for HV lines and cables: Dena Verteilnetzstudie 2021, p. 146 + # Source for eHV grid costs: Netzentwicklungsplan Strom 2037, Version 2023, + # 1. Entwurf + # Source for HV lines and cables: Dena Verteilnetzstudie 2012, p. 146 parameters["overnight_cost"] = { - "ac_ehv_overhead_line": 2.5e6 + "ac_ehv_overhead_line": 4.5e6 # NEP / ( 2 * parameters["electrical_parameters"]["ac_line_380kV"]["s_nom"] ), # [EUR/km/MW] - "ac_ehv_cable": 11.5e6 + "ac_ehv_cable": 16e6 # NEP / ( 2 * parameters["electrical_parameters"]["ac_cable_380kV"][ "s_nom" ] ), # [EUR/km/MW] - "ac_hv_overhead_line": 0.06e6 + "ac_hv_overhead_line": 0.06e6 # Verteilnetzstudie / parameters["electrical_parameters"]["ac_line_110kV"][ "s_nom" ], # [EUR/km/MW] - "ac_hv_cable": 0.8e6 + "ac_hv_cable": 0.8e6 # Verteilnetzstudie / parameters["electrical_parameters"]["ac_cable_110kV"][ "s_nom" ], # [EUR/km/MW] - "dc_overhead_line": 0.5e3, # [EUR/km/MW] - "dc_cable": 3.25e3, # [EUR/km/MW] - "dc_inverter": 0.3e6, # [EUR/MW] - "transformer_380_110": 17.33e3, # [EUR/MVA] - "transformer_380_220": 13.33e3, # [EUR/MVA] - "transformer_220_110": 17.5e3, # [EUR/MVA] + "dc_overhead_line": 0.5e3, # [EUR/km/MW] # NEP 2021 (na in 2023) + "dc_cable": 3.5e3, # [EUR/km/MW] # NEP 2023 for 320kV + "dc_inverter": 0.35e6, # [EUR/MW] # NEP 2023 for 320kV + "transformer_380_110": 21e3, # [EUR/MVA] # NEP + "transformer_380_220": 15e3, # [EUR/MVA] # NEP + "transformer_220_110": 18e3, # [EUR/MVA] # NEP "battery inverter": read_costs( costs, "battery inverter", "investment" ), @@ -357,7 +361,7 @@ def electricity(scenario): parameters["capital_cost"][comp] = annualize_capital_costs( parameters["overnight_cost"][comp], parameters["lifetime"][comp], - global_settings("eGon2035")["interest_rate"], + global_settings("nep2037_2025")["interest_rate"], ) parameters["capital_cost"]["battery"] = ( @@ -1007,6 +1011,81 @@ def gas(scenario): "biogas": 10000000, # [MWh] Netzentwicklungsplan Gas 2020–2030 } + elif scenario == "nep2037_2025": + costs = read_csv(2037) + + parameters = { + "main_gas_carrier": "CH4", + "H2_feedin_volumetric_fraction": 0.15, + } + # Insert effciencies in p.u. + parameters["efficiency"] = { + "power_to_H2": read_costs(costs, "electrolysis", "efficiency"), + "H2_to_power": read_costs(costs, "fuel cell", "efficiency"), + "CH4_to_H2": read_costs(costs, "SMR", "efficiency"), + "H2_feedin": 1, + "H2_to_CH4": read_costs(costs, "methanation", "efficiency"), + "OCGT": read_costs(costs, "OCGT", "efficiency"), + } + # Insert overnight investment costs + parameters["overnight_cost"] = { + "power_to_H2": read_costs(costs, "electrolysis", "investment"), + "H2_to_power": read_costs(costs, "fuel cell", "investment"), + "CH4_to_H2": read_costs(costs, "SMR", "investment"), + "H2_to_CH4": read_costs(costs, "methanation", "investment"), + "H2_feedin": 0, + "H2_underground": read_costs( + costs, "hydrogen storage underground", "investment" + ), + "H2_overground": read_costs( + costs, "hydrogen storage tank incl. compressor", "investment" + ), + "H2_pipeline": read_costs( + costs, "H2 (g) pipeline", "investment" + ), # [EUR/MW/km] + } + + # Insert lifetime + parameters["lifetime"] = { + "power_to_H2": read_costs(costs, "electrolysis", "lifetime"), + "H2_to_power": read_costs(costs, "fuel cell", "lifetime"), + "CH4_to_H2": read_costs(costs, "SMR", "lifetime"), + "H2_to_CH4": read_costs(costs, "methanation", "lifetime"), + "H2_underground": read_costs( + costs, "hydrogen storage underground", "lifetime" + ), + "H2_overground": read_costs( + costs, "hydrogen storage tank incl. compressor", "lifetime" + ), + "H2_pipeline": read_costs(costs, "H2 (g) pipeline", "lifetime"), + "H2_feedin": read_costs(costs, "CH4 (g) pipeline", "lifetime"), + } + + # Insert annualized capital costs + parameters["capital_cost"] = {} + + for comp in parameters["overnight_cost"].keys(): + parameters["capital_cost"][comp] = annualize_capital_costs( + parameters["overnight_cost"][comp], + parameters["lifetime"][comp], + global_settings("nep2037_2025")["interest_rate"], + ) + + parameters["marginal_cost"] = { + "CH4": global_settings(scenario)["fuel_costs"]["gas"] + + global_settings(scenario)["co2_costs"] + * global_settings(scenario)["co2_emissions"]["gas"], + "OCGT": read_costs(costs, "OCGT", "VOM"), + "biogas": global_settings(scenario)["fuel_costs"]["gas"], + "chp_gas": read_costs(costs, "central gas CHP", "VOM"), + } + + # Insert max gas production (generator) over the year + parameters["max_gas_generation_overtheyear"] = { + "CH4": 36000000, # [MWh] Netzentwicklungsplan Gas 2020–2030 + "biogas": 10000000, # [MWh] Netzentwicklungsplan Gas 2020–2030 + } + elif scenario == "eGon100RE": costs = read_csv(2050) interest_rate = 0.07 # [p.u.] @@ -1173,6 +1252,25 @@ def mobility(scenario): } } + elif scenario == "nep2037_2025": + parameters = { + "motorized_individual_travel": { + "NEP C 2037": { # amount of vehicles NEP 2023 (30.9 Mio EV, + # 3.2 Mio PHEV); share for + # vehicle + # categories (mini, mesdium, luxury) form MA Helfenbein + "ev_count": 34100000, + "bev_mini_share": 0.233, + "bev_medium_share": 0.518, + "bev_luxury_share": 0.155, + "phev_mini_share": 0.024, + "phev_medium_share": 0.054, + "phev_luxury_share": 0.016, + "model_parameters": {}, + } + } + } + elif scenario == "eGon100RE": # eGon100RE has 3 Scenario variations # * allocation will always be done for all scenarios @@ -1350,6 +1448,86 @@ def heat(scenario): "rural_heat_pump": 0, # Danish Energy Agency, Technology Data for Individual Heating Plants } + if scenario == "nep2037_2025": + costs = read_csv(2037) + + parameters = { + "DE_demand_reduction_residential": 0.854314018923104, + "DE_demand_reduction_service": 0.498286864771128, + "DE_district_heating_share": 0.14, + } + + # Insert efficiency in p.u. + parameters["efficiency"] = { + "water_tank_charger": read_costs( + costs, "water tank charger", "efficiency" + ), + "water_tank_discharger": read_costs( + costs, "water tank discharger", "efficiency" + ), + "central_resistive_heater": read_costs( + costs, "central resistive heater", "efficiency" + ), + "central_gas_boiler": read_costs( + costs, "central gas boiler", "efficiency" + ), + "rural_resistive_heater": read_costs( + costs, "decentral resistive heater", "efficiency" + ), + "rural_gas_boiler": read_costs( + costs, "decentral gas boiler", "efficiency" + ), + } + + # Insert overnight investment costs, in EUR/MWh + parameters["overnight_cost"] = { + "central_water_tank": read_costs( + costs, "central water tank storage", "investment" + ), + "rural_water_tank": read_costs( + costs, "decentral water tank storage", "investment" + ), + } + + # Insert lifetime + parameters["lifetime"] = { + "central_water_tank": read_costs( + costs, "central water tank storage", "lifetime" + ), + "rural_water_tank": read_costs( + costs, "decentral water tank storage", "lifetime" + ), + } + + # Insert annualized capital costs + parameters["capital_cost"] = {} + + for comp in parameters["overnight_cost"].keys(): + parameters["capital_cost"][comp] = annualize_capital_costs( + parameters["overnight_cost"][comp], + parameters["lifetime"][comp], + global_settings("nep2037_2025")["interest_rate"], + ) + + # Insert marginal_costs in EUR/MWh + # marginal cost can include fuel, C02 and operation and maintenance costs + parameters["marginal_cost"] = { + "central_heat_pump": read_costs( + costs, "central air-sourced heat pump", "VOM" + ), + "central_gas_chp": read_costs(costs, "central gas CHP", "VOM"), + "central_gas_boiler": read_costs( + costs, "central gas boiler", "VOM" + ), + "central_resistive_heater": read_costs( + costs, "central resistive heater", "VOM" + ), + "geo_thermal": 2.9, # Danish Energy Agency + "water_tank_charger": 0, # Danish Energy Agency + "water_tank_discharger": 0, # Danish Energy Agency + "rural_heat_pump": 0, # Danish Energy Agency, Technology Data for Individual Heating Plants + } + elif scenario == "eGon100RE": costs = read_csv(2050) diff --git a/src/egon/data/datasets/society_prognosis.py b/src/egon/data/datasets/society_prognosis.py index 48c70b6ab..8a66d1d98 100755 --- a/src/egon/data/datasets/society_prognosis.py +++ b/src/egon/data/datasets/society_prognosis.py @@ -96,7 +96,7 @@ def zensus_population(): {cfg['target']['population_prognosis']['table']}""" ) # Scale to pogosis values from demandregio - for year in [2035, 2050]: + for year in [2035, 2037, 2050]: # Input: dataset on population prognosis on district-level (NUTS3) prognosis = db.select_dataframe( f"""SELECT nuts3, population @@ -203,7 +203,7 @@ def zensus_household(): ) # Apply prognosis function - for year in [2035, 2050]: + for year in [2035, 2037, 2050]: print(f"start prognosis for year {year}") # Input: dataset on household prognosis on district-level (NUTS3) prognosis_nuts3 = db.select_dataframe( diff --git a/src/egon/data/datasets/storages/__init__.py b/src/egon/data/datasets/storages/__init__.py index 5306f051f..6b068b387 100755 --- a/src/egon/data/datasets/storages/__init__.py +++ b/src/egon/data/datasets/storages/__init__.py @@ -86,7 +86,8 @@ def create_tables(): def allocate_pumped_hydro(scn, export=True): - """Allocates pumped_hydro plants for eGon2035 and scenario2019 scenarios + """Allocates pumped_hydro plants for nep2037_2025, eGon2035 and scenario2019 + scenarios and either exports results to data base or returns as a dataframe Parameters @@ -99,7 +100,7 @@ def allocate_pumped_hydro(scn, export=True): Returns ------- power_plants : pandas.DataFrame - List of pumped hydro plants in 'eGon2035' and 'scenario2019' scenarios + List of pumped hydro plants in 'eGon2035', 'nep2037_2025' and 'scenario2019' scenarios """ carrier = "pumped_hydro" @@ -191,7 +192,7 @@ def allocate_pumped_hydro(scn, export=True): if nep.elec_capacity.sum() > 0: # Get location using geolocator and city information - located, unmatched = get_location(nep) + located, unmatched = get_location(nep, scn) # Bring both dataframes together matched = pd.concat( @@ -616,7 +617,8 @@ def allocate_pumped_hydro_eGon100RE(): def home_batteries_per_scenario(scenario): """Allocates home batteries which define a lower boundary for extendable battery storage units. The overall installed capacity is taken from NEP - for eGon2035 scenario. The spatial distribution of installed battery + for eGon2035 and nep2037_2025 scenario. The spatial distribution of installed + battery capacities is based on the installed pv rooftop capacity. Parameters @@ -631,7 +633,24 @@ def home_batteries_per_scenario(scenario): cfg = config.datasets()["storages"] dataset = config.settings()["egon-data"]["--dataset-boundary"] - if scenario == "eGon2035": + if scenario == "nep2037_2025": + target_file = ( + Path(".") + / "data_bundle_egon_data" + / "nep2037_version2025" + / cfg["sources"]["nep_capacities"] + ) + + capacities_nep = pd.read_excel( + target_file, + sheet_name="1.Entwurf_NEP2037_V2025", + index_col="Unnamed: 0", + ) + + # Select target value in MW + target = capacities_nep.Summe["PV-Batteriespeicher"] * 1000 + + elif scenario == "eGon2035": target_file = ( Path(".") / "data_bundle_egon_data" @@ -680,7 +699,7 @@ def home_batteries_per_scenario(scenario): battery["carrier"] = "home_battery" battery["scenario"] = scenario - if (scenario == "eGon2035") | ("status" in scenario): + if (scenario == "eGon2035" or scenario == "nep2037_2025") | ("status" in scenario): source = "NEP" else: @@ -712,7 +731,9 @@ def allocate_pv_home_batteries_to_grids(): def allocate_pumped_hydro_scn(): for scn in config.settings()["egon-data"]["--scenarios"]: - if scn == "eGon2035": + if scn == "nep2037_2025": + allocate_pumped_hydro(scn="nep2037_2025") + elif scn == "eGon2035": allocate_pumped_hydro(scn="eGon2035") elif scn == "eGon100RE": allocate_pumped_hydro_eGon100RE() diff --git a/src/egon/data/datasets/storages/pumped_hydro.py b/src/egon/data/datasets/storages/pumped_hydro.py index c9f2bace3..da94fbd19 100755 --- a/src/egon/data/datasets/storages/pumped_hydro.py +++ b/src/egon/data/datasets/storages/pumped_hydro.py @@ -49,6 +49,21 @@ def select_nep_pumped_hydro(scn): nep_ph.rename( columns={"c2035_capacity": "elec_capacity"}, inplace=True ) + elif scn == "nep2037_2025": + # Select plants with geolocation from list of conventional power plants + nep_ph = db.select_dataframe( + f""" + SELECT bnetza_id, name, carrier, postcode, capacity, city, + federal_state, c2037_capacity + FROM {cfg['sources']['nep_conv']} + WHERE carrier = '{carrier}' + AND c2037_capacity > 0 + AND postcode != 'None'; + """ + ) + nep_ph.rename( + columns={"c2037_capacity": "elec_capacity"}, inplace=True + ) elif "status" in scn: # Select plants with geolocation from list of conventional power plants year = int(scn[-4:]) @@ -292,7 +307,7 @@ def match_storage_units( return matched, mastr, nep -def get_location(unmatched): +def get_location(unmatched, scn): """Gets a geolocation for units which couldn't be matched using MaStR data. Uses geolocator and the city name from NEP data to create longitude and latitude for a list of unmatched units. @@ -341,7 +356,7 @@ def get_location(unmatched): located = located.rename( columns={"elec_capacity": "el_capacity", "bnetza_id": "MaStRNummer"} ) - located["scenario"] = "eGon2035" + located["scenario"] = scn located["source"] = "NEP power plants geolocated using city" unmatched = unmatched.drop(located.index.values) diff --git a/src/egon/data/datasets_original.yml b/src/egon/data/datasets_original.yml index beb79027d..6bea6eac2 100755 --- a/src/egon/data/datasets_original.yml +++ b/src/egon/data/datasets_original.yml @@ -136,6 +136,7 @@ demandregio_cts_ind_demand: schema: 'boundaries' table: 'vg250_krs' new_consumers_2035: 'new_largescale_consumers_nep.csv' + new_consumers_2037_2025: 'new_largescale_consumers_nep.csv' new_consumers_2050: "pes-demand-today": "industrial_energy_demand_per_country_today.csv" "pes-production-tomorrow": "industrial_production_per_country_tomorrow_2050.csv" @@ -162,7 +163,7 @@ electrical_demands_households: demandregio: schema: 'demand' table: 'egon_demandregio_hh' - scenarios: ["eGon2035", "eGon100RE"] + scenarios: ["eGon2035", "nep2037_2025", "eGon100RE"] population_prognosis_zensus: schema: 'society' table: 'egon_population_prognosis' @@ -179,7 +180,7 @@ electrical_demands_cts: demandregio: schema: 'demand' table: 'egon_demandregio_cts_ind' - scenarios: ["eGon2035", "eGon100RE"] + scenarios: ["eGon2035", "nep2037_2025", "eGon100RE"] demandregio_wz: schema: 'demand' table: 'egon_demandregio_wz' @@ -268,6 +269,9 @@ scenario_input: eGon2035: capacities: "NEP2035_V2021_scnC2035.xlsx" list_conv_pp: "Kraftwerksliste_NEP_2021_konv.csv" + nep2037_2025: + capacities: "NEP2037_V2025_scnC2037.xlsx" + list_conv_pp: "Kraftwerksliste_NEP_2021_konv.csv" eGon100RE: capacities: "nodal_capacities.csv" boundaries: @@ -372,6 +376,7 @@ power_plants: buses_data: "osmtgmod_results.bus_data" power_plants: 'supply.egon_power_plants' nep_2035: "NEP2035_V2021_scnC2035.xlsx" + nep_2037_2025: "NEP2037_V2025_scnC2037.xlsx" target: table: 'egon_power_plants' schema: 'supply' @@ -386,6 +391,7 @@ storages: ehv_voronoi: "grid.egon_ehv_substation_voronoi" nep_conv: "supply.egon_nep_2021_conventional_powerplants" nep_capacities: "NEP2035_V2021_scnC2035.xlsx" + nep_capacities: "NEP2037_V2025_scnC2037.xlsx" generators: "grid.egon_etrago_generator" bus: "grid.egon_etrago_bus" target: @@ -692,7 +698,7 @@ distributed_industrial_demand: demandregio: schema: 'demand' table: 'egon_demandregio_cts_ind' - scenarios: ["eGon2021", "eGon2035", "eGon100RE"] + scenarios: ["eGon2021", "eGon2035", "nep2037_2025", "eGon100RE"] wz: schema: 'demand' table: 'egon_demandregio_wz' @@ -1110,6 +1116,9 @@ emobility_mit: eGon2035: file: "eGon2035_RS7_min2k_2022-06-01_175429_simbev_run.tar.gz" file_metadata: "metadata_simbev_run.json" + nep2037_2025: + file: "eGon2035_RS7_min2k_2022-06-01_175429_simbev_run.tar.gz" + file_metadata: "metadata_simbev_run.json" eGon100RE: file: "eGon100RE_RS7_min2k_2022-06-01_175444_simbev_run.tar.gz" file_metadata: "metadata_simbev_run.json" @@ -1117,12 +1126,14 @@ emobility_mit: # used scenario variation (available scenarios see parameters.py) variation: eGon2035: "NEP C 2035" + nep2037_2025: "NEP C 2037" eGon100RE: "Reference 2050" # name of low-flex scenario lowflex: create_lowflex_scenario: True names: eGon2035: "eGon2035_lowflex" + nep2037_2025: "nep2037_2025_lowflex" eGon100RE: "eGon100RE_lowflex" model_timeseries: @@ -1194,6 +1205,7 @@ mobility_hgv: fcev_share: 1. scenarios: - "eGon2035" + - "nep2037_2025" - "eGon100RE" carrier: "H2_hgv_load" energy_value_h2: 39.4 # kWh/kg @@ -1208,6 +1220,14 @@ mobility_hgv: # hgv_mean_mileage = 100000 # Total mileage eGon2035: 10000000000 + # NEP data + # https://www.netzentwicklungsplan.de/sites/default/files/paragraphs-files/NEP_2035_V2021_2_Entwurf_Teil1.pdf + # total amount of HGVs - Scenario C 2037 + # hgv_amount = 100000 + # HGV traffic annual mileage per vehicle + # hgv_mean_mileage = 100000 + # Total mileage + nep2037_2025: 10000000000 # Langfristszenarien # https://www.langfristszenarien.de/enertile-explorer-wAssets/docs/LFS3_Langbericht_Verkehr_final.pdf#page=17 eGon100RE: 40000000000 @@ -1216,6 +1236,7 @@ home_batteries: constants: scenarios: - "eGon2035" + - "nep2037_2025" - "eGon100RE" # Mean ratio between the storage capacity and the power of the pv rooftop system cbat_ppv_ratio: 1 diff --git a/src/egon/data/metadata/demand.egon_building_electricity_peak_loads.json b/src/egon/data/metadata/demand.egon_building_electricity_peak_loads.json index 19d7b55cc..416af0772 100644 --- a/src/egon/data/metadata/demand.egon_building_electricity_peak_loads.json +++ b/src/egon/data/metadata/demand.egon_building_electricity_peak_loads.json @@ -146,7 +146,7 @@ }, { "name": "scenario", - "description": "Scenario (eGon100RE, eGon2035)", + "description": "Scenario (eGon100RE, nep2037_2025, eGon2035)", "type": "character varying", "unit": "none" }, diff --git a/src/egon/data/metadata/demand.egon_building_heat_peak_loads.json b/src/egon/data/metadata/demand.egon_building_heat_peak_loads.json index 17f7dfee0..48a8374df 100644 --- a/src/egon/data/metadata/demand.egon_building_heat_peak_loads.json +++ b/src/egon/data/metadata/demand.egon_building_heat_peak_loads.json @@ -160,7 +160,7 @@ }, { "name": "scenario", - "description": "Scenario (eGon100RE, eGon2035)", + "description": "Scenario (eGon100RE, nep2037_2025, eGon2035)", "type": "character varying", "unit": "none" }, diff --git a/src/egon/data/metadata/demand.egon_cts_electricity_demand_building_share.json b/src/egon/data/metadata/demand.egon_cts_electricity_demand_building_share.json index c7a60b34c..013f6d106 100644 --- a/src/egon/data/metadata/demand.egon_cts_electricity_demand_building_share.json +++ b/src/egon/data/metadata/demand.egon_cts_electricity_demand_building_share.json @@ -146,7 +146,7 @@ }, { "name": "scenario", - "description": "Scenario (eGon100RE, eGon2035) (reference: demand.egon_etrago_electricity_cts.scn_name)", + "description": "Scenario (eGon100RE, nep2037_2025, eGon2035) (reference: demand.egon_etrago_electricity_cts.scn_name)", "type": "character varying", "unit": "none" }, diff --git a/src/egon/data/metadata/demand.egon_cts_heat_demand_building_share.json b/src/egon/data/metadata/demand.egon_cts_heat_demand_building_share.json index 0f9228270..e934c04dc 100644 --- a/src/egon/data/metadata/demand.egon_cts_heat_demand_building_share.json +++ b/src/egon/data/metadata/demand.egon_cts_heat_demand_building_share.json @@ -132,7 +132,7 @@ }, { "name": "scenario", - "description": "Scenario (eGon100RE, eGon2035)", + "description": "Scenario (eGon100RE, nep2037_2025, eGon2035)", "type": "character varying", "unit": "none" }, diff --git a/src/egon/data/metadata/demand.egon_household_electricity_profile_in_census_cell.json b/src/egon/data/metadata/demand.egon_household_electricity_profile_in_census_cell.json index 3e0696b84..0cc3fd86e 100644 --- a/src/egon/data/metadata/demand.egon_household_electricity_profile_in_census_cell.json +++ b/src/egon/data/metadata/demand.egon_household_electricity_profile_in_census_cell.json @@ -2,7 +2,7 @@ "name": "demand.egon_household_electricity_profile_in_census_cell", "title": "eGon household electricity profiles in census cells", "id": "WILL_BE_SET_AT_PUBLICATION", - "description": "Mapping table for residential electricity profiles to census cell including scaling factors for two scenarios (eGon2035, eGon100RE).", + "description": "Mapping table for residential electricity profiles to census cell including scaling factors for two scenarios (eGon2035, nep2037_2025, eGon100RE).", "language": [ "en-EN", "de-DE" @@ -179,6 +179,12 @@ "type": "double precision", "unit": "none" }, + { + "name": "factor_2037_2025", + "description": "Scaling factor for all profiles in the respective census cell for scenario nep2037_2025", + "type": "double precision", + "unit": "none" + }, { "name": "factor_2050", "description": "Scaling factor for all profiles in the respective census cell for scenario eGon100RE",