use JRC-IDEES thermal energy service instead of FE for buildings

This change consists of:

- reading the final energy (FE) to thermal energy service (TES)
  efficiency for each each country, seperately for gas and oil (this
  efficiency represents e.g. the losses in older non-condensing
  boilers)

- using TES instead of FE for the n.loads, so that it is pure energy
  service instead of including losses in legacy equipment

- using the stored efficiencies for baseyear equipment in
  add_existing_baseyear

In the baseyear (e.g. 2020) this should have no effect on FE, since
the reduction to TES is conpensated by the lower efficiencies of
existing equipment.

In the future (e.g. 2050) this leads to a reduction in heating demand,
since new equipment is more efficient than existing.
This commit is contained in:
Tom Brown 2024-09-02 19:32:13 +02:00
parent e815c36b5c
commit 0bcb215597
5 changed files with 135 additions and 5 deletions

View File

@ -305,6 +305,7 @@ rule build_energy_totals:
co2_name=resources("co2_totals.csv"),
transport_name=resources("transport_data.csv"),
district_heat_share=resources("district_heat_share.csv"),
heating_efficiencies=resources("heating_efficiencies.csv"),
threads: 16
resources:
mem_mb=10000,
@ -1037,6 +1038,7 @@ rule prepare_sector_network:
district_heat_share=resources(
"district_heat_share_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
),
heating_efficiencies=resources("heating_efficiencies.csv"),
temp_soil_total=resources("temp_soil_total_elec_s{simpl}_{clusters}.nc"),
temp_air_total=resources("temp_air_total_elec_s{simpl}_{clusters}.nc"),
cop_profiles=resources("cop_profiles_elec_s{simpl}_{clusters}.nc"),

View File

@ -26,6 +26,7 @@ rule add_existing_baseyear:
existing_heating_distribution=resources(
"existing_heating_distribution_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
),
heating_efficiencies=resources("heating_efficiencies.csv"),
output:
RESULTS
+ "prenetworks-brownfield/elec_s{simpl}_{clusters}_l{ll}_{opts}_{sector_opts}_{planning_horizons}.nc",

View File

