Skip to content

Commit 243ef47

Browse files
committed
refining existing Haber-Bosch plants + Documentation
1 parent 5e8d03c commit 243ef47

1 file changed

Lines changed: 65 additions & 21 deletions

File tree

scripts/add_existing_baseyear.py

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -717,12 +717,25 @@ def add_heating_capacities_installed_before_baseyear(
717717
)
718718

719719

720-
def prepare_plant_data():
720+
def prepare_plant_data(
721+
regions_fn: str,
722+
isi_database: str,
723+
) -> tuple[pd.DataFrame, gpd.GeoDataFrame]:
724+
"""
725+
Reads in the Fraunhofer ISI database with high resolution plant data and maps them to the bus regions.
726+
Returns the database as df as well as the regions as gdf.
721727
728+
Parameters
729+
----------
730+
regions_fn : str
731+
path to the onshore regions file
732+
isi_database: str
733+
path to the fraunhofer isi database
734+
"""
722735
# add existing industry
723-
regions = gpd.read_file(snakemake.input.regions_onshore).set_index("name")
736+
regions = gpd.read_file(regions_fn).set_index("name")
724737

725-
isi_data = pd.read_excel(snakemake.input.isi_database, sheet_name="Database", index_col=1)
738+
isi_data = pd.read_excel(isi_database, sheet_name="Database", index_col=1)
726739
# assign bus region to each plant
727740
geometry = gpd.points_from_xy(isi_data["Longitude"], isi_data["Latitude"])
728741
plant_data = gpd.GeoDataFrame(isi_data, geometry=geometry, crs="EPSG:4326")
@@ -744,19 +757,27 @@ def prepare_plant_data():
744757
return plant_data, regions
745758

746759

747-
def add_existing_ammonia_plants(n):
748-
760+
def add_existing_ammonia_plants(
761+
n: pypsa.Network,
762+
) -> None:
763+
"""
764+
Adds existing Haber-Bosch plants.
765+
The plants are running on natural gas only since the retrofitting to hydrogen would be associated with costs. Plants are not expected to run at a minimal part load to avoid forcing the use of natural gas in planning horizons with climate targets.
766+
Exhaust heat is not integrated since assuming that heat is integrated to make the current process more efficient.
767+
"""
749768
logger.info("Adding existing ammonia plants.")
750769

751-
plant_data, regions = prepare_plant_data()
770+
plant_data, regions = prepare_plant_data(
771+
snakemake.input.regions_onshore,
772+
snakemake.input.isi_database,
773+
)
752774

753-
ammonia_plants = plant_data[plant_data.Product=="Ammonia"]
775+
fh_ammonia = plant_data[plant_data.Product=="Ammonia"]
754776

755-
ammonia_plants = ammonia_plants.groupby(['bus', 'Country', 'grouping_year', 'Product'], as_index=False)['Production in tons (calibrated)'].sum()
777+
fh_ammonia = fh_ammonia.groupby(['bus', 'Country', 'grouping_year', 'Product'], as_index=False)['Production in tons (calibrated)'].sum()
756778

757-
ammonia_plants.index = ammonia_plants['bus'] + " Haber-Bosch-" + ammonia_plants['grouping_year'].astype(str)
758-
759-
# add dataset
779+
fh_ammonia.index = fh_ammonia['bus'] + " Haber-Bosch-SMR-" + fh_ammonia['grouping_year'].astype(str)
780+
# add dataset for Non EU27 countries
760781
df = pd.read_csv(snakemake.input.ammonia, index_col=0)
761782

762783
geometry = gpd.points_from_xy(df.Longitude, df.Latitude)
@@ -765,22 +786,45 @@ def add_existing_ammonia_plants(n):
765786
gdf = gpd.sjoin(gdf, regions, how="inner", predicate="within")
766787

