diff --git a/config.default.yaml b/config.default.yaml index d48879c6..ae8f90c8 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -244,6 +244,7 @@ sector: # - onshore # more than 50 km from sea - nearshore # within 50 km of sea # - offshore + ammonia: false use_fischer_tropsch_waste_heat: true use_fuel_cell_waste_heat: true electricity_distribution_grid: true @@ -291,6 +292,7 @@ industry: # 2040: 0.3 # 2045: 0.25 # 2050: 0.2 + MWh_NH3_per_tNH3: 5.166 # LHV MWh_CH4_per_tNH3_SMR: 10.8 # 2012's demand from https://ec.europa.eu/docsroom/documents/4165/attachments/1/translations/en/renditions/pdf MWh_elec_per_tNH3_SMR: 0.7 # same source, assuming 94-6% split methane-elec of total energy demand 11.5 MWh/tNH3 MWh_H2_per_tNH3_electrolysis: 6.5 # from https://doi.org/10.1016/j.joule.2018.04.017, around 0.197 tH2/tHN3 (>3/17 since some H2 lost and used for energy) diff --git a/scripts/build_industrial_energy_demand_per_country_today.py b/scripts/build_industrial_energy_demand_per_country_today.py index 0adf84e7..a3fbf466 100644 --- a/scripts/build_industrial_energy_demand_per_country_today.py +++ b/scripts/build_industrial_energy_demand_per_country_today.py @@ -65,6 +65,8 @@ def industrial_energy_demand_per_country(country): df = df_dict[sheet][year].groupby(fuels).sum() + df["ammonia"] = 0. + df['other'] = df['all'] - df.loc[df.index != 'all'].sum() return df @@ -89,18 +91,21 @@ def add_ammonia_energy_demand(demand): fn = snakemake.input.ammonia_production ammonia = pd.read_csv(fn, index_col=0)[str(year)] / 1e3 - def ammonia_by_fuel(x): + def get_ammonia_by_fuel(x): fuels = {'gas': config['MWh_CH4_per_tNH3_SMR'], 'electricity': config['MWh_elec_per_tNH3_SMR']} return pd.Series({k: x*v for k,v in fuels.items()}) - ammonia = ammonia.apply(ammonia_by_fuel).T + ammonia_by_fuel = ammonia.apply(get_ammonia_by_fuel).T + ammonia_by_fuel = ammonia_by_fuel.unstack().reindex(index=demand.index, fill_value=0.) + + ammonia = pd.DataFrame({"ammonia": ammonia * config['MWh_NH3_per_tNH3']}).T demand['Ammonia'] = ammonia.unstack().reindex(index=demand.index, fill_value=0.) - demand['Basic chemicals (without ammonia)'] = demand["Basic chemicals"] - demand["Ammonia"] + demand['Basic chemicals (without ammonia)'] = demand["Basic chemicals"] - ammonia_by_fuel demand['Basic chemicals (without ammonia)'].clip(lower=0, inplace=True) diff --git a/scripts/build_industrial_energy_demand_per_node.py b/scripts/build_industrial_energy_demand_per_node.py index cb085ad1..d665f18e 100644 --- a/scripts/build_industrial_energy_demand_per_node.py +++ b/scripts/build_industrial_energy_demand_per_node.py @@ -9,6 +9,7 @@ if __name__ == '__main__': 'build_industrial_energy_demand_per_node', simpl='', clusters=48, + planning_horizons=2030, ) # import EU ratios df as csv diff --git a/scripts/build_industry_sector_ratios.py b/scripts/build_industry_sector_ratios.py index c8cac055..d1dbe9d8 100644 --- a/scripts/build_industry_sector_ratios.py +++ b/scripts/build_industry_sector_ratios.py @@ -60,6 +60,7 @@ index = [ "hydrogen", "heat", "naphtha", + "ammonia", "process emission", "process emission from feedstock", ] @@ -432,8 +433,11 @@ def chemicals_industry(): sector = "Ammonia" df[sector] = 0.0 - df.loc["hydrogen", sector] = config["MWh_H2_per_tNH3_electrolysis"] - df.loc["elec", sector] = config["MWh_elec_per_tNH3_electrolysis"] + if snakemake.config["sector"].get("ammonia", False): + df.loc["ammonia", sector] = config["MWh_NH3_per_tNH3"] + else: + df.loc["hydrogen", sector] = config["MWh_H2_per_tNH3_electrolysis"] + df.loc["elec", sector] = config["MWh_elec_per_tNH3_electrolysis"] # Chlorine diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f0f60934..a524e882 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -654,6 +654,59 @@ def add_generation(n, costs): ) +def add_ammonia(n, costs): + + logger.info("adding ammonia carrier") + + nodes = pop_layout.index + + n.add("Carrier", "NH3") + + n.madd("Bus", + nodes + " NH3", + location=nodes, + carrier="NH3" + ) + + n.madd("Link", + nodes, + suffix=" Haber-Bosch", + bus0=nodes, + bus1=nodes + " NH3", + bus2=nodes + " H2", + p_nom_extendable=True, + carrier="Haber-Bosch", + efficiency=+0.221, #MWh_e/MWh_NH3 0.247 https://github.com/euronion/trace/blob/44a5ff8401762edbef80eff9cfe5a47c8d3c8be4/data/efficiencies.csv + efficiency2=-1.226, #MWh_H2/MWh_NH3 1.148 https://github.com/euronion/trace/blob/44a5ff8401762edbef80eff9cfe5a47c8d3c8be4/data/efficiencies.csv + capital_cost=costs.at["Haber-Bosch synthesis", "fixed"], + lifetime=costs.at["Haber-Bosch synthesis", 'lifetime'] + ) + + n.madd("Link", + nodes, + suffix=" ammonia cracker", + bus0=nodes + " NH3", + bus1=nodes + " H2", + p_nom_extendable=True, + carrier ="ammonia cracker", + efficiency=0.685, #MWh_H2/MWh_NH3 https://github.com/euronion/trace/blob/44a5ff8401762edbef80eff9cfe5a47c8d3c8be4/data/efficiencies.csv + capital_cost=costs.at["Ammonia cracker", "fixed"] * 0.685, # given per MWh_H2 + lifetime=costs.at['Ammonia cracker', 'lifetime'] + ) + + # Ammonia Storage + n.madd("Store", + nodes, + suffix=" ammonia store", + bus=nodes + " NH3", + e_nom_extendable=True, + e_cyclic=True, + carrier="ammonia store", + capital_cost=costs.at["NH3 (l) storage tank incl. liquefaction", "fixed"], + lifetime=costs.at['NH3 (l) storage tank incl. liquefaction', 'lifetime'] + ) + + def add_wave(n, wave_cost_factor): # TODO: handle in Snakefile @@ -2148,6 +2201,15 @@ def add_industry(n, costs): lifetime=costs.at['cement capture', 'lifetime'] ) + if options["ammonia"]: + n.madd("Load", + nodes, + suffix=" NH3", + bus=nodes + " NH3", + carrier="NH3", + p_set=industrial_demand.loc[nodes, "ammonia"] / 8760 + ) + def add_waste_heat(n): # TODO options? @@ -2377,6 +2439,9 @@ if __name__ == "__main__": if options['dac']: add_dac(n, costs) + if options['ammonia']: + add_ammonia(n, costs) + if "decentral" in opts: decentral(n)