@ -546,6 +546,14 @@ def add_heating_capacities_installed_before_baseyear(
lifetime=costs.at[heat_system.resistive_heater_costs_name, "lifetime"],
)
if "residential" in heat_system.value:
efficiency = nodes.str[:2].map(heating_efficiencies["gas residential space efficiency"])
elif "services" in heat_system.value:
efficiency = nodes.str[:2].map(heating_efficiencies["gas services space efficiency"])
else:
#default used for urban central, since no info on district heating boilers
efficiency = costs.at[heat_system.gas_boiler_costs_name, "efficiency"]
n.madd(
"Link",
nodes,
@ -554,7 +562,7 @@ def add_heating_capacities_installed_before_baseyear(
bus1=nodes + " " + heat_system.value + " heat",
bus2="co2 atmosphere",
carrier=heat_system.value + " gas boiler",
efficiency=costs.at[heat_system.gas_boiler_costs_name, "efficiency"],
efficiency=efficiency,
efficiency2=costs.at["gas", "CO2 intensity"],
capital_cost=(
costs.at[heat_system.gas_boiler_costs_name, "efficiency"]
@ -569,6 +577,14 @@ def add_heating_capacities_installed_before_baseyear(
lifetime=costs.at[heat_system.gas_boiler_costs_name, "lifetime"],
)
if "residential" in heat_system.value:
efficiency = nodes.str[:2].map(heating_efficiencies["oil residential space efficiency"])
elif "services" in heat_system.value:
efficiency = nodes.str[:2].map(heating_efficiencies["oil services space efficiency"])
else:
#default used for urban central, since no info on district heating boilers
efficiency = costs.at[heat_system.oil_boiler_costs_name, "efficiency"]
n.madd(
"Link",
nodes,
@ -577,7 +593,7 @@ def add_heating_capacities_installed_before_baseyear(
bus1=nodes + " " + heat_system.value + " heat",
bus2="co2 atmosphere",
carrier=heat_system.value + " oil boiler",
efficiency=costs.at[heat_system.oil_boiler_costs_name, "efficiency"],
efficiency=efficiency,
efficiency2=costs.at["oil", "CO2 intensity"],
capital_cost=costs.at[heat_system.oil_boiler_costs_name, "efficiency"]
* costs.at[heat_system.oil_boiler_costs_name, "fixed"],
@ -660,6 +676,11 @@ if __name__ == "__main__":
if options["heating"]:
#one could use baseyear here instead (but dangerous if no data)
heating_efficiencies = (
pd.read_csv(snakemake.input.heating_efficiencies, index_col=[0,1])
).swaplevel().loc[int(snakemake.config["energy"]["energy_totals_year"])]
add_heating_capacities_installed_before_baseyear(
n=n,
baseyear=baseyear,

View File

@ -367,6 +367,24 @@ def idees_per_country(ct: str, base_dir: str) -> pd.DataFrame:
assert df.index[43] == "Thermal uses"
ct_totals["thermal uses residential"] = df.iloc[43]
df = pd.read_excel(fn_residential, "RES_hh_eff", index_col=0)
ct_totals["total residential space efficiency"] = df.loc["Space heating"]
assert df.index[5] == "Diesel oil"
ct_totals["oil residential space efficiency"] = df.iloc[5]
assert df.index[6] == "Natural gas"
ct_totals["gas residential space efficiency"] = df.iloc[6]
ct_totals["total residential water efficiency"] = df.loc["Water heating"]
assert df.index[18] == "Diesel oil"
ct_totals["oil residential water efficiency"] = df.iloc[18]
assert df.index[19] == "Natural gas"
ct_totals["gas residential water efficiency"] = df.iloc[19]
# services
df = pd.read_excel(fn_tertiary, "SER_hh_fec", index_col=0)
@ -400,6 +418,24 @@ def idees_per_country(ct: str, base_dir: str) -> pd.DataFrame:
assert df.index[46] == "Thermal uses"
ct_totals["thermal uses services"] = df.iloc[46]
df = pd.read_excel(fn_tertiary, "SER_hh_eff", index_col=0)
ct_totals["total services space efficiency"] = df.loc["Space heating"]
assert df.index[5] == "Diesel oil"
ct_totals["oil services space efficiency"] = df.iloc[5]
assert df.index[7] == "Conventional gas heaters"
ct_totals["gas services space efficiency"] = df.iloc[7]
ct_totals["total services water efficiency"] = df.loc["Hot water"]
assert df.index[20] == "Diesel oil"
ct_totals["oil services water efficiency"] = df.iloc[20]
assert df.index[21] == "Natural gas"
ct_totals["gas services water efficiency"] = df.iloc[21]
# agriculture, forestry and fishing
start = "Detailed split of energy consumption (ktoe)"
@ -576,7 +612,9 @@ def build_idees(countries: List[str]) -> pd.DataFrame:
# efficiency kgoe/100km -> ktoe/100km so that after conversion TWh/100km
totals.loc[:, "passenger car efficiency"] /= 1e6
# convert ktoe to TWh
exclude = totals.columns.str.fullmatch("passenger cars")
exclude = totals.columns.str.fullmatch("passenger cars") \
^ totals.columns.str.fullmatch(".*space efficiency") \
^ totals.columns.str.fullmatch(".*water efficiency")
totals = totals.copy()
totals.loc[:, ~exclude] *= 11.63 / 1e3
@ -654,11 +692,16 @@ def build_energy_totals(
eurostat_countries = eurostat.index.unique(0)
eurostat_years = eurostat.index.unique(1)
to_drop = ["passenger cars", "passenger car efficiency"]
new_index = pd.MultiIndex.from_product(
[countries, eurostat_years], names=["country", "year"]
)
to_drop = idees.columns[idees.columns.str.contains("space efficiency")
^ idees.columns.str.contains("water efficiency")]
to_drop = to_drop.append(pd.Index(
["passenger cars", "passenger car efficiency"]
))
df = idees.reindex(new_index).drop(to_drop, axis=1)
in_eurostat = df.index.levels[0].intersection(eurostat_countries)
@ -1500,6 +1543,57 @@ def build_transformation_output_coke(eurostat, fn):
df = eurostat.loc[slicer, :].droplevel(level=[2, 3, 4, 5])
df.to_csv(fn)
def build_heating_efficiencies(
countries: List[str], idees: pd.DataFrame
) -> pd.DataFrame:
"""
Build heating efficiencies for a set of countries based on IDEES data.
Parameters
----------
countries : List[str]
List of country codes.
idees : pd.DataFrame
DataFrame with IDEES data.
Returns
-------
pd.DataFrame
DataFrame with heating efficiencies.
Notes
-----
- It fills missing data with average data.
"""
years = np.arange(2000, 2022)
cols = idees.columns[idees.columns.str.contains("space efficiency")
^ idees.columns.str.contains("water efficiency")]
logger.info(cols)
heating_efficiencies = pd.DataFrame(idees[cols])
new_index = pd.MultiIndex.from_product(
[countries, heating_efficiencies.index.unique(1)],
names=["country", "year"],
)
heating_efficiencies = heating_efficiencies.reindex(index=new_index)
for col in cols:
unstacked = heating_efficiencies[col].unstack()
fillvalue = unstacked.mean()
for ct in unstacked.index:
mask = unstacked.loc[ct].isna()
unstacked.loc[ct, mask] = fillvalue[mask]
heating_efficiencies[col] = unstacked.stack()
return heating_efficiencies
# %%
if __name__ == "__main__":
@ -1556,3 +1650,6 @@ if __name__ == "__main__":
transport = build_transport_data(countries, population, idees)
transport.to_csv(snakemake.output.transport_name)
heating_efficiencies = build_heating_efficiencies(countries, idees)
heating_efficiencies.to_csv(snakemake.output.heating_efficiencies)

View File

@ -1806,9 +1806,14 @@ def build_heat_demand(n):
for sector, use in product(sectors, uses):
name = f"{sector} {use}"
#efficiency for final energy to thermal energy service
eff = pop_weighted_energy_totals.index.str[:2].map(
heating_efficiencies[f"total {sector} {use} efficiency"]
)
heat_demand[name] = (
heat_demand_shape[name] / heat_demand_shape[name].sum()
).multiply(pop_weighted_energy_totals[f"total {sector} {use}"]) * 1e6
).multiply(pop_weighted_energy_totals[f"total {sector} {use}"]*eff) * 1e6
electric_heat_supply[name] = (
heat_demand_shape[name] / heat_demand_shape[name].sum()
).multiply(pop_weighted_energy_totals[f"electricity {sector} {use}"]) * 1e6
@ -4282,6 +4287,10 @@ if __name__ == "__main__":
)
pop_weighted_energy_totals.update(pop_weighted_heat_totals)
heating_efficiencies = (
pd.read_csv(snakemake.input.heating_efficiencies, index_col=[0,1])
).swaplevel().loc[int(snakemake.config["energy"]["energy_totals_year"])]
patch_electricity_network(n)
spatial = define_spatial(pop_layout.index, options)