From 63724279a465ae46959a9feb0fd8c93e21aa4761 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 24 Nov 2021 16:01:58 +0100 Subject: [PATCH 01/30] Snakefile: export conda environment --- Snakefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Snakefile b/Snakefile index 76dca9ab..7d7acb70 100644 --- a/Snakefile +++ b/Snakefile @@ -411,6 +411,14 @@ rule copy_config: script: "scripts/copy_config.py" +rule copy_conda_env: + output: SDIR + '/configs/environment.yaml' + threads: 1 + resources: mem_mb=500 + benchmark: SDIR + "/benchmarks/copy_conda_env" + shell: "conda env export -f {output} --no-builds" + + rule make_summary: input: overrides="data/override_component_attrs", @@ -468,6 +476,7 @@ if config["foresight"] == "overnight": network=RDIR + "/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc", costs=CDIR + "costs_{planning_horizons}.csv", config=SDIR + '/configs/config.yaml' + env=SDIR + '/configs/environment.yaml' output: RDIR + "/postnetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc" shadow: "shallow" log: From 9573f33860d9e0981ad73b1a07bfaf8d45181bb5 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 10 Jun 2022 14:44:36 +0200 Subject: [PATCH 02/30] 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 03/30] 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 04/30] 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 05/30] 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 06/30] 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 07/30] 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 08/30] 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 09/30] 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 10/30] 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 11/30] 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 12/30] 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 13/30] 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** From ce6b92dd6d9b3a5fe47e27e9c705c3b659181073 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 8 Aug 2022 08:53:07 +0200 Subject: [PATCH 14/30] add options to aggregate snapshots temporally --- scripts/prepare_sector_network.py | 85 +++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0c175ff4..0bfc78e0 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2413,6 +2413,84 @@ def limit_individual_line_extension(n, maxext): hvdc = n.links.index[n.links.carrier == 'DC'] n.links.loc[hvdc, 'p_nom_max'] = n.links.loc[hvdc, 'p_nom'] + maxext + +def apply_time_segmentation(n, segments, solver_name="cbc", + overwrite_time_dependent=False): + """Aggregating time series to segments with different lengths + + Input: + n: pypsa Network + segments: (int) number of segments in which the typical period should be + subdivided + solver_name: (str) name of solver + overwrite_time_dependent: (bool) overwrite time dependent data of pypsa network + with typical time series created by tsam + """ + try: + import tsam.timeseriesaggregation as tsam + except: + raise ModuleNotFoundError("Optional dependency 'tsam' not found." + "Install via 'pip install tsam'") + + # get all time-dependent data + columns = pd.MultiIndex.from_tuples([],names=['component', 'key', 'asset']) + raw = pd.DataFrame(index=n.snapshots,columns=columns) + for component in n.all_components: + pnl = n.pnl(component) + for key in pnl.keys(): + if not pnl[key].empty: + df = pnl[key].copy() + df.columns = pd.MultiIndex.from_product([[component], [key], df.columns]) + raw = pd.concat([raw, df], axis=1) + + # normalise all time-dependent data + annual_max = raw.max().replace(0,1) + raw = raw.div(annual_max, level=0) + + # get representative segments + agg = tsam.TimeSeriesAggregation(raw, hoursPerPeriod=len(raw), + noTypicalPeriods=1, noSegments=int(segments), + segmentation=True, solver=solver_name) + segmented = agg.createTypicalPeriods() + + + weightings = segmented.index.get_level_values("Segment Duration") + offsets = np.insert(np.cumsum(weightings[:-1]), 0, 0) + timesteps = [raw.index[0] + pd.Timedelta(f"{offset}h") for offset in offsets] + snapshots = pd.DatetimeIndex(timesteps) + sn_weightings = pd.Series(weightings, index=snapshots, name="weightings", dtype="float64") + + n.set_snapshots(sn_weightings.index) + n.snapshot_weightings = n.snapshot_weightings.mul(sn_weightings, axis=0) + + # overwrite time-dependent data with timeseries created by tsam + if overwrite_time_dependent: + values_t = segmented.mul(annual_max).set_index(snapshots) + for component, key in values_t.columns.droplevel(2).unique(): + n.pnl(component)[key] = values_t[component, key] + + return n + +def set_temporal_aggregation(n, opts, solver_name): + """Aggregate network temporally.""" + for o in opts: + # temporal averaging + m = re.match(r"^\d+h$", o, re.IGNORECASE) + if m is not None: + n = average_every_nhours(n, m.group(0)) + # representive snapshots + m = re.match(r"^\d+sn$", o, re.IGNORECASE) + if m is not None: + sn = int(m.group(0).split("sn")[0]) + logger.info("use every {} snapshot as representative".format(sn)) + n.set_snapshots(n.snapshots[::sn]) + n.snapshot_weightings *= sn + # segments with package tsam + if "SEG" in o: + segments = int(o.replace("SEG","")) + logger.info("use temporal segmentation with {} segments".format(segments)) + n = apply_time_segmentation(n, segments, solver_name=solver_name) + return n #%% if __name__ == "__main__": if 'snakemake' not in globals(): @@ -2514,11 +2592,8 @@ if __name__ == "__main__": if options["co2_network"]: add_co2_network(n, costs) - for o in opts: - m = re.match(r'^\d+h$', o, re.IGNORECASE) - if m is not None: - n = average_every_nhours(n, m.group(0)) - break + solver_name = snakemake.config["solving"]["solver"]["name"] + n = set_temporal_aggregation(n, opts, solver_name) limit_type = "config" limit = get(snakemake.config["co2_budget"], investment_year) From 4accfff1bbf50cfdec3d39e2a2a04d9cc2d3ba96 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Mon, 8 Aug 2022 08:53:16 +0200 Subject: [PATCH 15/30] update release notes --- doc/release_notes.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index abcbaad5..7a454f14 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -57,11 +57,13 @@ incorporates retrofitting options to hydrogen. **New features and functionality** +* Add option to aggregate network temporally using representative snapshots or segments (with tsam package) + * Add option for biomass boilers (wood pellets) for decentral heating -* Add option for BioSNG (methane from biomass) with and without CC +* Add option for BioSNG (methane from biomass) with and without CC -* Add option for BtL (Biomass to liquid fuel/oil) with and without CC +* Add option for BtL (Biomass to liquid fuel/oil) with and without CC * Units are assigned to the buses. These only provide a better understanding. The specifications of the units are not taken into account in the optimisation, which means that no automatic conversion of units takes place. From e34bd9b7fb6a9bad241b95e2f678f344652ff1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ebbe=20Kyhl=20G=C3=B8tske=20ekg=40mpe=2Eau=2Edk?= Date: Wed, 31 Aug 2022 13:07:43 +0200 Subject: [PATCH 16/30] Change costs year in snakefile and config file --- Snakefile | 6 +++--- config.default.yaml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Snakefile b/Snakefile index 6db0bca0..6b195d12 100644 --- a/Snakefile +++ b/Snakefile @@ -474,7 +474,7 @@ rule prepare_sector_network: co2="data/eea/UNFCCC_v23.csv", biomass_potentials='resources/biomass_potentials_s{simpl}_{clusters}.csv', heat_profile="data/heat_load_profile_BDEW.csv", - costs=CDIR + "costs_{planning_horizons}.csv", + costs=CDIR + "costs_{}.csv".format(config['costs']['year']) if config["foresight"] == "overnight" else CDIR + "costs_{planning_horizons}.csv", profile_offwind_ac=pypsaeur("resources/profile_offwind-ac.nc"), profile_offwind_dc=pypsaeur("resources/profile_offwind-dc.nc"), h2_cavern="resources/salt_cavern_potentials_s{simpl}_{clusters}.csv", @@ -539,7 +539,7 @@ rule make_summary: RDIR + "/postnetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc", **config['scenario'] ), - costs=CDIR + "costs_{}.csv".format(config['scenario']['planning_horizons'][0]), + costs=CDIR + "costs_{}.csv".format(config['costs']['year']) if config["foresight"] == "overnight" else CDIR + "costs_{}.csv".format(config['scenario']['planning_horizons'][0]), plots=expand( RDIR + "/maps/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}-costs-all_{planning_horizons}.pdf", **config['scenario'] @@ -589,7 +589,7 @@ if config["foresight"] == "overnight": input: overrides="data/override_component_attrs", network=RDIR + "/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc", - costs=CDIR + "costs_{planning_horizons}.csv", + costs=CDIR + "costs_{}.csv".format(config['costs']['year']), config=SDIR + '/configs/config.yaml' output: RDIR + "/postnetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc" shadow: "shallow" diff --git a/config.default.yaml b/config.default.yaml index 5f7cbb33..c964abce 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -40,7 +40,7 @@ scenario: # planning_horizons), be:beta decay; ex:exponential decay # cb40ex0 distributes a carbon budget of 40 GtCO2 following an exponential # decay with initial growth rate 0 - planning_horizons: # investment years for myopic and perfect; or costs year for overnight + planning_horizons: # investment years for myopic and perfect; for overnight, year of cost assumptions can be different and is defined under 'costs' - 2030 # for example, set to # - 2020 @@ -327,6 +327,7 @@ industry: # Material Economics (2019): https://materialeconomics.com/latest-updates/industrial-transformation-2050 costs: + year: 2030 lifetime: 25 #default lifetime # From a Lion Hirth paper, also reflects average of Noothout et al 2016 discountrate: 0.07 From ed8f7830964e576f70815f17c1fefb448ad6cf3e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 14 Sep 2022 16:16:50 +0200 Subject: [PATCH 17/30] include code review --- scripts/prepare_sector_network.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0bfc78e0..2a7d6ce9 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2415,7 +2415,7 @@ def limit_individual_line_extension(n, maxext): def apply_time_segmentation(n, segments, solver_name="cbc", - overwrite_time_dependent=False): + overwrite_time_dependent=True): """Aggregating time series to segments with different lengths Input: @@ -2435,12 +2435,12 @@ def apply_time_segmentation(n, segments, solver_name="cbc", # get all time-dependent data columns = pd.MultiIndex.from_tuples([],names=['component', 'key', 'asset']) raw = pd.DataFrame(index=n.snapshots,columns=columns) - for component in n.all_components: - pnl = n.pnl(component) - for key in pnl.keys(): - if not pnl[key].empty: - df = pnl[key].copy() - df.columns = pd.MultiIndex.from_product([[component], [key], df.columns]) + for c in n.iterate_components(): + for attr, pnl in c.pnl.items(): + # exclude e_min_pu which is used for SOC of EVs in the morning + if not pnl.empty and attr != 'e_min_pu': + df = pnl.copy() + df.columns = pd.MultiIndex.from_product([[c.name], [attr], df.columns]) raw = pd.concat([raw, df], axis=1) # normalise all time-dependent data @@ -2478,18 +2478,22 @@ def set_temporal_aggregation(n, opts, solver_name): m = re.match(r"^\d+h$", o, re.IGNORECASE) if m is not None: n = average_every_nhours(n, m.group(0)) + break # representive snapshots - m = re.match(r"^\d+sn$", o, re.IGNORECASE) + m = re.match(r"(^\d+)sn$", o, re.IGNORECASE) if m is not None: - sn = int(m.group(0).split("sn")[0]) - logger.info("use every {} snapshot as representative".format(sn)) + sn = int(m[1]) + logger.info(f"use every {sn} snapshot as representative") n.set_snapshots(n.snapshots[::sn]) n.snapshot_weightings *= sn + break # segments with package tsam - if "SEG" in o: - segments = int(o.replace("SEG","")) - logger.info("use temporal segmentation with {} segments".format(segments)) + m = re.match(r"^(\d+)seg$", o, re.IGNORECASE) + if m is not None: + segments = int(m[1]) + logger.info(f"use temporal segmentation with {segments} segments") n = apply_time_segmentation(n, segments, solver_name=solver_name) + break return n #%% if __name__ == "__main__": From 77dfca76e06cb5b5ab941259bfd43b4d028f1dbb Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 14 Sep 2022 16:44:53 +0200 Subject: [PATCH 18/30] uncommented annual dictionaries, set reference year to 2050 --- config.default.yaml | 112 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index c964abce..9824b1f5 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -41,7 +41,7 @@ scenario: # cb40ex0 distributes a carbon budget of 40 GtCO2 following an exponential # decay with initial growth rate 0 planning_horizons: # investment years for myopic and perfect; for overnight, year of cost assumptions can be different and is defined under 'costs' - - 2030 + - 2050 # for example, set to # - 2020 # - 2030 @@ -154,11 +154,11 @@ sector: potential: 0.6 # maximum fraction of urban demand which can be supplied by district heating # increase of today's district heating demand to potential maximum district heating share # progress = 0 means today's district heating share, progress = 1 means maximum fraction of urban demand is supplied by district heating - progress: 1 - # 2020: 0.0 - # 2030: 0.3 - # 2040: 0.6 - # 2050: 1.0 + progress: + 2020: 0.0 + 2030: 0.3 + 2040: 0.6 + 2050: 1.0 district_heating_loss: 0.15 bev_dsm_restriction_value: 0.75 #Set to 0 for no restriction on BEV DSM bev_dsm_restriction_time: 7 #Time at which SOC of BEV has to be dsm_restriction_value @@ -178,16 +178,16 @@ sector: bev_avail_mean: 0.8 v2g: true #allows feed-in to grid from EV battery #what is not EV or FCEV is oil-fuelled ICE - land_transport_fuel_cell_share: 0.15 # 1 means all FCEVs - # 2020: 0 - # 2030: 0.05 - # 2040: 0.1 - # 2050: 0.15 - land_transport_electric_share: 0.85 # 1 means all EVs - # 2020: 0 - # 2030: 0.25 - # 2040: 0.6 - # 2050: 0.85 + land_transport_fuel_cell_share: # 1 means all FCEVs + 2020: 0 + 2030: 0.05 + 2040: 0.1 + 2050: 0.15 + land_transport_electric_share: # 1 means all EVs + 2020: 0 + 2030: 0.25 + 2040: 0.6 + 2050: 0.85 transport_fuel_cell_efficiency: 0.5 transport_internal_combustion_efficiency: 0.3 agriculture_machinery_electric_share: 0 @@ -195,29 +195,29 @@ sector: agriculture_machinery_electric_efficiency: 0.3 # electricity per use shipping_average_efficiency: 0.4 #For conversion of fuel oil to propulsion in 2011 shipping_hydrogen_liquefaction: false # whether to consider liquefaction costs for shipping H2 demands - shipping_hydrogen_share: 1 # 1 means all hydrogen FC - # 2020: 0 - # 2025: 0 - # 2030: 0.05 - # 2035: 0.15 - # 2040: 0.3 - # 2045: 0.6 - # 2050: 1 + shipping_hydrogen_share: # 1 means all hydrogen FC + 2020: 0 + 2025: 0 + 2030: 0.05 + 2035: 0.15 + 2040: 0.3 + 2045: 0.6 + 2050: 1 time_dep_hp_cop: true #time dependent heat pump coefficient of performance heat_pump_sink_T: 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py # conservatively high to cover hot water and space heating in poorly-insulated buildings reduce_space_heat_exogenously: true # reduces space heat demand by a given factor (applied before losses in DH) # this can represent e.g. building renovation, building demolition, or if # the factor is negative: increasing floor area, increased thermal comfort, population growth - reduce_space_heat_exogenously_factor: 0.29 # per unit reduction in space heat demand + reduce_space_heat_exogenously_factor: # per unit reduction in space heat demand # the default factors are determined by the LTS scenario from http://tool.european-calculator.eu/app/buildings/building-types-area/?levers=1ddd4444421213bdbbbddd44444ffffff11f411111221111211l212221 - # 2020: 0.10 # this results in a space heat demand reduction of 10% - # 2025: 0.09 # first heat demand increases compared to 2020 because of larger floor area per capita - # 2030: 0.09 - # 2035: 0.11 - # 2040: 0.16 - # 2045: 0.21 - # 2050: 0.29 + 2020: 0.10 # this results in a space heat demand reduction of 10% + 2025: 0.09 # first heat demand increases compared to 2020 because of larger floor area per capita + 2030: 0.09 + 2035: 0.11 + 2040: 0.16 + 2045: 0.21 + 2050: 0.29 retrofitting : # co-optimises building renovation to reduce space heat demand retro_endogen: false # co-optimise space heat savings cost_factor: 1.0 # weight costs for building renovation @@ -276,32 +276,32 @@ sector: industry: - St_primary_fraction: 0.3 # fraction of steel produced via primary route versus secondary route (scrap+EAF); today fraction is 0.6 - # 2020: 0.6 - # 2025: 0.55 - # 2030: 0.5 - # 2035: 0.45 - # 2040: 0.4 - # 2045: 0.35 - # 2050: 0.3 - DRI_fraction: 1 # fraction of the primary route converted to DRI + EAF - # 2020: 0 - # 2025: 0 - # 2030: 0.05 - # 2035: 0.2 - # 2040: 0.4 - # 2045: 0.7 - # 2050: 1 + St_primary_fraction: # fraction of steel produced via primary route versus secondary route (scrap+EAF); today fraction is 0.6 + 2020: 0.6 + 2025: 0.55 + 2030: 0.5 + 2035: 0.45 + 2040: 0.4 + 2045: 0.35 + 2050: 0.3 + DRI_fraction: # fraction of the primary route converted to DRI + EAF + 2020: 0 + 2025: 0 + 2030: 0.05 + 2035: 0.2 + 2040: 0.4 + 2045: 0.7 + 2050: 1 H2_DRI: 1.7 #H2 consumption in Direct Reduced Iron (DRI), MWh_H2,LHV/ton_Steel from 51kgH2/tSt in Vogl et al (2018) doi:10.1016/j.jclepro.2018.08.279 elec_DRI: 0.322 #electricity consumption in Direct Reduced Iron (DRI) shaft, MWh/tSt HYBRIT brochure https://ssabwebsitecdn.azureedge.net/-/media/hybrit/files/hybrit_brochure.pdf - Al_primary_fraction: 0.2 # fraction of aluminium produced via the primary route versus scrap; today fraction is 0.4 - # 2020: 0.4 - # 2025: 0.375 - # 2030: 0.35 - # 2035: 0.325 - # 2040: 0.3 - # 2045: 0.25 - # 2050: 0.2 + Al_primary_fraction: # fraction of aluminium produced via the primary route versus scrap; today fraction is 0.4 + 2020: 0.4 + 2025: 0.375 + 2030: 0.35 + 2035: 0.325 + 2040: 0.3 + 2045: 0.25 + 2050: 0.2 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) From d53f1fe03185f9531d74d6d29a36899a1cdf4d34 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 21 Sep 2022 07:36:06 +0200 Subject: [PATCH 19/30] update test configs to inclode cost year --- test/config.myopic.yaml | 1 + test/config.overnight.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/test/config.myopic.yaml b/test/config.myopic.yaml index 1a54b937..a44614e3 100644 --- a/test/config.myopic.yaml +++ b/test/config.myopic.yaml @@ -316,6 +316,7 @@ industry: # Material Economics (2019): https://materialeconomics.com/latest-updates/industrial-transformation-2050 costs: + year: 2030 lifetime: 25 #default lifetime # From a Lion Hirth paper, also reflects average of Noothout et al 2016 discountrate: 0.07 diff --git a/test/config.overnight.yaml b/test/config.overnight.yaml index 3764c8fa..61dd9927 100644 --- a/test/config.overnight.yaml +++ b/test/config.overnight.yaml @@ -314,6 +314,7 @@ industry: # Material Economics (2019): https://materialeconomics.com/latest-updates/industrial-transformation-2050 costs: + year: 2030 lifetime: 25 #default lifetime # From a Lion Hirth paper, also reflects average of Noothout et al 2016 discountrate: 0.07 From f13902510010b734c510c38c4cae99356f683058 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 21 Sep 2022 07:41:31 +0200 Subject: [PATCH 20/30] update test configs to include recent biomass additions --- test/config.myopic.yaml | 3 +++ test/config.overnight.yaml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/config.myopic.yaml b/test/config.myopic.yaml index a44614e3..d0a6a918 100644 --- a/test/config.myopic.yaml +++ b/test/config.myopic.yaml @@ -262,6 +262,9 @@ sector: biomass_transport: false # biomass transport between nodes conventional_generation: # generator : carrier OCGT: gas + biomass_boiler: false + biomass_to_liquid: false + biosng: false industry: diff --git a/test/config.overnight.yaml b/test/config.overnight.yaml index 61dd9927..1dc314dd 100644 --- a/test/config.overnight.yaml +++ b/test/config.overnight.yaml @@ -260,6 +260,9 @@ sector: biomass_transport: false # biomass transport between nodes conventional_generation: # generator : carrier OCGT: gas + biomass_boiler: false + biomass_to_liquid: false + biosng: false industry: From 76065624496203a49c8d41b9aa751fc49114438f Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 28 Sep 2022 16:09:53 +0200 Subject: [PATCH 21/30] update sector opt for co2 seq potential --- config.default.yaml | 2 +- scripts/solve_network.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index 9824b1f5..3e6576e3 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -33,7 +33,7 @@ scenario: # A for agriculture, forestry and fishing # solar+c0.5 reduces the capital cost of solar to 50\% of reference value # solar+p3 multiplies the available installable potential by factor 3 - # co2 stored+e2 multiplies the potential of CO2 sequestration by a factor 2 + # seq2 (or e.g. seq0p5) multiplies the potential of CO2 sequestration by a factor 2 (or e.g. 0.5) # dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv # for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative # emissions throughout the transition path in the timeframe determined by the diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 563d8c29..626cc2a7 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -227,7 +227,8 @@ def add_co2_sequestration_limit(n, sns): limit = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6 for o in opts: if not "seq" in o: continue - limit = float(o[o.find("seq")+3:]) + factor = float(o[o.find("seq")+3:]) + limit *= factor break name = 'co2_sequestration_limit' From 9a74e3d699e9addd1476435dc2796251056c0b5d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Thu, 29 Sep 2022 09:12:36 +0200 Subject: [PATCH 22/30] set co2seq to limit of sequestration potential --- config.default.yaml | 2 +- scripts/solve_network.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index 3e6576e3..bab9d483 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -33,7 +33,7 @@ scenario: # A for agriculture, forestry and fishing # solar+c0.5 reduces the capital cost of solar to 50\% of reference value # solar+p3 multiplies the available installable potential by factor 3 - # seq2 (or e.g. seq0p5) multiplies the potential of CO2 sequestration by a factor 2 (or e.g. 0.5) + # seq400 sets the potential of CO2 sequestration to 400 Mt CO2 per year # dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv # for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative # emissions throughout the transition path in the timeframe determined by the diff --git a/scripts/solve_network.py b/scripts/solve_network.py index 626cc2a7..93442a41 100644 --- a/scripts/solve_network.py +++ b/scripts/solve_network.py @@ -227,8 +227,7 @@ def add_co2_sequestration_limit(n, sns): limit = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6 for o in opts: if not "seq" in o: continue - factor = float(o[o.find("seq")+3:]) - limit *= factor + limit = float(o[o.find("seq")+3:]) * 1e6 break name = 'co2_sequestration_limit' From 4f7b2b9868d14864f2dcf8f8b674c6118a777590 Mon Sep 17 00:00:00 2001 From: nnhjy Date: Thu, 6 Oct 2022 18:43:41 +0200 Subject: [PATCH 23/30] untrack *.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index aa531c3d..a7049efe 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ config.yaml doc/_build *.xls + +*.geojson From 80868e5f0fbbfabe2b0a57bf677649c3b8561a75 Mon Sep 17 00:00:00 2001 From: "Huang, Jiangyi" Date: Thu, 3 Nov 2022 13:46:20 +0100 Subject: [PATCH 24/30] Update make_summary.py The value for `na_action` needs to be "ignore" or "None". --- scripts/make_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/make_summary.py b/scripts/make_summary.py index 8d5f4e48..f9c74c89 100644 --- a/scripts/make_summary.py +++ b/scripts/make_summary.py @@ -273,7 +273,7 @@ def calculate_supply(n, label, supply): for end in [col[3:] for col in c.df.columns if col[:3] == "bus"]: - items = c.df.index[c.df["bus" + end].map(bus_map, na_action=False)] + items = c.df.index[c.df["bus" + end].map(bus_map, na_action=None)] if len(items) == 0: continue @@ -318,7 +318,7 @@ def calculate_supply_energy(n, label, supply_energy): for end in [col[3:] for col in c.df.columns if col[:3] == "bus"]: - items = c.df.index[c.df["bus" + str(end)].map(bus_map, na_action=False)] + items = c.df.index[c.df["bus" + str(end)].map(bus_map, na_action=None)] if len(items) == 0: continue From 6c51152ab5280da05fd3a3eaa05d54ed0b67ad8b Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 10 Nov 2022 15:29:52 +0100 Subject: [PATCH 25/30] config: shipping now defaults to (synthetic) oil demand not hydrogen --- config.default.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index 4cf7f5fa..4078ee01 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -195,14 +195,7 @@ sector: agriculture_machinery_electric_efficiency: 0.3 # electricity per use shipping_average_efficiency: 0.4 #For conversion of fuel oil to propulsion in 2011 shipping_hydrogen_liquefaction: false # whether to consider liquefaction costs for shipping H2 demands - shipping_hydrogen_share: # 1 means all hydrogen FC - 2020: 0 - 2025: 0 - 2030: 0.05 - 2035: 0.15 - 2040: 0.3 - 2045: 0.6 - 2050: 1 + shipping_hydrogen_share: 0 time_dep_hp_cop: true #time dependent heat pump coefficient of performance heat_pump_sink_T: 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py # conservatively high to cover hot water and space heating in poorly-insulated buildings From 3815d0da619ac1c4d3ae37ce48d6a2992862e652 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 10 Nov 2022 15:31:25 +0100 Subject: [PATCH 26/30] add release note --- doc/release_notes.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 1eb33652..51c533f4 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -80,6 +80,8 @@ incorporates retrofitting options to hydrogen. * Updated `data bundle `_ that includes the hydrogan salt cavern storage potentials. +* Shipping demand now defaults to (synthetic) oil rather than liquefied hydrogen until 2050. + **Bugfixes** * The CO2 sequestration limit implemented as GlobalConstraint (introduced in the previous version) From ae8f601f0a1ed492da67a9986cd35e702a5e35d4 Mon Sep 17 00:00:00 2001 From: euronion <42553970+euronion@users.noreply.github.com> Date: Mon, 14 Nov 2022 09:38:09 +0100 Subject: [PATCH 27/30] Fix relative imports from subworkflow PyPSA-EUR --- Snakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Snakefile b/Snakefile index 6b195d12..0b95d30d 100644 --- a/Snakefile +++ b/Snakefile @@ -256,9 +256,9 @@ rule build_biomass_potentials: enspreso_biomass=HTTP.remote("https://cidportal.jrc.ec.europa.eu/ftp/jrc-opendata/ENSPRESO/ENSPRESO_BIOMASS.xlsx", keep_local=True), nuts2="data/nuts/NUTS_RG_10M_2013_4326_LEVL_2.geojson", # https://gisco-services.ec.europa.eu/distribution/v2/nuts/download/#nuts21 regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"), - nuts3_population="../pypsa-eur/data/bundle/nama_10r_3popgdp.tsv.gz", - swiss_cantons="../pypsa-eur/data/bundle/ch_cantons.csv", - swiss_population="../pypsa-eur/data/bundle/je-e-21.03.02.xls", + nuts3_population=pypsaeur("data/bundle/nama_10r_3popgdp.tsv.gz"), + swiss_cantons=pypsaeur("data/bundle/ch_cantons.csv"), + swiss_population=pypsaeur("data/bundle/je-e-21.03.02.xls"), country_shapes=pypsaeur('resources/country_shapes.geojson') output: biomass_potentials_all='resources/biomass_potentials_all_s{simpl}_{clusters}.csv', From e1e18a50e75fa7b88638b7fdcf595a1b103ce456 Mon Sep 17 00:00:00 2001 From: Fabian Hofmann Date: Wed, 16 Nov 2022 12:18:58 +0100 Subject: [PATCH 28/30] Fix input listing in Snakefile --- Snakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Snakefile b/Snakefile index 5ecba666..5dc26dd8 100644 --- a/Snakefile +++ b/Snakefile @@ -598,8 +598,8 @@ if config["foresight"] == "overnight": overrides="data/override_component_attrs", network=RDIR + "/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc", costs=CDIR + "costs_{}.csv".format(config['costs']['year']), - config=SDIR + '/configs/config.yaml' - env=SDIR + '/configs/environment.yaml' + config=SDIR + '/configs/config.yaml', + env=SDIR + '/configs/environment.yaml', output: RDIR + "/postnetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc" shadow: "shallow" log: From e25fb02e9cff7249ae2f058e5686b815c0a0b3f5 Mon Sep 17 00:00:00 2001 From: Fabian Hofmann Date: Wed, 16 Nov 2022 12:44:28 +0100 Subject: [PATCH 29/30] CI: make coincbc and ipopt installed by conda --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f528c28d..da984fd3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,7 +64,7 @@ jobs: - name: Add solver to environment run: | - echo -e " - coincbc\n - ipopt<3.13.3" >> ../pypsa-eur/envs/environment.yaml + echo -e "- coincbc\n- ipopt<3.13.3" >> ../pypsa-eur/envs/environment.yaml - name: Setup Mambaforge uses: conda-incubator/setup-miniconda@v2 From df4eb85a19078e32709d66f9b868222bdebc925c Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 18 Nov 2022 09:08:07 +0100 Subject: [PATCH 30/30] fix typos --- doc/data.csv | 4 ++-- doc/installation.rst | 2 +- doc/release_notes.rst | 2 +- doc/spatial_resolution.rst | 2 +- scripts/add_existing_baseyear.py | 6 +++--- scripts/build_industry_sector_ratios.py | 4 ++-- scripts/build_retro_cost.py | 16 ++++++++-------- scripts/helper.py | 2 +- scripts/plot_summary.py | 4 ++-- scripts/prepare_sector_network.py | 6 +++--- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/data.csv b/doc/data.csv index 2c6ac8c8..49fbc536 100644 --- a/doc/data.csv +++ b/doc/data.csv @@ -23,7 +23,7 @@ Floor area missing in hotmaps building stock data,floor_area_missing.csv,unknown Comparative level investment,comparative_level_investment.csv,Eurostat,https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Comparative_price_levels_for_investment Electricity taxes,electricity_taxes_eu.csv,Eurostat,https://appsso.eurostat.ec.europa.eu/nui/show.do?dataset=nrg_pc_204&lang=en Building topologies and corresponding standard values,tabula-calculator-calcsetbuilding.csv,unknown,https://episcope.eu/fileadmin/tabula/public/calc/tabula-calculator.xlsx -Retrofitting thermal envelope costs for Germany,retro_cost_germany.csv,unkown,https://www.iwu.de/forschung/handlungslogiken/kosten-energierelevanter-bau-und-anlagenteile-bei-modernisierung/ +Retrofitting thermal envelope costs for Germany,retro_cost_germany.csv,unknown,https://www.iwu.de/forschung/handlungslogiken/kosten-energierelevanter-bau-und-anlagenteile-bei-modernisierung/ District heating most countries,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees,, -District heating missing countries,district_heat_share.csv,unkown,https://www.euroheat.org/knowledge-hub/country-profiles,, +District heating missing countries,district_heat_share.csv,unknown,https://www.euroheat.org/knowledge-hub/country-profiles,, diff --git a/doc/installation.rst b/doc/installation.rst index 6637cdfd..f5cb7c7a 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -54,7 +54,7 @@ The requirements are the same as `PyPSA-Eur xarray version >= 0.15.1, you will need the latest master branch of atlite version 0.0.2. -You can create an enviroment using the environment.yaml file in pypsa-eur/envs: +You can create an environment using the environment.yaml file in pypsa-eur/envs: .. code:: bash diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 51c533f4..51e9b43d 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -28,7 +28,7 @@ incorporates retrofitting options to hydrogen. * New rule ``cluster_gas_network`` that clusters the gas transmission network data to the model resolution. Cross-regional pipeline capacities are aggregated - (while pressure and diameter compability is ignored), intra-regional pipelines + (while pressure and diameter compatibility is ignored), intra-regional pipelines are dropped. Lengths are recalculated based on the regions' centroids. * With the option ``sector: gas_network:``, the existing gas network is diff --git a/doc/spatial_resolution.rst b/doc/spatial_resolution.rst index d410065a..ff11cc39 100644 --- a/doc/spatial_resolution.rst +++ b/doc/spatial_resolution.rst @@ -15,7 +15,7 @@ The total number of nodes for Europe is set in the ``config.yaml`` file under ``clusters``. The number of nodes can vary between 37, the number of independent countries / synchronous areas, and several hundred. With 200-300 nodes the model needs 100-150 GB RAM to solve -with a commerical solver like Gurobi. +with a commercial solver like Gurobi. Not all of the sectors are at the full nodal resolution, and some diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 0c60288a..e5b7e1d1 100644 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -165,11 +165,11 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas df_agg.loc[biomass_i, 'DateOut'] = df_agg.loc[biomass_i, 'DateOut'].fillna(dateout) - # drop assets which are already phased out / decomissioned + # drop assets which are already phased out / decommissioned phased_out = df_agg[df_agg["DateOut"] Euro/MWh capital_cost = retro_data.loc[(ct, sec), ("cost")] * floor_area_node / \ @@ -2565,7 +2565,7 @@ def set_temporal_aggregation(n, opts, solver_name): if m is not None: n = average_every_nhours(n, m.group(0)) break - # representive snapshots + # representative snapshots m = re.match(r"(^\d+)sn$", o, re.IGNORECASE) if m is not None: sn = int(m[1])