767788
gdf.rename(columns={"name": "bus"}, inplace=True)
768-
gdf["country"] = gdf.bus.str[:2]
789+
gdf["Country"] = gdf.bus.str[:2]
769790
# filter for countries that are missing
770-
gdf[~gdf.country.isin(ammonia_plants.Country.unique())]
771-
791+
gdf = gdf[(~gdf.Country.isin(fh_ammonia.Country.unique())) & (gdf.Country.isin(snakemake.params.countries))]
792+
# following approach from build_industrial_distribution_key.py
793+
for country in gdf.Country:
794+
facilities = gdf.query("Country == @country")
795+
production = facilities["Ammonia [kt/a]"]
796+
# assume 50% of the minimum production for missing values
797+
production = production.fillna(0.5 * facilities["Ammonia [kt/a]"].min())
798+
799+
# missing data
800+
gdf.drop(gdf[gdf["Ammonia [kt/a]"].isna()].index, inplace=True)
801+
802+
# get average plant age:
803+
avg_age = plant_data[plant_data.Product=="Ammonia"]["Year of last modernisation"].mean()
804+
gdf["grouping_year"] = min((y for y in snakemake.params.existing_capacities["grouping_years_industry"] if y > avg_age))
805+
# match database
806+
gdf.index = gdf["bus"] + " Haber-Bosch-SMR-" + gdf["grouping_year"].values.astype(str)
807+
gdf.rename(columns={"Ammonia [kt/a]": "Production in tons (calibrated)"}, inplace=True)
808+
gdf["Production in tons (calibrated)"] *= 1e3
809+
810+
ammonia_plants = pd.concat([fh_ammonia, gdf[["bus", "Country", "grouping_year", "Production in tons (calibrated)"]]])
811+
812+
# https://dechema.de/dechema_media/Downloads/Positionspapiere/Technology_study_Low_carbon_energy_and_feedstock_for_the_European_chemical_industry.pdf
813+
# page 56: 1.83 t_CO2/t_NH3
814+
ch4_per_nh3 = 1.83 / costs.at["gas", "CO2 intensity"] / snakemake.params["MWh_NH3_per_tNH3"]
772815
n.add(
773816
"Link",
774817
ammonia_plants.index,
775-
bus0=ammonia_plants.bus,
818+
bus0=[bus + " gas" for bus in ammonia_plants.bus] if snakemake.params.sector["gas_network"] else "EU gas",
776819
bus1=[bus + " NH3" for bus in ammonia_plants.bus] if snakemake.params.sector["ammonia"] else "EU NH3",
777-
bus2=[bus + " gas" for bus in ammonia_plants.bus] if snakemake.params.sector["gas_network"] else "EU gas",
778-
p_nom=ammonia_plants["Production in tons (calibrated)"].mul(snakemake.params.MWh_NH3_per_tNH3).div(costs.at["Haber-Bosch", "electricity-input"]).div(8760).values,
820+
bus2=ammonia_plants.bus,
821+
bus3="co2 atmosphere",
822+
p_nom=ammonia_plants["Production in tons (calibrated)"].mul(snakemake.params.MWh_NH3_per_tNH3).div(ch4_per_nh3).div(8760).values,
779823
p_nom_extendable=False,
780824
carrier="Haber-Bosch",
781-
efficiency=1 / costs.at["Haber-Bosch", "electricity-input"],
782-
efficiency2=-costs.at["Haber-Bosch", "hydrogen-input"]
783-
/ costs.at["Haber-Bosch", "electricity-input"],
825+
efficiency=1 / ch4_per_nh3,
826+
efficiency1=-costs.at["Haber-Bosch", "electricity-input"] / ch4_per_nh3,
827+
efficiency2=costs.at["gas", "CO2 intensity"],
784828
capital_cost=costs.at["Haber-Bosch", "capital_cost"]
785829
/ costs.at["Haber-Bosch", "electricity-input"],
786830
marginal_cost=costs.at["Haber-Bosch", "VOM"]
@@ -873,7 +917,7 @@ def add_existing_ammonia_plants(n):
873917
cluster_heat_buses(n)
874918

875919
# add existing industry plants
876-
if snakemake.sector.ammonia:
920+
if snakemake.params.sector["ammonia"]:
877921
add_existing_ammonia_plants(n)
878922

879923
n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards)))

0 commit comments

Comments
 (0)