@@ -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