diff --git a/doc/release_notes.rst b/doc/release_notes.rst index dc1a9dd1..56fee0d8 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -28,6 +28,24 @@ Upcoming Release * Cluster residential and services heat buses by default. Can be disabled with ``cluster_heat_buses: false``. +* Bugfix: Do not reduce district heat share when building population-weighted + energy statistics. Previously the district heating share was being multiplied + by the population weighting, reducing the DH share with multiple nodes. + +* Move building of daily heat profile to its own rule + :mod:`build_hourly_heat_demand` from :mod:`prepare_sector_network`. + +* In :mod:`build_energy_totals`, district heating shares are now reported in a + separate file. + +* Move calculation of district heating share to its own rule + :mod:`build_district_heat_share`. + +* Move building of distribution of existing heating to own rule + :mod:`build_existing_heating_distribution`. This makes the distribution of + existing heating to urban/rural, residential/services and spatially more + transparent. + PyPSA-Eur 0.9.0 (5th January 2024) ================================== diff --git a/doc/sector.rst b/doc/sector.rst index 303e7ed2..411bfd57 100644 --- a/doc/sector.rst +++ b/doc/sector.rst @@ -20,6 +20,12 @@ Rule ``add_existing_baseyear`` .. automodule:: add_existing_baseyear +Rule ``build_existing_heating_distribution`` +============================================================================== + +.. automodule:: build_existing_heating_distribution + + Rule ``build_ammonia_production`` ============================================================================== @@ -60,10 +66,20 @@ Rule ``build_gas_network`` .. automodule:: build_gas_network -Rule ``build_heat_demand`` +Rule ``build_daily_heat_demand`` ============================================================================== -.. automodule:: build_heat_demand +.. automodule:: build_daily_heat_demand + +Rule ``build_hourly_heat_demand`` +============================================================================== + +.. automodule:: build_hourly_heat_demand + +Rule ``build_district_heat_share`` +============================================================================== + +.. automodule:: build_district_heat_share Rule ``build_industrial_distribution_key`` ============================================================================== diff --git a/rules/solve_overnight.smk b/rules/solve_overnight.smk index 39778162..47c86410 100644 --- a/rules/solve_overnight.smk +++ b/rules/solve_overnight.smk @@ -28,9 +28,10 @@ rule solve_sector_network: + "logs/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}_memory.log", python=RESULTS + "logs/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}_python.log", - threads: config["solving"]["solver_options"][config["solving"]["solver"]["options"]].get( - "threads", 4 -) + threads: + config["solving"]["solver_options"][ + config["solving"]["solver"]["options"] + ].get("threads", 4) resources: mem_mb=config["solving"]["mem"], walltime=config["solving"].get("walltime", "12:00:00"), diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 01d54cc2..d61ece85 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -451,7 +451,7 @@ def add_heating_capacities_installed_before_baseyear( efficiency=efficiency, capital_cost=costs.at[costs_name, "efficiency"] * costs.at[costs_name, "fixed"], - p_nom=existing_heating[(name, f"{heat_pump_type} heat pump")][nodes] * ratio / costs.at[costs_name, "efficiency"], + p_nom=existing_heating.loc[nodes, (name, f"{heat_pump_type} heat pump")] * ratio / costs.at[costs_name, "efficiency"], build_year=int(grouping_year), lifetime=costs.at[costs_name, "lifetime"], ) @@ -470,7 +470,7 @@ def add_heating_capacities_installed_before_baseyear( * costs.at[f"{name_type} resistive heater", "fixed"] ), p_nom=( - existing_heating[(name, "resistive heater")][nodes] + existing_heating.loc[nodes, (name, "resistive heater")] * ratio / costs.at[f"{name_type} resistive heater", "efficiency"] ), @@ -493,7 +493,7 @@ def add_heating_capacities_installed_before_baseyear( * costs.at[f"{name_type} gas boiler", "fixed"] ), p_nom=( - existing_heating[(name, "gas boiler")][nodes] + existing_heating.loc[nodes, (name, "gas boiler")] * ratio / costs.at[f"{name_type} gas boiler", "efficiency"] ), @@ -514,7 +514,7 @@ def add_heating_capacities_installed_before_baseyear( capital_cost=costs.at["decentral oil boiler", "efficiency"] * costs.at["decentral oil boiler", "fixed"], p_nom= ( - existing_heating[(name, "oil boiler")][nodes] + existing_heating.loc[nodes, (name, "oil boiler")] * ratio / costs.at["decentral oil boiler", "efficiency"]), build_year=int(grouping_year), diff --git a/scripts/build_daily_heat_demand.py b/scripts/build_daily_heat_demand.py index b983f125..e334b1b3 100644 --- a/scripts/build_daily_heat_demand.py +++ b/scripts/build_daily_heat_demand.py @@ -18,7 +18,8 @@ if __name__ == "__main__": from _helpers import mock_snakemake snakemake = mock_snakemake( - "build_heat_demands", + "build_daily_heat_demands", + scope="total", simpl="", clusters=48, ) diff --git a/scripts/build_district_heat_share.py b/scripts/build_district_heat_share.py index d521214d..996ed861 100644 --- a/scripts/build_district_heat_share.py +++ b/scripts/build_district_heat_share.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# SPDX-FileCopyrightText: : 2020-2023 The PyPSA-Eur Authors +# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors # # SPDX-License-Identifier: MIT """ @@ -21,9 +21,10 @@ if __name__ == "__main__": from _helpers import mock_snakemake snakemake = mock_snakemake( - "build_heat_demands", + "build_district_heat_share", simpl="", clusters=48, + planning_horizons="2050", ) investment_year = int(snakemake.wildcards.planning_horizons[-4:]) @@ -68,10 +69,10 @@ if __name__ == "__main__": f"resulting in new average share of {dist_fraction_node.mean():.2%}" ) - df = pd.DataFrame(dtype=float) - - df["original district heat share"] = district_heat_share - df["district fraction of node"] = dist_fraction_node - df["urban fraction"] = urban_fraction + df = pd.DataFrame({ + "original district heat share": district_heat_share, + "district fraction of node": dist_fraction_node, + "urban fraction": urban_fraction + }, dtype=float) df.to_csv(snakemake.output.district_heat_share) diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index 306caf4d..08d5bef5 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -583,10 +583,10 @@ def build_district_heat_share(countries, idees): # Missing district heating share dh_share = pd.read_csv( snakemake.input.district_heat_share, index_col=0, usecols=[0, 1] - ) + ).div(100).squeeze() # make conservative assumption and take minimum from both data sets district_heat_share = pd.concat( - [district_heat_share, dh_share.reindex(index=district_heat_share.index) / 100], axis=1 + [district_heat_share, dh_share.reindex_like(district_heat_share)], axis=1 ).min(axis=1) district_heat_share.name = "district heat share" diff --git a/scripts/build_existing_heating_distribution.py b/scripts/build_existing_heating_distribution.py index fe282d39..443c5baa 100644 --- a/scripts/build_existing_heating_distribution.py +++ b/scripts/build_existing_heating_distribution.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# SPDX-FileCopyrightText: : 2020-2023 The PyPSA-Eur Authors +# SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors # # SPDX-License-Identifier: MIT """ @@ -7,8 +7,6 @@ Builds table of existing heat generation capacities for initial planning horizon. """ import pandas as pd -import sys -from pypsa.descriptors import Dict import numpy as np import country_converter as coco @@ -17,19 +15,13 @@ cc = coco.CountryConverter() def build_existing_heating(): # retrieve existing heating capacities - techs = [ - "gas boiler", - "oil boiler", - "resistive heater", - "air heat pump", - "ground heat pump", - ] existing_heating = pd.read_csv(snakemake.input.existing_heating, index_col=0, header=0) - # data for Albania, Montenegro and Macedonia not included in database existing_heating.loc["Albania"] = np.nan + # data for Albania, Montenegro and Macedonia not included in database + existing_heating.loc["Albania"] = np.nan existing_heating.loc["Montenegro"] = np.nan existing_heating.loc["Macedonia"] = np.nan @@ -104,5 +96,14 @@ def build_existing_heating(): if __name__ == "__main__": + if "snakemake" not in globals(): + from _helpers import mock_snakemake + + snakemake = mock_snakemake( + "build_existing_heating_distribution", + simpl="", + clusters=48, + planning_horizons=2050, + ) build_existing_heating() diff --git a/scripts/build_hourly_heat_demand.py b/scripts/build_hourly_heat_demand.py index 94ad7266..2d1dee54 100644 --- a/scripts/build_hourly_heat_demand.py +++ b/scripts/build_hourly_heat_demand.py @@ -6,19 +6,19 @@ Build hourly heat demand time series from daily ones. """ -import pandas as pd -import xarray as xr -from _helpers import generate_periodic_profiles, update_config_with_sector_opts from itertools import product - +import pandas as pd +import xarray as xr +from _helpers import generate_periodic_profiles if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake snakemake = mock_snakemake( - "build_heat_demands", + "build_hourly_heat_demands", + scope="total", simpl="", clusters=48, ) @@ -58,12 +58,6 @@ if __name__ == "__main__": heat_demand.index.name="snapshots" - print(heat_demand) - - print(heat_demand.stack()) - - ds = heat_demand.stack().to_xarray()#xr.Dataset.from_dataframe(heat_demand) - - print(ds) + ds = heat_demand.stack().to_xarray() ds.to_netcdf(snakemake.output.heat_demand) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0bf9848b..241f3c30 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1654,7 +1654,7 @@ def build_heat_demand(n): heat_demand[name] = ( heat_demand_shape[name] / heat_demand_shape[name].sum() ).multiply(pop_weighted_energy_totals[f"total {sector} {use}"]) * 1e6 - electric_heat_supply[f"{sector} {use}"] = ( + electric_heat_supply[name] = ( heat_demand_shape[name] / heat_demand_shape[name].sum() ).multiply(pop_weighted_energy_totals[f"electricity {sector} {use}"]) * 1e6