Merge branch 'master' into blast-furnace-with-cc

This commit is contained in:
Fabian Neumann 2023-08-23 09:37:07 +02:00 committed by GitHub
commit 8cdba1c8e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 82 additions and 24 deletions

View File

@ -14,6 +14,11 @@ Upcoming Release
* Add option for carbon capture in integrated steelworks. * Add option for carbon capture in integrated steelworks.
* For industry distribution, use EPRTR as fallback if ETS data is not available.
* The minimum capacity for renewable generators when using the myopic option has been fixed.
PyPSA-Eur 0.8.1 (27th July 2023) PyPSA-Eur 0.8.1 (27th July 2023)
================================ ================================

View File

@ -106,6 +106,8 @@ rule plot_summary:
countries=config["countries"], countries=config["countries"],
planning_horizons=config["scenario"]["planning_horizons"], planning_horizons=config["scenario"]["planning_horizons"],
sector_opts=config["scenario"]["sector_opts"], sector_opts=config["scenario"]["sector_opts"],
emissions_scope=config["energy"]["emissions"],
eurostat_report_year=config["energy"]["eurostat_report_year"],
plotting=config["plotting"], plotting=config["plotting"],
RDIR=RDIR, RDIR=RDIR,
input: input:
@ -113,6 +115,7 @@ rule plot_summary:
energy=RESULTS + "csvs/energy.csv", energy=RESULTS + "csvs/energy.csv",
balances=RESULTS + "csvs/supply_energy.csv", balances=RESULTS + "csvs/supply_energy.csv",
eurostat=input_eurostat, eurostat=input_eurostat,
co2="data/eea/UNFCCC_v23.csv",
output: output:
costs=RESULTS + "graphs/costs.pdf", costs=RESULTS + "graphs/costs.pdf",
energy=RESULTS + "graphs/energy.pdf", energy=RESULTS + "graphs/energy.pdf",

View File

