Skip to content

Commit 5e8d03c

Browse files
committed
implementing existing ammonia plants
1 parent dc14c8f commit 5e8d03c

4 files changed

Lines changed: 92 additions & 2 deletions

File tree

config/config.default.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ solar_thermal:
472472
existing_capacities:
473473
grouping_years_power: [1920, 1950, 1955, 1960, 1965, 1970, 1975, 1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025]
474474
grouping_years_heat: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2019] # heat grouping years >= baseyear will be ignored
475+
grouping_years_industry: [1995, 2000, 2005, 2010, 2015, 2020, 2025]
475476
threshold_capacity: 10
476477
default_heating_lifetime: 20
477478
conventional_carriers:
@@ -812,7 +813,7 @@ sector:
812813
var_cf: true
813814
sustainability_factor: 0.0025
814815
solid_biomass_import:
815-
enable: false
816+
enable: true
816817
price: 54 #EUR/MWh
817818
max_amount: 1390 # TWh
818819
upstream_emissions_factor: .1 #share of solid biomass CO2 emissions at full combustion
@@ -968,7 +969,7 @@ clustering:
968969
ramp_limit_down: max
969970
temporal:
970971
resolution_elec: false
971-
resolution_sector: false
972+
resolution_sector: 8760H
972973

973974
# docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#adjustments
974975
adjustments:

