diff --git a/config/config.default.yaml b/config/config.default.yaml index 91e4af8d..d613aef9 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -693,8 +693,10 @@ sector: conventional_generation: OCGT: gas biomass_to_liquid: false + biomass_to_liquid_cc: false electrobiofuels: false biosng: false + biosng_cc: false bioH2: false municipal_solid_waste: false limit_max_growth: diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index 08c903b8..64f0a6c0 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -167,7 +167,9 @@ biomass_transport,--,"{true, false}",Add option for transporting solid biomass b 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. 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 +biosng_cc,--,"{true, false}",Add option for transforming solid biomass into synthesis gas with the same properties as natural gas with carbon capture bioH2,--,"{true, false}",Add option for transforming solid biomass into hydrogen with carbon capture municipal_solid_waste,--,"{true, false}",Add option for municipal solid waste limit_max_growth,,, diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 8859c41d..dca98d37 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -68,6 +68,11 @@ Upcoming Release defaults to 10 km. Previously the distance to the region's centroid was used, which is not practical when the regions are already aggregated. +* Added options ``biosng_cc`` and ``biomass_to_liquid_cc`` to separate the base + technology from the option to capture carbon from it. + +* Added 98% imperfect capture rate of Allam cycle gas turbine. + PyPSA-Eur 0.13.0 (13th September 2024) ====================================== diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f088b97c..89b4f6e1 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -132,9 +132,9 @@ def define_spatial(nodes, options): # ammonia - if options.get("ammonia"): + if options["ammonia"]: spatial.ammonia = SimpleNamespace() - if options.get("ammonia") == "regional": + if options["ammonia"] == "regional": spatial.ammonia.nodes = nodes + " NH3" spatial.ammonia.locations = nodes else: @@ -519,7 +519,7 @@ def add_carrier_buses(n, carrier, nodes=None): ) fossils = ["coal", "gas", "oil", "lignite"] - if options.get("fossil_fuels", True) and carrier in fossils: + if options["fossil_fuels"] and carrier in fossils: suffix = "" @@ -750,7 +750,7 @@ def add_co2_network(n, costs): ) -def add_allam(n, costs): +def add_allam_gas(n, costs): logger.info("Adding Allam cycle gas power plants.") nodes = pop_layout.index @@ -758,17 +758,18 @@ def add_allam(n, costs): n.madd( "Link", nodes, - suffix=" allam", + suffix=" allam gas", bus0=spatial.gas.df.loc[nodes, "nodes"].values, bus1=nodes, bus2=spatial.co2.df.loc[nodes, "nodes"].values, - carrier="allam", + bus3="co2 atmosphere", + carrier="allam gas", p_nom_extendable=True, - # TODO: add costs to technology-data capital_cost=costs.at["allam", "fixed"] * costs.at["allam", "efficiency"], marginal_cost=costs.at["allam", "VOM"] * costs.at["allam", "efficiency"], efficiency=costs.at["allam", "efficiency"], - efficiency2=costs.at["gas", "CO2 intensity"], + efficiency2=0.98 * costs.at["gas", "CO2 intensity"], + efficiency3=0.02 * costs.at["gas", "CO2 intensity"], lifetime=costs.at["allam", "lifetime"], ) @@ -823,8 +824,10 @@ def add_biomass_to_methanol_cc(n, costs): ) -def add_methanol_to_power(n, costs, types={}): - # TODO: add costs to technology-data +def add_methanol_to_power(n, costs, types=None): + + if types is None: + types = {} nodes = pop_layout.index @@ -1121,8 +1124,7 @@ def add_generation(n, costs): nodes = pop_layout.index - fallback = {"OCGT": "gas"} - conventionals = options.get("conventional_generation", fallback) + conventionals = options["conventional_generation"] for generator, carrier in conventionals.items(): carrier_nodes = vars(spatial)[carrier].nodes @@ -1564,7 +1566,7 @@ def add_storage_and_grids(n, costs): complement_edges["length"] = complement_edges.apply(haversine, axis=1) # apply k_edge_augmentation weighted by length of complement edges - k_edge = options.get("gas_network_connectivity_upgrade", 3) + k_edge = options["gas_network_connectivity_upgrade"] if augmentation := list( k_edge_augmentation(G, k_edge, avail=complement_edges.values) ): @@ -1612,7 +1614,7 @@ def add_storage_and_grids(n, costs): lifetime=costs.at["H2 (g) pipeline repurposed", "lifetime"], ) - if options.get("H2_network", True): + if options["H2_network"]: logger.info("Add options for new hydrogen pipelines.") h2_pipes = create_network_topology( @@ -1682,7 +1684,7 @@ def add_storage_and_grids(n, costs): bus2=spatial.co2.nodes, p_nom_extendable=True, carrier="Sabatier", - p_min_pu=options.get("min_part_load_methanation", 0), + p_min_pu=options["min_part_load_methanation"], efficiency=costs.at["methanation", "efficiency"], efficiency2=-costs.at["methanation", "efficiency"] * costs.at["gas", "CO2 intensity"], @@ -1691,7 +1693,7 @@ def add_storage_and_grids(n, costs): lifetime=costs.at["methanation", "lifetime"], ) - if options.get("coal_cc"): + if options["coal_cc"]: n.madd( "Link", spatial.nodes, @@ -1835,7 +1837,7 @@ def add_EVs( p_set=profile, ) - p_nom = number_cars * options.get("bev_charge_rate", 0.011) * electric_share + p_nom = number_cars * options["bev_charge_rate"] * electric_share n.madd( "Link", @@ -1847,7 +1849,7 @@ def add_EVs( carrier="BEV charger", p_max_pu=avail_profile[spatial.nodes], lifetime=1, - efficiency=options.get("bev_charge_efficiency", 0.9), + efficiency=options["bev_charge_efficiency"], ) if options["v2g"]: @@ -1861,13 +1863,13 @@ def add_EVs( carrier="V2G", p_max_pu=avail_profile[spatial.nodes], lifetime=1, - efficiency=options.get("bev_charge_efficiency", 0.9), + efficiency=options["bev_charge_efficiency"], ) if options["bev_dsm"]: e_nom = ( number_cars - * options.get("bev_energy", 0.05) + * options["bev_energy"] * options["bev_availability"] * electric_share ) @@ -2116,7 +2118,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray): unit="MWh_th", ) - if heat_system == HeatSystem.URBAN_CENTRAL and options.get("central_heat_vent"): + if heat_system == HeatSystem.URBAN_CENTRAL and options["central_heat_vent"]: n.madd( "Generator", nodes + f" {heat_system} heat vent", @@ -2786,7 +2788,7 @@ def add_biomass(n, costs): p_nom_extendable=True, ) - if options.get("biogas_upgrading_cc"): + if options["biogas_upgrading_cc"]: # Assuming for costs that the CO2 from upgrading is pure, such as in amine scrubbing. I.e., with and without CC is # equivalent. Adding biomass CHP capture because biogas is often small-scale and decentral so further # from e.g. CO2 grid or buyers. This is a proxy for the added cost for e.g. a raw biogas pipeline to a central upgrading facility @@ -3034,6 +3036,8 @@ def add_biomass(n, costs): marginal_cost=costs.at["BtL", "VOM"] * costs.at["BtL", "efficiency"], ) + # Solid biomass to liquid fuel with carbon capture + if options["biomass_to_liquid_cc"]: # Assuming that acid gas removal (incl. CO2) from syngas i performed with Rectisol # process (Methanol) and that electricity demand for this is included in the base process n.madd( @@ -3044,7 +3048,7 @@ def add_biomass(n, costs): bus1=spatial.oil.nodes, bus2="co2 atmosphere", bus3=spatial.co2.nodes, - carrier="biomass to liquid", + carrier="biomass to liquid CC", lifetime=costs.at["BtL", "lifetime"], efficiency=costs.at["BtL", "efficiency"], efficiency2=-costs.at["solid biomass", "CO2 intensity"] @@ -3112,6 +3116,8 @@ def add_biomass(n, costs): marginal_cost=costs.at["BioSNG", "VOM"] * costs.at["BioSNG", "efficiency"], ) + # BioSNG from solid biomass with carbon capture + if options["biosng_cc"]: # Assuming that acid gas removal (incl. CO2) from syngas i performed with Rectisol # process (Methanol) and that electricity demand for this is included in the base process n.madd( @@ -3122,7 +3128,7 @@ def add_biomass(n, costs): bus1=spatial.gas.nodes, bus2=spatial.co2.nodes, bus3="co2 atmosphere", - carrier="BioSNG", + carrier="BioSNG CC", lifetime=costs.at["BioSNG", "lifetime"], efficiency=costs.at["BioSNG", "efficiency"], efficiency2=costs.at["BioSNG", "CO2 stored"] @@ -3162,10 +3168,6 @@ def add_biomass(n, costs): * costs.at["solid biomass to hydrogen", "efficiency"] + costs.at["biomass CHP capture", "fixed"] * costs.at["solid biomass", "CO2 intensity"], - overnight_cost=costs.at["solid biomass to hydrogen", "investment"] - * costs.at["solid biomass to hydrogen", "efficiency"] - + costs.at["biomass CHP capture", "investment"] - * costs.at["solid biomass", "CO2 intensity"], marginal_cost=0.0, ) @@ -3356,7 +3358,7 @@ def add_industry(n, costs): bus3=spatial.co2.nodes, carrier="methanolisation", p_nom_extendable=True, - p_min_pu=options.get("min_part_load_methanolisation", 0), + p_min_pu=options["min_part_load_methanolisation"], capital_cost=costs.at["methanolisation", "fixed"] * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a marginal_cost=options["MWh_MeOH_per_MWh_H2"] @@ -3552,12 +3554,12 @@ def add_industry(n, costs): efficiency2=-costs.at["oil", "CO2 intensity"] * costs.at["Fischer-Tropsch", "efficiency"], p_nom_extendable=True, - p_min_pu=options.get("min_part_load_fischer_tropsch", 0), + p_min_pu=options["min_part_load_fischer_tropsch"], lifetime=costs.at["Fischer-Tropsch", "lifetime"], ) # naphtha - demand_factor = options.get("HVC_demand_factor", 1) + demand_factor = options["HVC_demand_factor"] if demand_factor != 1: logger.warning(f"Changing HVC demand by {demand_factor*100-100:+.2f}%.") @@ -3630,7 +3632,7 @@ def add_industry(n, costs): efficiency3=process_co2_per_naphtha, ) - if options.get("biomass", True) and options["municipal_solid_waste"]: + if options["biomass"] and options["municipal_solid_waste"]: n.madd( "Link", spatial.msw.locations, @@ -3722,7 +3724,7 @@ def add_industry(n, costs): ) # aviation - demand_factor = options.get("aviation_demand_factor", 1) + demand_factor = options["aviation_demand_factor"] if demand_factor != 1: logger.warning(f"Changing aviation demand by {demand_factor*100-100:+.2f}%.") @@ -3862,7 +3864,7 @@ def add_industry(n, costs): lifetime=costs.at["cement capture", "lifetime"], ) - if options.get("ammonia"): + if options["ammonia"]: if options["ammonia"] == "regional": p_set = ( industrial_demand.loc[spatial.ammonia.locations, "ammonia"].rename( @@ -4639,8 +4641,8 @@ if __name__ == "__main__": if options["co2network"]: add_co2_network(n, costs) - if options["allam_cycle"]: - add_allam(n, costs) + if options["allam_cycle_gas"]: + add_allam_gas(n, costs) n = set_temporal_aggregation( n, snakemake.params.time_resolution, snakemake.input.snapshot_weightings @@ -4701,7 +4703,7 @@ if __name__ == "__main__": snakemake.params.planning_horizons[0] == investment_year ) - if options.get("cluster_heat_buses", False) and not first_year_myopic: + if options["cluster_heat_buses"] and not first_year_myopic: cluster_heat_buses(n) maybe_adjust_costs_and_potentials(