@ -165,7 +165,7 @@ def sanitize_carriers(n, config):
nice_names = ( nice_names = (
pd.Series(config["plotting"]["nice_names"]) pd.Series(config["plotting"]["nice_names"])
.reindex(carrier_i) .reindex(carrier_i)
.fillna(carrier_i.to_series().str.title()) .fillna(carrier_i.to_series())
) )
n.carriers["nice_name"] = n.carriers.nice_name.where( n.carriers["nice_name"] = n.carriers.nice_name.where(
n.carriers.nice_name != "", nice_names n.carriers.nice_name != "", nice_names
@ -358,12 +358,11 @@ def update_transmission_costs(n, costs, length_factor=1.0):
def attach_wind_and_solar( def attach_wind_and_solar(
n, costs, input_profiles, carriers, extendable_carriers, line_length_factor=1 n, costs, input_profiles, carriers, extendable_carriers, line_length_factor=1
): ):
add_missing_carriers(n, carriers)
for car in carriers: for car in carriers:
if car == "hydro": if car == "hydro":
continue continue
n.add("Carrier", car)
with xr.open_dataset(getattr(input_profiles, "profile_" + car)) as ds: with xr.open_dataset(getattr(input_profiles, "profile_" + car)) as ds:
if ds.indexes["bus"].empty: if ds.indexes["bus"].empty:
continue continue

View File

@ -435,15 +435,20 @@ def add_heating_capacities_installed_before_baseyear(
# split existing capacities between residential and services # split existing capacities between residential and services
# proportional to energy demand # proportional to energy demand
p_set_sum = n.loads_t.p_set.sum()
ratio_residential = pd.Series( ratio_residential = pd.Series(
[ [
( (
n.loads_t.p_set.sum()[f"{node} residential rural heat"] p_set_sum[f"{node} residential rural heat"]
/ ( / (
n.loads_t.p_set.sum()[f"{node} residential rural heat"] p_set_sum[f"{node} residential rural heat"]
+ n.loads_t.p_set.sum()[f"{node} services rural heat"] + p_set_sum[f"{node} services rural heat"]
) )
) )
# if rural heating demand for one of the nodes doesn't exist,
# then columns were dropped before and heating demand share should be 0.0
if all(f"{node} {service} rural heat" in p_set_sum.index for service in ["residential", "services"])
else 0.
for node in nodal_df.index for node in nodal_df.index
], ],
index=nodal_df.index, index=nodal_df.index,

View File

@ -17,6 +17,8 @@ import geopandas as gpd
import pandas as pd import pandas as pd
from packaging.version import Version, parse from packaging.version import Version, parse
import country_converter as coco
cc = coco.CountryConverter()
def locate_missing_industrial_sites(df): def locate_missing_industrial_sites(df):
""" """
@ -93,6 +95,17 @@ def prepare_hotmaps_database(regions):
gdf.rename(columns={"index_right": "bus"}, inplace=True) gdf.rename(columns={"index_right": "bus"}, inplace=True)
gdf["country"] = gdf.bus.str[:2] gdf["country"] = gdf.bus.str[:2]
# the .sjoin can lead to duplicates if a geom is in two overlapping regions
if gdf.index.duplicated().any():
# get all duplicated entries
duplicated_i = gdf.index[gdf.index.duplicated()]
# convert from raw data country name to iso-2-code
code = cc.convert(gdf.loc[duplicated_i, "Country"], to="iso2")
# screen out malformed country allocation
gdf_filtered = gdf.loc[duplicated_i].query("country == @code")
# concat not duplicated and filtered gdf
gdf = pd.concat([gdf.drop(duplicated_i), gdf_filtered])
return gdf return gdf
@ -115,7 +128,9 @@ def build_nodal_distribution_key(hotmaps, regions, countries):
facilities = hotmaps.query("country == @country and Subsector == @sector") facilities = hotmaps.query("country == @country and Subsector == @sector")
if not facilities.empty: if not facilities.empty:
emissions = facilities["Emissions_ETS_2014"] emissions = facilities["Emissions_ETS_2014"].fillna(
hotmaps["Emissions_EPRTR_2014"]
)
if emissions.sum() == 0: if emissions.sum() == 0:
key = pd.Series(1 / len(facilities), facilities.index) key = pd.Series(1 / len(facilities), facilities.index)
else: else:

View File

@ -28,9 +28,7 @@ def allocate_sequestration_potential(
overlay["share"] = area(overlay) / overlay["area_sqkm"] overlay["share"] = area(overlay) / overlay["area_sqkm"]
adjust_cols = overlay.columns.difference({"name", "area_sqkm", "geometry", "share"}) adjust_cols = overlay.columns.difference({"name", "area_sqkm", "geometry", "share"})
overlay[adjust_cols] = overlay[adjust_cols].multiply(overlay["share"], axis=0) overlay[adjust_cols] = overlay[adjust_cols].multiply(overlay["share"], axis=0)
gdf_regions = overlay.groupby("name").sum() return overlay.dissolve("name", aggfunc="sum")[attr]
gdf_regions.drop(["area_sqkm", "share"], axis=1, inplace=True)
return gdf_regions.squeeze()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -711,5 +711,5 @@ if __name__ == "__main__":
if snakemake.params.foresight == "myopic": if snakemake.params.foresight == "myopic":
cumulative_cost = calculate_cumulative_cost() cumulative_cost = calculate_cumulative_cost()
cumulative_cost.to_csv( cumulative_cost.to_csv(
"results/" + snakemake.params.RDIR + "/csvs/cumulative_cost.csv" "results/" + snakemake.params.RDIR + "csvs/cumulative_cost.csv"
) )

View File

@ -387,6 +387,9 @@ def historical_emissions(countries):
countries.remove("GB") countries.remove("GB")
countries.append("UK") countries.append("UK")
# remove countries which are not included in eea historical emission dataset
countries_to_remove = {"AL", "BA", "ME", "MK", "RS"}
countries = list(set(countries) - countries_to_remove)
year = np.arange(1990, 2018).tolist() year = np.arange(1990, 2018).tolist()
idx = pd.IndexSlice idx = pd.IndexSlice
@ -457,9 +460,20 @@ def plot_carbon_budget_distribution(input_eurostat):
ax1.set_ylim([0, 5]) ax1.set_ylim([0, 5])
ax1.set_xlim([1990, snakemake.params.planning_horizons[-1] + 1]) ax1.set_xlim([1990, snakemake.params.planning_horizons[-1] + 1])
path_cb = "results/" + snakemake.params.RDIR + "/csvs/" path_cb = "results/" + snakemake.params.RDIR + "csvs/"
countries = snakemake.params.countries countries = snakemake.params.countries
e_1990 = co2_emissions_year(countries, input_eurostat, opts, year=1990) emissions_scope = snakemake.params.emissions_scope
report_year = snakemake.params.eurostat_report_year
input_co2 = snakemake.input.co2
e_1990 = co2_emissions_year(
countries,
input_eurostat,
opts,
emissions_scope,
report_year,
input_co2,
year=1990,
)
CO2_CAP = pd.read_csv(path_cb + "carbon_budget_distribution.csv", index_col=0) CO2_CAP = pd.read_csv(path_cb + "carbon_budget_distribution.csv", index_col=0)
ax1.plot(e_1990 * CO2_CAP[o], linewidth=3, color="dodgerblue", label=None) ax1.plot(e_1990 * CO2_CAP[o], linewidth=3, color="dodgerblue", label=None)
@ -535,7 +549,7 @@ def plot_carbon_budget_distribution(input_eurostat):
fancybox=True, fontsize=18, loc=(0.01, 0.01), facecolor="white", frameon=True fancybox=True, fontsize=18, loc=(0.01, 0.01), facecolor="white", frameon=True
) )
path_cb_plot = "results/" + snakemake.params.RDIR + "/graphs/" path_cb_plot = "results/" + snakemake.params.RDIR + "graphs/"
plt.savefig(path_cb_plot + "carbon_budget_plot.pdf", dpi=300) plt.savefig(path_cb_plot + "carbon_budget_plot.pdf", dpi=300)

View File

@ -193,17 +193,15 @@ def get(item, investment_year=None):
def co2_emissions_year( def co2_emissions_year(
countries, input_eurostat, opts, emissions_scope, report_year, year countries, input_eurostat, opts, emissions_scope, report_year, input_co2, year
): ):
""" """
Calculate CO2 emissions in one specific year (e.g. 1990 or 2018). Calculate CO2 emissions in one specific year (e.g. 1990 or 2018).
""" """
emissions_scope = snakemake.params.energy["emissions"] eea_co2 = build_eea_co2(input_co2, year, emissions_scope)
eea_co2 = build_eea_co2(snakemake.input.co2, year, emissions_scope)
# TODO: read Eurostat data from year > 2014 # TODO: read Eurostat data from year > 2014
# this only affects the estimation of CO2 emissions for BA, RS, AL, ME, MK # this only affects the estimation of CO2 emissions for BA, RS, AL, ME, MK
report_year = snakemake.params.energy["eurostat_report_year"]
if year > 2014: if year > 2014:
eurostat_co2 = build_eurostat_co2( eurostat_co2 = build_eurostat_co2(
input_eurostat, countries, report_year, year=2014 input_eurostat, countries, report_year, year=2014
@ -224,7 +222,7 @@ def co2_emissions_year(
# TODO: move to own rule with sector-opts wildcard? # TODO: move to own rule with sector-opts wildcard?
def build_carbon_budget(o, input_eurostat, fn, emissions_scope, report_year): def build_carbon_budget(o, input_eurostat, fn, emissions_scope, report_year, input_co2):
""" """
Distribute carbon budget following beta or exponential transition path. Distribute carbon budget following beta or exponential transition path.
""" """
@ -242,12 +240,24 @@ def build_carbon_budget(o, input_eurostat, fn, emissions_scope, report_year):
countries = snakemake.params.countries countries = snakemake.params.countries
e_1990 = co2_emissions_year( e_1990 = co2_emissions_year(
countries, input_eurostat, opts, emissions_scope, report_year, year=1990 countries,
input_eurostat,
opts,
emissions_scope,
report_year,
input_co2,
year=1990,
) )
# emissions at the beginning of the path (last year available 2018) # emissions at the beginning of the path (last year available 2018)
e_0 = co2_emissions_year( e_0 = co2_emissions_year(
countries, input_eurostat, opts, emissions_scope, report_year, year=2018 countries,
input_eurostat,
opts,
emissions_scope,
report_year,
input_co2,
year=2018,
) )
planning_horizons = snakemake.params.planning_horizons planning_horizons = snakemake.params.planning_horizons
@ -3458,12 +3468,18 @@ if __name__ == "__main__":
if "cb" not in o: if "cb" not in o:
continue continue
limit_type = "carbon budget" limit_type = "carbon budget"
fn = "results/" + snakemake.params.RDIR + "/csvs/carbon_budget_distribution.csv" fn = "results/" + snakemake.params.RDIR + "csvs/carbon_budget_distribution.csv"
if not os.path.exists(fn): if not os.path.exists(fn):
emissions_scope = snakemake.params.emissions_scope emissions_scope = snakemake.params.emissions_scope
report_year = snakemake.params.eurostat_report_year report_year = snakemake.params.eurostat_report_year
input_co2 = snakemake.input.co2
build_carbon_budget( build_carbon_budget(
o, snakemake.input.eurostat, fn, emissions_scope, report_year o,
snakemake.input.eurostat,
fn,
emissions_scope,
report_year,
input_co2,
) )
co2_cap = pd.read_csv(fn, index_col=0).squeeze() co2_cap = pd.read_csv(fn, index_col=0).squeeze()
limit = co2_cap.loc[investment_year] limit = co2_cap.loc[investment_year]

View File

@ -51,6 +51,9 @@ def _add_land_use_constraint(n):
# warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind' # warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind'
for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc"]: for carrier in ["solar", "onwind", "offwind-ac", "offwind-dc"]:
extendable_i = (n.generators.carrier == carrier) & n.generators.p_nom_extendable
n.generators.loc[extendable_i, "p_nom_min"] = 0
ext_i = (n.generators.carrier == carrier) & ~n.generators.p_nom_extendable ext_i = (n.generators.carrier == carrier) & ~n.generators.p_nom_extendable
existing = ( existing = (
n.generators.loc[ext_i, "p_nom"] n.generators.loc[ext_i, "p_nom"]