rules/solve_myopic.smk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ rule add_existing_baseyear:
1212
costs=config_provider("costs"),
1313
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
1414
energy_totals_year=config_provider("energy", "energy_totals_year"),
15+
countries=config_provider("countries"),
16+
MWh_NH3_per_tNH3=config_provider("industry", "MWh_NH3_per_tNH3"),
1517
input:
1618
network=resources(
1719
"networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc"
@@ -28,6 +30,9 @@ rule add_existing_baseyear:
2830
"existing_heating_distribution_base_s_{clusters}_{planning_horizons}.csv"
2931
),
3032
heating_efficiencies=resources("heating_efficiencies.csv"),
33+
regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"),
34+
ammonia="data/ammonia_plants.csv",
35+
isi_database="data/1-s2.0-S0196890424010586-mmc2.xlsx",
3136
output:
3237
resources(
3338
"networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}_brownfield.nc"

rules/solve_perfect.smk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ rule add_existing_baseyear:
1010
costs=config_provider("costs"),
1111
heat_pump_sources=config_provider("sector", "heat_pump_sources"),
1212
energy_totals_year=config_provider("energy", "energy_totals_year"),
13+
countries=config_provider("countries"),
14+
MWh_NH3_per_tNH3=config_provider("industry", "MWh_NH3_per_tNH3"),
1315
input:
1416
network=resources(
1517
"networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}.nc"
@@ -27,6 +29,9 @@ rule add_existing_baseyear:
2729
),
2830
existing_heating="data/existing_infrastructure/existing_heating_raw.csv",
2931
heating_efficiencies=resources("heating_efficiencies.csv"),
32+
regions_onshore=resources("regions_onshore_base_s_{clusters}.geojson"),
33+
ammonia="data/ammonia_plants.csv",
34+
isi_database="data/1-s2.0-S0196890424010586-mmc2.xlsx",
3035
output:
3136
resources(
3237
"networks/base_s_{clusters}_{opts}_{sector_opts}_{planning_horizons}_brownfield.nc"

scripts/add_existing_baseyear.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import country_converter as coco
1414
import numpy as np
1515
import pandas as pd
16+
import geopandas as gpd
1617
import powerplantmatching as pm
1718
import pypsa
1819
import xarray as xr
@@ -716,6 +717,80 @@ def add_heating_capacities_installed_before_baseyear(
716717
)
717718

718719

720+
def prepare_plant_data():
721+
722+
# add existing industry
723+
regions = gpd.read_file(snakemake.input.regions_onshore).set_index("name")
724+
725+
isi_data = pd.read_excel(snakemake.input.isi_database, sheet_name="Database", index_col=1)
726+
# assign bus region to each plant
727+
geometry = gpd.points_from_xy(isi_data["Longitude"], isi_data["Latitude"])
728+
plant_data = gpd.GeoDataFrame(isi_data, geometry=geometry, crs="EPSG:4326")
729+
plant_data = gpd.sjoin(plant_data, regions, how="inner", predicate="within")
730+
plant_data.rename(columns={"name": "bus"}, inplace=True)
731+
# filter for countries in model scope
732+
plant_data = plant_data[plant_data.Country.isin(snakemake.params.countries)]
733+
# replace UK with GB in Country column
734+
plant_data["Country"] = plant_data["Country"].replace("UK", "GB")
735+
# assign industry grouping year
736+
grouping_years = snakemake.params.existing_capacities["grouping_years_industry"]
737+
plant_data.loc[:, 'Year of last modernisation'] = plant_data['Year of last modernisation'].replace("x", np.nan)
738+
plant_data['grouping_year'] = 0
739+
valid_mask = plant_data['Year of last modernisation'].notna()
740+
valid_years = plant_data.loc[valid_mask, 'Year of last modernisation']
741+
indices = np.searchsorted(grouping_years, valid_years, side='right')
742+
plant_data.loc[valid_years.index, 'grouping_year'] = np.array(grouping_years)[indices]
743+
744+
return plant_data, regions
745+
746+
747+
def add_existing_ammonia_plants(n):
748+
749+
logger.info("Adding existing ammonia plants.")
750+
751+
plant_data, regions = prepare_plant_data()
752+
753+
ammonia_plants = plant_data[plant_data.Product=="Ammonia"]
754+
755+
ammonia_plants = ammonia_plants.groupby(['bus', 'Country', 'grouping_year', 'Product'], as_index=False)['Production in tons (calibrated)'].sum()
756+
757+
ammonia_plants.index = ammonia_plants['bus'] + " Haber-Bosch-" + ammonia_plants['grouping_year'].astype(str)
758+
759+
# add dataset
760+
df = pd.read_csv(snakemake.input.ammonia, index_col=0)
761+
762+
geometry = gpd.points_from_xy(df.Longitude, df.Latitude)
763+
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326")
764+
765+
gdf = gpd.sjoin(gdf, regions, how="inner", predicate="within")
766+
767+
gdf.rename(columns={"name": "bus"}, inplace=True)
768+
gdf["country"] = gdf.bus.str[:2]
769+
# filter for countries that are missing
770+
gdf[~gdf.country.isin(ammonia_plants.Country.unique())]
771+
772+
n.add(
773+
"Link",
774+
ammonia_plants.index,
775+
bus0=ammonia_plants.bus,
776+
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,
779+
p_nom_extendable=False,
780+
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"],
784+
capital_cost=costs.at["Haber-Bosch", "capital_cost"]
785+
/ costs.at["Haber-Bosch", "electricity-input"],
786+
marginal_cost=costs.at["Haber-Bosch", "VOM"]
787+
/ costs.at["Haber-Bosch", "electricity-input"],
788+
build_year=ammonia_plants["grouping_year"],
789+
lifetime=costs.at["Haber-Bosch", "lifetime"],
790+
)
791+
792+
793+
719794
if __name__ == "__main__":
720795
if "snakemake" not in globals():
721796
from scripts._helpers import mock_snakemake
@@ -797,6 +872,10 @@ def add_heating_capacities_installed_before_baseyear(
797872
if options.get("cluster_heat_buses", False):
798873
cluster_heat_buses(n)
799874

875+
# add existing industry plants
876+
if snakemake.sector.ammonia:
877+
add_existing_ammonia_plants(n)
878+
800879
n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards)))
801880

802881
sanitize_custom_columns(n)

0 commit comments

Comments
 (0)