diff --git a/rules/build_sector.smk b/rules/build_sector.smk index f95731b7..ec7fcd0c 100644 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -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"), diff --git a/rules/solve_myopic.smk b/rules/solve_myopic.smk index 8d7fa284..f868a997 100644 --- a/rules/solve_myopic.smk +++ b/rules/solve_myopic.smk @@ -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", diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 212ae8af..7e9dd082 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -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, diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index 24ba86bb..73c3d1a0 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -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) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 87aa90fa..f303c10c 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -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)