From 3fd2e51e8d093ebe8f9223ba3ac214858c59dc6d Mon Sep 17 00:00:00 2001 From: martacki Date: Wed, 16 Oct 2024 11:12:11 +0200 Subject: [PATCH 1/4] keep existing conventionals in sector network --- config/config.default.yaml | 5 ++- rules/build_sector.smk | 1 + scripts/prepare_sector_network.py | 75 +++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/config/config.default.yaml b/config/config.default.yaml index a475c6fdf..92b48f57f 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -360,7 +360,6 @@ pypsa_eur: - solar-hsat - solar - ror - - nuclear StorageUnit: - PHS - hydro @@ -693,6 +692,10 @@ sector: biogas_upgrading_cc: false conventional_generation: OCGT: gas + nuclear: uranium + coal: coal + lignite: lignite + keep_existing_capacities: false biomass_to_liquid: false biomass_to_liquid_cc: false electrobiofuels: false diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 88e29dbe1..36857e91c 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -1023,6 +1023,7 @@ rule prepare_sector_network: countries=config_provider("countries"), adjustments=config_provider("adjustments", "sector"), emissions_scope=config_provider("energy", "emissions"), + electricity=config_provider("electricity"), biomass=config_provider("biomass"), RDIR=RDIR, heat_pump_sources=config_provider("sector", "heat_pump_sources"), diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f65e2d7b3..eb843cc05 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -518,7 +518,7 @@ def add_carrier_buses(n, carrier, nodes=None): capital_cost=capital_cost, ) - fossils = ["coal", "gas", "oil", "lignite"] + fossils = ["coal", "gas", "oil", "lignite", "uranium"] if options["fossil_fuels"] and carrier in fossils: suffix = "" @@ -1106,18 +1106,21 @@ def annuity_factor(v): return costs -def add_generation(n, costs): +def add_generation(n, costs, existing_capacities=0, existing_efficiencies=None, existing_nodes=None): logger.info("Adding electricity generation") - nodes = pop_layout.index - - conventionals = options["conventional_generation"] + conventionals = options.get("conventional_generation", {}) for generator, carrier in conventionals.items(): carrier_nodes = vars(spatial)[carrier].nodes add_carrier_buses(n, carrier, carrier_nodes) + if existing_nodes is None: + nodes = pop_layout.index + else: + nodes = existing_nodes[generator] + n.madd( "Link", nodes + " " + generator, @@ -1128,14 +1131,58 @@ def add_generation(n, costs): * costs.at[generator, "VOM"], # NB: VOM is per MWel capital_cost=costs.at[generator, "efficiency"] * costs.at[generator, "fixed"], # NB: fixed cost is per MWel - p_nom_extendable=True, + p_nom_extendable=( + True + if generator + in snakemake.params.electricity.get("extendable_carriers", dict()).get( + "Generator", list() + ) + else False + ), + p_nom=( + existing_capacities[generator] / existing_efficiencies[generator] + if not existing_capacities == 0 else 0 + ), # NB: existing capacities are MWel + p_max_pu = 0.7 if carrier == "uranium" else 1, # be conservative for nuclear (maintance or unplanned shut downs) carrier=generator, - efficiency=costs.at[generator, "efficiency"], + efficiency=( + existing_efficiencies[generator] + if existing_efficiencies is not None + else costs.at[generator, "efficiency"] + ), efficiency2=costs.at[carrier, "CO2 intensity"], lifetime=costs.at[generator, "lifetime"], ) +def get_capacities_from_elec(n, carriers, component): + """ + Gets capacities and efficiencies for {carrier} in n.{component} that were + previously assigned in add_electricity. + """ + component_list = ["generators", "storage_units", "links", "stores"] + component_dict = {name: getattr(n, name) for name in component_list} + e_nom_carriers = ["stores"] + nom_col = {x: "e_nom" if x in e_nom_carriers else "p_nom" for x in component_list} + eff_col = "efficiency" + + capacity_dict = {} + efficiency_dict = {} + node_dict = {} + for carrier in carriers: + capacity_dict[carrier] = component_dict[component].query("carrier in @carrier")[ + nom_col[component] + ] + efficiency_dict[carrier] = component_dict[component].query( + "carrier in @carrier" + )[eff_col] + node_dict[carrier] = component_dict[component].query("carrier in @carrier")[ + "bus" + ] + + return capacity_dict, efficiency_dict, node_dict + + def add_ammonia(n, costs): logger.info("Adding ammonia carrier with synthesis, cracking and storage") @@ -4570,6 +4617,16 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): ) pop_weighted_energy_totals.update(pop_weighted_heat_totals) + + if options.get("keep_existing_capacities", False): + existing_capacities, existing_efficiencies, existing_nodes = get_capacities_from_elec( + n, + carriers=options.get("conventional_generation").keys(), + component="generators", + ) + else: + existing_capacities, existing_efficiencies, existing_nodes = 0, None, None + landfall_lengths = { tech: settings["landfall_length"] for tech, settings in snakemake.params.renewable.items() @@ -4583,7 +4640,7 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): spatial = define_spatial(pop_layout.index, options) - if snakemake.params.foresight in ["myopic", "perfect"]: + if snakemake.params.foresight in ["overnight", "myopic", "perfect"]: add_lifetime_wind_solar(n, costs) conventional = snakemake.params.conventional_carriers @@ -4594,7 +4651,7 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): add_co2_tracking(n, costs, options) - add_generation(n, costs) + add_generation(n, costs, existing_capacities, existing_efficiencies, existing_nodes) add_storage_and_grids(n, costs) From 57985c1677453ad5f530de0fdb94079baa4ee085 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 09:28:33 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/prepare_sector_network.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 71b66ad03..2b54c7d33 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1099,7 +1099,9 @@ def annuity_factor(v): return costs -def add_generation(n, costs, existing_capacities=0, existing_efficiencies=None, existing_nodes=None): +def add_generation( + n, costs, existing_capacities=0, existing_efficiencies=None, existing_nodes=None +): logger.info("Adding electricity generation") conventionals = options.get("conventional_generation", {}) @@ -1134,9 +1136,12 @@ def add_generation(n, costs, existing_capacities=0, existing_efficiencies=None, ), p_nom=( existing_capacities[generator] / existing_efficiencies[generator] - if not existing_capacities == 0 else 0 - ), # NB: existing capacities are MWel - p_max_pu = 0.7 if carrier == "uranium" else 1, # be conservative for nuclear (maintance or unplanned shut downs) + if not existing_capacities == 0 + else 0 + ), # NB: existing capacities are MWel + p_max_pu=( + 0.7 if carrier == "uranium" else 1 + ), # be conservative for nuclear (maintance or unplanned shut downs) carrier=generator, efficiency=( existing_efficiencies[generator] @@ -4597,12 +4602,13 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): ) pop_weighted_energy_totals.update(pop_weighted_heat_totals) - if options.get("keep_existing_capacities", False): - existing_capacities, existing_efficiencies, existing_nodes = get_capacities_from_elec( - n, - carriers=options.get("conventional_generation").keys(), - component="generators", + existing_capacities, existing_efficiencies, existing_nodes = ( + get_capacities_from_elec( + n, + carriers=options.get("conventional_generation").keys(), + component="generators", + ) ) else: existing_capacities, existing_efficiencies, existing_nodes = 0, None, None From a6eb47f07cfc9bcc6301b9205d432409311a54d2 Mon Sep 17 00:00:00 2001 From: martacki Date: Wed, 16 Oct 2024 11:37:37 +0200 Subject: [PATCH 3/4] release_notes and configtables --- doc/configtables/sector.csv | 1 + doc/release_notes.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index eeee192eb..c3ca5dc34 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -167,6 +167,7 @@ biomass_spatial,--,"{true, false}",Add option for resolving biomass demand regio biomass_transport,--,"{true, false}",Add option for transporting solid biomass between nodes biogas_upgrading_cc,--,"{true, false}",Add option to capture CO2 from biomass upgrading conventional_generation,,,Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel. +keep_existing_capacities,--,"{true, false}",Keep existing conventional carriers from the power model. Defaults to false. biomass_to_liquid,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil biomass_to_liquid_cc,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil with carbon capture biosng,--,"{true, false}",Add option for transforming solid biomass into synthesis gas with the same properties as natural gas diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 7bc693504..3f0d475d5 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -78,6 +78,8 @@ Upcoming Release * Bugfix: demand for ammonia was double-counted at current/near-term planning horizons when ``sector['ammonia']`` was set to ``True``. +* Enable retaining exisiting conventional capacities added in the power only model for sector-coupeled applications. + PyPSA-Eur 0.13.0 (13th September 2024) ====================================== From 03aa47dbebc478f1d6574db21ea1b2ff8245a442 Mon Sep 17 00:00:00 2001 From: martacki Date: Fri, 18 Oct 2024 14:37:03 +0200 Subject: [PATCH 4/4] fix typos --- doc/release_notes.rst | 2 +- scripts/prepare_sector_network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 3f0d475d5..a627e397e 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -78,7 +78,7 @@ Upcoming Release * Bugfix: demand for ammonia was double-counted at current/near-term planning horizons when ``sector['ammonia']`` was set to ``True``. -* Enable retaining exisiting conventional capacities added in the power only model for sector-coupeled applications. +* Enable retaining existing conventional capacities added in the power only model for sector-coupeled applications. PyPSA-Eur 0.13.0 (13th September 2024) ====================================== diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 2b54c7d33..ba2ffe129 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1141,7 +1141,7 @@ def add_generation( ), # NB: existing capacities are MWel p_max_pu=( 0.7 if carrier == "uranium" else 1 - ), # be conservative for nuclear (maintance or unplanned shut downs) + ), # be conservative for nuclear (maintenance or unplanned shut downs) carrier=generator, efficiency=( existing_efficiencies[generator]