Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of unsustainable biomass potentials #1139

Merged
merged 33 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
16301e5
add columns to potential df defined by difference to eurostat
cpschau May 13, 2024
c50fc9f
add network components
cpschau May 16, 2024
f6b2557
add unsustainable bioliquids
cpschau May 17, 2024
4a81993
replaced stores by generators, still infeasible
cpschau Jun 7, 2024
26e771b
remove municipal waste
cpschau Jul 8, 2024
3246d0a
remove separate treatment of waste from biomass potential calculation
cpschau Jul 8, 2024
8f422aa
phase out unsustainble biomass potentials
cpschau Jul 8, 2024
1b761d0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 8, 2024
0b34283
phase-out unsustainable bioliquids
cpschau Jul 9, 2024
2dfe089
remove waste_incineration from build_sector rule
cpschau Jul 9, 2024
275f66c
multiple potential calculation for different planning horizons
cpschau Jul 12, 2024
7fc6fdc
raised costs of unsustainable solid biomass
cpschau Jul 12, 2024
82a9234
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 12, 2024
6d82ada
stores instead of generators
cpschau Jul 12, 2024
517db86
change snakemake inputs
cpschau Jul 23, 2024
5b1603a
add phas-eout to config
cpschau Jul 24, 2024
49d30ce
add techcolor for unsustainable bioliquids
cpschau Jul 24, 2024
e048e3e
add config parameter to disable inclusion of unsustainable bioenergy …
cpschau Jul 25, 2024
62173b6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 25, 2024
d5b0963
add biomass to params
cpschau Jul 26, 2024
7065903
Merge branch 'master' into unsus_biomass
cpschau Jul 26, 2024
13bb7ad
remove call of snakemake object in define_spatial
cpschau Jul 26, 2024
d4a890d
Quick resolve of review part 1 (config parameters, if-clause-reductio…
cpschau Aug 2, 2024
b900c01
Quick resolve of review part 2 (config table, helper function, fixed …
cpschau Aug 2, 2024
deaa6cc
Cast of planning_horizon parameter to int type after test run
cpschau Aug 2, 2024
49eac35
added JRC fuel costs for solid and liquid biofuels, BtL VOM
cpschau Aug 5, 2024
4fd1ec8
Merge branch 'master' into unsus_biomass
cpschau Aug 6, 2024
9b944d3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 6, 2024
07d456d
clean-up after master merge
cpschau Aug 6, 2024
01f0318
Merge branch 'master' into unsus_biomass
lisazeyen Aug 7, 2024
d64917f
adressed review (increase threads for build_eurostat, fixed e_max_pu …
cpschau Aug 7, 2024
81ded81
Merge branch 'master' into unsus_biomass
cpschau Aug 7, 2024
fe2975c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,23 @@ biomass:
- Sludge
municipal solid waste:
- Municipal waste
share_unsustainable_use_retained:
2020: 1
2025: 0.66
2030: 0.33
2035: 0
2040: 0
2045: 0
2050: 0
share_sustainable_potential_available:
2020: 0
2025: 0.33
2030: 0.66
2035: 1
2040: 1
2045: 1
2050: 1


# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#solar-thermal
solar_thermal:
Expand Down Expand Up @@ -722,7 +739,7 @@ industry:
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#costs
costs:
year: 2030
version: v0.9.0
cpschau marked this conversation as resolved.
Show resolved Hide resolved
version: "d0c646ad4561e2a82729dfb8c96e0fb5b29423b7"
social_discountrate: 0.02
fill_values:
FOM: 0
Expand Down Expand Up @@ -1040,6 +1057,7 @@ plotting:
services rural biomass boiler: '#c6cf98'
services urban decentral biomass boiler: '#dde5b5'
biomass to liquid: '#32CD32'
unsustainable bioliquids: '#32CD32'
electrobiofuels: 'red'
BioSNG: '#123456'
# power transmission
Expand Down
2 changes: 2 additions & 0 deletions doc/configtables/biomass.csv
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ classes ,,,
-- solid biomass,--,Array of biomass comodity,The comodity that are included as solid biomass
-- not included,--,Array of biomass comodity,The comodity that are not included as a biomass potential
-- biogas,--,Array of biomass comodity,The comodity that are included as biogas
share_unsustainable_use_retained,--,Dictionary with planning horizons as keys., Share of unsustainable biomass use retained using primary production of Eurostat data as reference
share_sustainable_potential_available,--,Dictionary with planning horizons as keys., Share determines phase-in of ENSPRESO biomass potentials
4 changes: 3 additions & 1 deletion rules/build_sector.smk
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ rule build_biomass_potentials:
"https://zenodo.org/records/10356004/files/ENSPRESO_BIOMASS.xlsx",
keep_local=True,
),
nuts2="data/bundle/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", # https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/#nuts21
eurostat="data/eurostat/Balances-April2023",
nuts2="data/bundle/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson",
regions_onshore=resources("regions_onshore_elec_s{simpl}_{clusters}.geojson"),
nuts3_population=ancient("data/bundle/nama_10r_3popgdp.tsv.gz"),
swiss_cantons=ancient("data/ch_cantons.csv"),
Expand Down Expand Up @@ -940,6 +941,7 @@ rule prepare_sector_network:
countries=config_provider("countries"),
adjustments=config_provider("adjustments", "sector"),
emissions_scope=config_provider("energy", "emissions"),
biomass=config_provider("biomass"),
RDIR=RDIR,
input:
unpack(input_profile_offwind),
Expand Down
134 changes: 132 additions & 2 deletions scripts/build_biomass_potentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,51 @@
import numpy as np
import pandas as pd
from _helpers import configure_logging, set_scenario_config
from build_energy_totals import build_eurostat

logger = logging.getLogger(__name__)
AVAILABLE_BIOMASS_YEARS = [2010, 2020, 2030, 2040, 2050]


def _calc_unsustainable_potential(df, df_unsustainable, share_unsus, resource_type):
"""
Calculate the unsustainable biomass potential for a given resource type or
regex.

Parameters
----------
df : pd.DataFrame
The dataframe with sustainable biomass potentials.
df_unsustainable : pd.DataFrame
The dataframe with unsustainable biomass potentials.
share_unsus : float
The share of unsustainable biomass potential retained.
resource_type : str or regex
The resource type to calculate the unsustainable potential for.

Returns
-------
pd.Series
The unsustainable biomass potential for the given resource type or regex.
"""

if "|" in resource_type:
resource_potential = df_unsustainable.filter(regex=resource_type).sum(axis=1)
else:
resource_potential = df_unsustainable[resource_type]

return (
df.apply(
lambda c: c.sum()
/ df.loc[df.index.str[:2] == c.name[:2]].sum().sum()
* resource_potential.loc[c.name[:2]],
axis=1,
)
.mul(share_unsus)
.clip(lower=0)
)


def build_nuts_population_data(year=2013):
pop = pd.read_csv(
snakemake.input.nuts3_population,
Expand Down Expand Up @@ -211,15 +251,103 @@ def convert_nuts2_to_regions(bio_nuts2, regions):
return bio_regions


def add_unsustainable_potentials(df):
"""
Add unsustainable biomass potentials to the given dataframe. The difference
between the data of JRC and Eurostat is assumed to be unsustainable
biomass.

Parameters
----------
df : pd.DataFrame
The dataframe with sustainable biomass potentials.
unsustainable_biomass : str
Path to the file with unsustainable biomass potentials.

Returns
-------
pd.DataFrame
The dataframe with added unsustainable biomass potentials.
"""
if "GB" in snakemake.config["countries"]:
latest_year = 2019
else:
latest_year = 2021
idees_rename = {"GR": "EL", "GB": "UK"}
df_unsustainable = (
build_eurostat(
countries=snakemake.config["countries"],
input_eurostat=snakemake.input.eurostat,
)
cpschau marked this conversation as resolved.
Show resolved Hide resolved
.xs(
max(min(latest_year, int(snakemake.wildcards.planning_horizons)), 1990),
level=1,
)
.xs("Primary production", level=2)
.droplevel([1, 2, 3])
)

df_unsustainable.index = df_unsustainable.index.str.strip()
df_unsustainable = df_unsustainable.rename(
{v: k for k, v in idees_rename.items()}, axis=0
)

bio_carriers = [
"Primary solid biofuels",
"Biogases",
"Renewable municipal waste",
"Pure biogasoline",
"Blended biogasoline",
"Pure biodiesels",
"Blended biodiesels",
"Pure bio jet kerosene",
"Blended bio jet kerosene",
"Other liquid biofuels",
]

df_unsustainable = df_unsustainable[bio_carriers]

# Phase out unsustainable biomass potentials linearly from 2020 to 2035 while phasing in sustainable potentials
share_unsus = params.get("share_unsustainable_use_retained").get(investment_year)

df_wo_ch = df.drop(df.filter(regex="CH\d", axis=0).index)

# Calculate unsustainable solid biomass
df_wo_ch["unsustainable solid biomass"] = _calc_unsustainable_potential(
df_wo_ch, df_unsustainable, share_unsus, "Primary solid biofuels"
)

# Calculate unsustainable biogas
df_wo_ch["unsustainable biogas"] = _calc_unsustainable_potential(
df_wo_ch, df_unsustainable, share_unsus, "Biogases"
)

# Calculate unsustainable bioliquids
df_wo_ch["unsustainable bioliquids"] = _calc_unsustainable_potential(
df_wo_ch,
df_unsustainable,
share_unsus,
resource_type="gasoline|diesel|kerosene|liquid",
)

share_sus = params.get("share_sustainable_potential_available").get(investment_year)
df *= share_sus

df = df.join(df_wo_ch.filter(like="unsustainable")).fillna(0)

return df


if __name__ == "__main__":
if "snakemake" not in globals():

from _helpers import mock_snakemake

snakemake = mock_snakemake(
"build_biomass_potentials",
simpl="",
clusters="5",
planning_horizons=2050,
clusters="37",
planning_horizons=2020,
)

configure_logging(snakemake)
Expand Down Expand Up @@ -269,6 +397,8 @@ def convert_nuts2_to_regions(bio_nuts2, regions):
grouper = {v: k for k, vv in params["classes"].items() for v in vv}
df = df.T.groupby(grouper).sum().T

df = add_unsustainable_potentials(df)

df *= 1e6 # TWh/a to MWh/a
df.index.name = "MWh/a"

Expand Down
98 changes: 98 additions & 0 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ def define_spatial(nodes, options):

if options.get("biomass_spatial", options["biomass_transport"]):
spatial.biomass.nodes = nodes + " solid biomass"
spatial.biomass.bioliquids = nodes + " bioliquids"
spatial.biomass.locations = nodes
spatial.biomass.industry = nodes + " solid biomass for industry"
spatial.biomass.industry_cc = nodes + " solid biomass for industry CC"
spatial.msw.nodes = nodes + " municipal solid waste"
spatial.msw.locations = nodes
else:
spatial.biomass.nodes = ["EU solid biomass"]
spatial.biomass.bioliquids = ["EU unsustainable bioliquids"]
spatial.biomass.locations = ["EU"]
spatial.biomass.industry = ["solid biomass for industry"]
spatial.biomass.industry_cc = ["solid biomass for industry CC"]
Expand Down Expand Up @@ -2248,8 +2250,14 @@ def add_biomass(n, costs):
biogas_potentials_spatial = biomass_potentials["biogas"].rename(
index=lambda x: x + " biogas"
)
unsustainable_biogas_potentials_spatial = biomass_potentials[
"unsustainable biogas"
].rename(index=lambda x: x + " biogas")
else:
biogas_potentials_spatial = biomass_potentials["biogas"].sum()
unsustainable_biogas_potentials_spatial = biomass_potentials[
"unsustainable biogas"
].sum()

if options.get("biomass_spatial", options["biomass_transport"]):
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename(
Expand All @@ -2258,11 +2266,27 @@ def add_biomass(n, costs):
msw_biomass_potentials_spatial = biomass_potentials[
"municipal solid waste"
].rename(index=lambda x: x + " municipal solid waste")
unsustainable_solid_biomass_potentials_spatial = biomass_potentials[
"unsustainable solid biomass"
].rename(index=lambda x: x + " solid biomass")

else:
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum()
msw_biomass_potentials_spatial = biomass_potentials[
"municipal solid waste"
].sum()
unsustainable_solid_biomass_potentials_spatial = biomass_potentials[
"unsustainable solid biomass"
].sum()

if options["regional_oil_demand"]:
unsustainable_liquid_biofuel_potentials_spatial = biomass_potentials[
"unsustainable bioliquids"
].rename(index=lambda x: x + " bioliquids")
else:
unsustainable_liquid_biofuel_potentials_spatial = biomass_potentials[
"unsustainable bioliquids"
].sum()

n.add("Carrier", "biogas")
n.add("Carrier", "solid biomass")
Expand Down Expand Up @@ -2387,6 +2411,79 @@ def add_biomass(n, costs):
p_nom_extendable=True,
)

if biomass_potentials.filter(like="unsustainable").sum().sum() > 0:

# Create timeseries to force usage of unsustainable potentials
e_max_pu = pd.Series(1, index=n.snapshots)
cpschau marked this conversation as resolved.
Show resolved Hide resolved
e_max_pu.iloc[-1] = 0

n.madd(
"Store",
spatial.gas.biogas,
suffix=" unsustainable",
bus=spatial.gas.biogas,
carrier="unsustainable biogas",
e_nom=unsustainable_biogas_potentials_spatial,
marginal_cost=costs.at["biogas", "fuel"],
cpschau marked this conversation as resolved.
Show resolved Hide resolved
e_initial=unsustainable_biogas_potentials_spatial,
e_nom_extendable=False,
e_max_pu=e_max_pu.rename(
spatial.gas.biogas[0] + " unsustainable"
cpschau marked this conversation as resolved.
Show resolved Hide resolved
).to_frame(),
)

n.madd(
"Store",
spatial.biomass.nodes,
suffix=" unsustainable",
bus=spatial.biomass.nodes,
carrier="unsustainable solid biomass",
e_nom=unsustainable_solid_biomass_potentials_spatial,
marginal_cost=costs.at["fuelwood", "fuel"],
e_initial=unsustainable_solid_biomass_potentials_spatial,
e_nom_extendable=False,
e_max_pu=e_max_pu.rename(
spatial.biomass.nodes[0] + " unsustainable"
).to_frame(),
)

n.madd(
"Bus",
spatial.biomass.bioliquids,
location=spatial.biomass.locations,
carrier="unsustainable bioliquids",
unit="MWh_LHV",
)

n.madd(
"Store",
spatial.biomass.bioliquids,
suffix=" unsustainable",
bus=spatial.biomass.bioliquids,
carrier="unsustainable bioliquids",
e_nom=unsustainable_liquid_biofuel_potentials_spatial,
marginal_cost=costs.at["biodiesel crops", "fuel"],
e_initial=unsustainable_liquid_biofuel_potentials_spatial,
e_nom_extendable=False,
e_max_pu=e_max_pu.rename(
spatial.biomass.bioliquids[0] + " unsustainable"
).to_frame(),
)

n.madd(
cpschau marked this conversation as resolved.
Show resolved Hide resolved
"Link",
spatial.biomass.bioliquids,
bus0=spatial.biomass.bioliquids,
bus1=spatial.oil.nodes,
bus2="co2 atmosphere",
carrier="unsustainable bioliquids",
efficiency=1,
efficiency2=-costs.at["solid biomass", "CO2 intensity"]
+ costs.at["BtL", "CO2 stored"],
p_nom=unsustainable_liquid_biofuel_potentials_spatial,
marginal_cost=costs.at["BtL", "VOM"],
)

n.madd(
"Link",
spatial.gas.biogas_to_gas,
Expand Down Expand Up @@ -4122,6 +4219,7 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs):
# %%
if __name__ == "__main__":
if "snakemake" not in globals():

from _helpers import mock_snakemake

snakemake = mock_snakemake(
Expand Down
Loading