From 9573f33860d9e0981ad73b1a07bfaf8d45181bb5 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 14:44:36 +0200 Subject: [PATCH 01/12] add ammonia as carrier: with Haber-Bosch, crackers, store, load --- config.default.yaml | 2 ++ ...uild_industrial_energy_demand_per_country_today.py | 11 ++++++++--- scripts/build_industrial_energy_demand_per_node.py | 1 + scripts/build_industry_sector_ratios.py | 8 ++++++-- 4 files changed, 17 insertions(+), 5 deletions(-) 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 From 6cfee1f98a647509f2e2cab0d36348e8cbcbe08d Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 14:44:36 +0200 Subject: [PATCH 02/12] add ammonia as carrier: with Haber-Bosch, crackers, store, load --- config.default.yaml | 2 + ...ustrial_energy_demand_per_country_today.py | 11 +++- ...build_industrial_energy_demand_per_node.py | 1 + scripts/build_industry_sector_ratios.py | 8 ++- scripts/prepare_sector_network.py | 65 +++++++++++++++++++ 5 files changed, 82 insertions(+), 5 deletions(-) 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) From 2d562c13495a21cbba504ceb09f9614b2365d58a Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 14:57:03 +0200 Subject: [PATCH 03/12] add coloring for ammonia --- config.default.yaml | 5 +++++ scripts/plot_network.py | 2 ++ scripts/plot_summary.py | 2 ++ 3 files changed, 9 insertions(+) diff --git a/config.default.yaml b/config.default.yaml index ae8f90c8..b510780e 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -573,6 +573,11 @@ plotting: H2 pipeline retrofitted: '#ba99b5' H2 Fuel Cell: '#c251ae' H2 Electrolysis: '#ff29d9' + # ammonia + NH3: '#46caf0' + ammonia store: '#00ace0' + ammonia cracker: '#87d0e6' + Haber-Bosch: '#076987' # syngas Sabatier: '#9850ad' methanation: '#c44ce6' diff --git a/scripts/plot_network.py b/scripts/plot_network.py index 4a1bc6d0..61f91df1 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -23,6 +23,8 @@ def rename_techs_tyndp(tech): return "power-to-gas" elif tech == "H2": return "H2 storage" + elif tech in ["NH3", "Haber-Bosch", "ammonia cracker", "ammonia store"]: + return "ammonia" elif tech in ["OCGT", "CHP", "gas boiler", "H2 Fuel Cell"]: return "gas-to-power/heat" elif "solar" in tech: diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index 8b073b17..97166f54 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -51,6 +51,7 @@ def rename_techs(label): "ror": "hydroelectricity", "hydro": "hydroelectricity", "PHS": "hydroelectricity", + "NH3": "ammonia" "co2 Store": "DAC", "co2 stored": "CO2 sequestration", "AC": "transmission lines", @@ -106,6 +107,7 @@ preferred_order = pd.Index([ "natural gas", "helmeth", "methanation", + "ammonia", "hydrogen storage", "power-to-gas", "power-to-liquid", From a2a4cf7c023eccdbb5d5de2043fac2036a16347f Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 16:43:29 +0200 Subject: [PATCH 04/12] use config to manage conversion efficiencies --- config.default.yaml | 1 + scripts/prepare_sector_network.py | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index b510780e..d2291f58 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -297,6 +297,7 @@ industry: 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) MWh_elec_per_tNH3_electrolysis: 1.17 # from https://doi.org/10.1016/j.joule.2018.04.017 Table 13 (air separation and HB) + MWh_NH3_per_MWh_H2_cracker: 1.46 # https://github.com/euronion/trace/blob/44a5ff8401762edbef80eff9cfe5a47c8d3c8be4/data/efficiencies.csv NH3_process_emissions: 24.5 # in MtCO2/a from SMR for H2 production for NH3 from UNFCCC for 2015 for EU28 petrochemical_process_emissions: 25.5 # in MtCO2/a for petrochemical and other from UNFCCC for 2015 for EU28 HVC_primary_fraction: 1. # fraction of today's HVC produced via primary route diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index a524e882..dd4e6e39 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -660,6 +660,8 @@ def add_ammonia(n, costs): nodes = pop_layout.index + cf_industry = snakemake.config["industry"] + n.add("Carrier", "NH3") n.madd("Bus", @@ -676,8 +678,8 @@ def add_ammonia(n, costs): 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 + efficiency=1 / (cf_industry["MWh_elec_per_tNH3_electrolysis"] / cf_industry["MWh_NH3_per_tNH3"]) # output: MW_NH3 per MW_elec + efficiency2=-cf_industry["MWh_H2_per_tNH3_electrolysis"] / cf_industry["MWh_elec_per_tNH3_electrolysis"] # input: MW_H2 per MW_elec capital_cost=costs.at["Haber-Bosch synthesis", "fixed"], lifetime=costs.at["Haber-Bosch synthesis", 'lifetime'] ) @@ -688,9 +690,9 @@ def add_ammonia(n, costs): 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 + carrier="ammonia cracker", + efficiency=1 / cf_industry["MWh_NH3_per_MWh_H2_cracker"] + capital_cost=costs.at["Ammonia cracker", "fixed"] / cf_industry["MWh_NH3_per_MWh_H2_cracker"], # given per MW_H2 lifetime=costs.at['Ammonia cracker', 'lifetime'] ) From 4984ba199e2e2a41bf8183cbd7832b5262521b53 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 16:53:37 +0200 Subject: [PATCH 05/12] use spatial namespace to manage ammonia resolution --- config.default.yaml | 2 +- scripts/prepare_sector_network.py | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index d2291f58..d0887bc8 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -244,7 +244,7 @@ sector: # - onshore # more than 50 km from sea - nearshore # within 50 km of sea # - offshore - ammonia: false + ammonia: false # can be false (no NH3 carrier), true (copperplated NH3), "regional" (regionalised NH3 without network) use_fischer_tropsch_waste_heat: true use_fuel_cell_waste_heat: true electricity_distribution_grid: true diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index dd4e6e39..9411a282 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -93,6 +93,18 @@ def define_spatial(nodes, options): spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes) + # ammonia + + spatial.ammonia = SimpleNamespace() + if options["ammonia"] == "regional": + spatial.ammonia.nodes = nodes + " NH3" + spatial.ammonia.locations = nodes + else: + spatial.ammonia.nodes = ["EU ammonia"] + spatial.ammonia.locations = ["EU"] + + spatial.ammonia.df = pd.DataFrame(vars(spatial.ammonia), index=nodes) + # oil spatial.oil = SimpleNamespace() spatial.oil.nodes = ["EU oil"] @@ -656,7 +668,7 @@ def add_generation(n, costs): def add_ammonia(n, costs): - logger.info("adding ammonia carrier") + logger.info("adding ammonia carrier with synthesis, cracking and storage") nodes = pop_layout.index @@ -665,8 +677,8 @@ def add_ammonia(n, costs): n.add("Carrier", "NH3") n.madd("Bus", - nodes + " NH3", - location=nodes, + spatial.ammonia.nodes, + location=spatial.ammonia.locations, carrier="NH3" ) @@ -674,7 +686,7 @@ def add_ammonia(n, costs): nodes, suffix=" Haber-Bosch", bus0=nodes, - bus1=nodes + " NH3", + bus1=spatial.ammonia.nodes, bus2=nodes + " H2", p_nom_extendable=True, carrier="Haber-Bosch", @@ -687,7 +699,7 @@ def add_ammonia(n, costs): n.madd("Link", nodes, suffix=" ammonia cracker", - bus0=nodes + " NH3", + bus0=spatial.ammonia.nodes, bus1=nodes + " H2", p_nom_extendable=True, carrier="ammonia cracker", @@ -698,9 +710,9 @@ def add_ammonia(n, costs): # Ammonia Storage n.madd("Store", - nodes, + spatial.ammonia.nodes, suffix=" ammonia store", - bus=nodes + " NH3", + bus=spatial.ammonia.nodes, e_nom_extendable=True, e_cyclic=True, carrier="ammonia store", From 27ac40d2eaa1308830b862391c69a6c2e8961922 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 16:53:37 +0200 Subject: [PATCH 06/12] use spatial namespace to manage ammonia resolution --- config.default.yaml | 2 +- scripts/prepare_sector_network.py | 31 +++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index d2291f58..d0887bc8 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -244,7 +244,7 @@ sector: # - onshore # more than 50 km from sea - nearshore # within 50 km of sea # - offshore - ammonia: false + ammonia: false # can be false (no NH3 carrier), true (copperplated NH3), "regional" (regionalised NH3 without network) use_fischer_tropsch_waste_heat: true use_fuel_cell_waste_heat: true electricity_distribution_grid: true diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index dd4e6e39..f0e94c19 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -93,6 +93,18 @@ def define_spatial(nodes, options): spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes) + # ammonia + + spatial.ammonia = SimpleNamespace() + if options["ammonia"] == "regional": + spatial.ammonia.nodes = nodes + " NH3" + spatial.ammonia.locations = nodes + else: + spatial.ammonia.nodes = ["EU ammonia"] + spatial.ammonia.locations = ["EU"] + + spatial.ammonia.df = pd.DataFrame(vars(spatial.ammonia), index=nodes) + # oil spatial.oil = SimpleNamespace() spatial.oil.nodes = ["EU oil"] @@ -656,7 +668,7 @@ def add_generation(n, costs): def add_ammonia(n, costs): - logger.info("adding ammonia carrier") + logger.info("adding ammonia carrier with synthesis, cracking and storage") nodes = pop_layout.index @@ -665,8 +677,8 @@ def add_ammonia(n, costs): n.add("Carrier", "NH3") n.madd("Bus", - nodes + " NH3", - location=nodes, + spatial.ammonia.nodes, + location=spatial.ammonia.locations, carrier="NH3" ) @@ -674,7 +686,7 @@ def add_ammonia(n, costs): nodes, suffix=" Haber-Bosch", bus0=nodes, - bus1=nodes + " NH3", + bus1=spatial.ammonia.nodes, bus2=nodes + " H2", p_nom_extendable=True, carrier="Haber-Bosch", @@ -687,7 +699,7 @@ def add_ammonia(n, costs): n.madd("Link", nodes, suffix=" ammonia cracker", - bus0=nodes + " NH3", + bus0=spatial.ammonia.nodes, bus1=nodes + " H2", p_nom_extendable=True, carrier="ammonia cracker", @@ -698,9 +710,9 @@ def add_ammonia(n, costs): # Ammonia Storage n.madd("Store", - nodes, + spatial.ammonia.nodes, suffix=" ammonia store", - bus=nodes + " NH3", + bus=spatial.ammonia.nodes, e_nom_extendable=True, e_cyclic=True, carrier="ammonia store", @@ -2205,9 +2217,8 @@ def add_industry(n, costs): if options["ammonia"]: n.madd("Load", - nodes, - suffix=" NH3", - bus=nodes + " NH3", + spatial.ammonia.nodes, + bus=spatial.ammonia.nodes, carrier="NH3", p_set=industrial_demand.loc[nodes, "ammonia"] / 8760 ) From 4ecfccea6cce75e39507c3f95dae0102dd4706aa Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 17:07:48 +0200 Subject: [PATCH 07/12] handle ammonia demand both regionalised and copperplated --- scripts/prepare_sector_network.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f0e94c19..64c1868f 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2216,11 +2216,17 @@ def add_industry(n, costs): ) if options["ammonia"]: + + if options["ammonia"] == 'regional': + p_set = industrial_demand.loc[spatial.ammonia.locations, "ammonia"].rename(index=lambda x: x + " NH3") / 8760 + else: + p_set = industrial_demand["ammonia"].sum() / 8760 + n.madd("Load", spatial.ammonia.nodes, bus=spatial.ammonia.nodes, carrier="NH3", - p_set=industrial_demand.loc[nodes, "ammonia"] / 8760 + p_set=p_set ) From 37c052667a78c1cd473575c9f2eacd70252997c5 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 22 Jun 2022 15:50:32 +0200 Subject: [PATCH 08/12] handle absent ammonia config flag --- scripts/prepare_sector_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 64c1868f..64207f0c 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -96,7 +96,7 @@ def define_spatial(nodes, options): # ammonia spatial.ammonia = SimpleNamespace() - if options["ammonia"] == "regional": + if options.get("ammonia") == "regional": spatial.ammonia.nodes = nodes + " NH3" spatial.ammonia.locations = nodes else: @@ -2215,7 +2215,7 @@ def add_industry(n, costs): lifetime=costs.at['cement capture', 'lifetime'] ) - if options["ammonia"]: + if options.get("ammonia"): if options["ammonia"] == 'regional': p_set = industrial_demand.loc[spatial.ammonia.locations, "ammonia"].rename(index=lambda x: x + " NH3") / 8760 From 9f91af28e727e77285c9761876b6e5657dd71ed4 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 23 Jun 2022 15:17:41 +0200 Subject: [PATCH 09/12] fix syntax errors --- scripts/plot_network.py | 6 +++--- scripts/plot_summary.py | 4 ++-- scripts/prepare_sector_network.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/plot_network.py b/scripts/plot_network.py index 61f91df1..3f7c6960 100644 --- a/scripts/plot_network.py +++ b/scripts/plot_network.py @@ -309,7 +309,7 @@ def plot_h2_map(network): ) n.plot( - geomap=False, + # geomap=False, bus_sizes=0, link_colors='#72d3d6', link_widths=link_widths_retro, @@ -443,7 +443,7 @@ def plot_ch4_map(network): ) n.plot( - geomap=False, + # geomap=False, ax=ax, bus_sizes=0., link_colors='#e8d1d1', @@ -453,7 +453,7 @@ def plot_ch4_map(network): ) n.plot( - geomap=False, + # geomap=False, ax=ax, bus_sizes=0., link_colors=link_color_used, diff --git a/scripts/plot_summary.py b/scripts/plot_summary.py index 97166f54..f58c81af 100644 --- a/scripts/plot_summary.py +++ b/scripts/plot_summary.py @@ -51,7 +51,7 @@ def rename_techs(label): "ror": "hydroelectricity", "hydro": "hydroelectricity", "PHS": "hydroelectricity", - "NH3": "ammonia" + "NH3": "ammonia", "co2 Store": "DAC", "co2 stored": "CO2 sequestration", "AC": "transmission lines", @@ -256,7 +256,7 @@ def plot_balances(): df = df / 1e6 #remove trailing link ports - df.index = [i[:-1] if ((i != "co2") and (i[-1:] in ["0","1","2","3"])) else i for i in df.index] + df.index = [i[:-1] if ((i not in ["co2", "NH3"]) and (i[-1:] in ["0","1","2","3"])) else i for i in df.index] df = df.groupby(df.index.map(rename_techs)).sum() diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 64207f0c..f56b3606 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -100,7 +100,7 @@ def define_spatial(nodes, options): spatial.ammonia.nodes = nodes + " NH3" spatial.ammonia.locations = nodes else: - spatial.ammonia.nodes = ["EU ammonia"] + spatial.ammonia.nodes = ["EU NH3"] spatial.ammonia.locations = ["EU"] spatial.ammonia.df = pd.DataFrame(vars(spatial.ammonia), index=nodes) @@ -690,8 +690,8 @@ def add_ammonia(n, costs): bus2=nodes + " H2", p_nom_extendable=True, carrier="Haber-Bosch", - efficiency=1 / (cf_industry["MWh_elec_per_tNH3_electrolysis"] / cf_industry["MWh_NH3_per_tNH3"]) # output: MW_NH3 per MW_elec - efficiency2=-cf_industry["MWh_H2_per_tNH3_electrolysis"] / cf_industry["MWh_elec_per_tNH3_electrolysis"] # input: MW_H2 per MW_elec + efficiency=1 / (cf_industry["MWh_elec_per_tNH3_electrolysis"] / cf_industry["MWh_NH3_per_tNH3"]), # output: MW_NH3 per MW_elec + efficiency2=-cf_industry["MWh_H2_per_tNH3_electrolysis"] / cf_industry["MWh_elec_per_tNH3_electrolysis"], # input: MW_H2 per MW_elec capital_cost=costs.at["Haber-Bosch synthesis", "fixed"], lifetime=costs.at["Haber-Bosch synthesis", 'lifetime'] ) @@ -703,7 +703,7 @@ def add_ammonia(n, costs): bus1=nodes + " H2", p_nom_extendable=True, carrier="ammonia cracker", - efficiency=1 / cf_industry["MWh_NH3_per_MWh_H2_cracker"] + efficiency=1 / cf_industry["MWh_NH3_per_MWh_H2_cracker"], capital_cost=costs.at["Ammonia cracker", "fixed"] / cf_industry["MWh_NH3_per_MWh_H2_cracker"], # given per MW_H2 lifetime=costs.at['Ammonia cracker', 'lifetime'] ) From d69efe3d1fc817f0709e4b8d892cde8f3381acb4 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 23 Jun 2022 15:22:21 +0200 Subject: [PATCH 10/12] add ammonia color to config.default.yaml --- config.default.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config.default.yaml b/config.default.yaml index d0887bc8..3621b072 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -576,6 +576,7 @@ plotting: H2 Electrolysis: '#ff29d9' # ammonia NH3: '#46caf0' + ammonia: '#46caf0' ammonia store: '#00ace0' ammonia cracker: '#87d0e6' Haber-Bosch: '#076987' From 203753455770d2b82b56c7d621a4a7e189b34128 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 23 Jun 2022 15:24:44 +0200 Subject: [PATCH 11/12] prepare: only add ammonia to spatial if config selected --- scripts/prepare_sector_network.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f56b3606..d6269413 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -95,15 +95,16 @@ def define_spatial(nodes, options): # ammonia - spatial.ammonia = SimpleNamespace() - if options.get("ammonia") == "regional": - spatial.ammonia.nodes = nodes + " NH3" - spatial.ammonia.locations = nodes - else: - spatial.ammonia.nodes = ["EU NH3"] - spatial.ammonia.locations = ["EU"] + if options.get('ammonia'): + spatial.ammonia = SimpleNamespace() + if options.get("ammonia") == "regional": + spatial.ammonia.nodes = nodes + " NH3" + spatial.ammonia.locations = nodes + else: + spatial.ammonia.nodes = ["EU NH3"] + spatial.ammonia.locations = ["EU"] - spatial.ammonia.df = pd.DataFrame(vars(spatial.ammonia), index=nodes) + spatial.ammonia.df = pd.DataFrame(vars(spatial.ammonia), index=nodes) # oil spatial.oil = SimpleNamespace() From e1a6e13f750e84e7b0630d7303c675ab423a024e Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 23 Jun 2022 15:28:00 +0200 Subject: [PATCH 12/12] add release note [no ci] --- doc/release_notes.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 7808d2ba..552ffccb 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -62,6 +62,11 @@ incorporates retrofitting options to hydrogen. * Add option to sweep the global CO2 sequestration potentials with keyword ``seq200`` in the ``{sector_opts}`` wildcard (for limit of 200 Mt CO2). +* Add option to resolve ammonia as separate energy carrier with Haber-Bosch + synthesis, ammonia cracking, storage and industrial demand. The ammonia + carrier can be nodally resolved or copperplated across Europe. This feature is + controlled by ``sector: ammonia:``. + * Updated `data bundle `_ that includes the hydrogan salt cavern storage potentials. **Bugfixes**