options: biosng_cc, biomass_to_liquid_cc, 98% capture rate Allam gas,… (#1298)

* options: biosng_cc, biomass_to_liquid_cc, 98% capture rate Allam gas, avoid option.get

* fix duplicated bus port
This commit is contained in:
Fabian Neumann 2024-09-16 15:04:24 +02:00 committed by GitHub
parent df71b1a64c
commit d8f8a828f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 48 additions and 37 deletions

View File

@ -693,8 +693,10 @@ sector:
conventional_generation: conventional_generation:
OCGT: gas OCGT: gas
biomass_to_liquid: false biomass_to_liquid: false
biomass_to_liquid_cc: false
electrobiofuels: false electrobiofuels: false
biosng: false biosng: false
biosng_cc: false
bioH2: false bioH2: false
municipal_solid_waste: false municipal_solid_waste: false
limit_max_growth: limit_max_growth:

View File

@ -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 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. 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,--,"{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,--,"{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 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 municipal_solid_waste,--,"{true, false}",Add option for municipal solid waste
limit_max_growth,,, limit_max_growth,,,

1 Unit Values Description
167 biogas_upgrading_cc -- {true, false} Add option to capture CO2 from biomass upgrading
168 conventional_generation Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel.
169 biomass_to_liquid -- {true, false} Add option for transforming solid biomass into liquid fuel with the same properties as oil
170 biomass_to_liquid_cc -- {true, false} Add option for transforming solid biomass into liquid fuel with the same properties as oil with carbon capture
171 biosng -- {true, false} Add option for transforming solid biomass into synthesis gas with the same properties as natural gas
172 biosng_cc -- {true, false} Add option for transforming solid biomass into synthesis gas with the same properties as natural gas with carbon capture
173 bioH2 -- {true, false} Add option for transforming solid biomass into hydrogen with carbon capture
174 municipal_solid_waste -- {true, false} Add option for municipal solid waste
175 limit_max_growth

View File

@ -68,6 +68,11 @@ Upcoming Release
defaults to 10 km. Previously the distance to the region's centroid was defaults to 10 km. Previously the distance to the region's centroid was
used, which is not practical when the regions are already aggregated. 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) PyPSA-Eur 0.13.0 (13th September 2024)
====================================== ======================================

View File

@ -132,9 +132,9 @@ def define_spatial(nodes, options):
# ammonia # ammonia
if options.get("ammonia"): if options["ammonia"]:
spatial.ammonia = SimpleNamespace() spatial.ammonia = SimpleNamespace()
if options.get("ammonia") == "regional": if options["ammonia"] == "regional":
spatial.ammonia.nodes = nodes + " NH3" spatial.ammonia.nodes = nodes + " NH3"
spatial.ammonia.locations = nodes spatial.ammonia.locations = nodes
else: else:
@ -519,7 +519,7 @@ def add_carrier_buses(n, carrier, nodes=None):
) )
fossils = ["coal", "gas", "oil", "lignite"] fossils = ["coal", "gas", "oil", "lignite"]
if options.get("fossil_fuels", True) and carrier in fossils: if options["fossil_fuels"] and carrier in fossils:
suffix = "" 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.") logger.info("Adding Allam cycle gas power plants.")
nodes = pop_layout.index nodes = pop_layout.index
@ -758,17 +758,18 @@ def add_allam(n, costs):
n.madd( n.madd(
"Link", "Link",
nodes, nodes,
suffix=" allam", suffix=" allam gas",
bus0=spatial.gas.df.loc[nodes, "nodes"].values, bus0=spatial.gas.df.loc[nodes, "nodes"].values,
bus1=nodes, bus1=nodes,
bus2=spatial.co2.df.loc[nodes, "nodes"].values, bus2=spatial.co2.df.loc[nodes, "nodes"].values,
carrier="allam", bus3="co2 atmosphere",
carrier="allam gas",
p_nom_extendable=True, p_nom_extendable=True,
# TODO: add costs to technology-data
capital_cost=costs.at["allam", "fixed"] * costs.at["allam", "efficiency"], capital_cost=costs.at["allam", "fixed"] * costs.at["allam", "efficiency"],
marginal_cost=costs.at["allam", "VOM"] * costs.at["allam", "efficiency"], marginal_cost=costs.at["allam", "VOM"] * costs.at["allam", "efficiency"],
efficiency=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"], 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={}): def add_methanol_to_power(n, costs, types=None):
# TODO: add costs to technology-data
if types is None:
types = {}
nodes = pop_layout.index nodes = pop_layout.index
@ -1121,8 +1124,7 @@ def add_generation(n, costs):
nodes = pop_layout.index nodes = pop_layout.index
fallback = {"OCGT": "gas"} conventionals = options["conventional_generation"]
conventionals = options.get("conventional_generation", fallback)
for generator, carrier in conventionals.items(): for generator, carrier in conventionals.items():
carrier_nodes = vars(spatial)[carrier].nodes 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) complement_edges["length"] = complement_edges.apply(haversine, axis=1)
# apply k_edge_augmentation weighted by length of complement edges # 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( if augmentation := list(
k_edge_augmentation(G, k_edge, avail=complement_edges.values) 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"], 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.") logger.info("Add options for new hydrogen pipelines.")
h2_pipes = create_network_topology( h2_pipes = create_network_topology(
@ -1682,7 +1684,7 @@ def add_storage_and_grids(n, costs):
bus2=spatial.co2.nodes, bus2=spatial.co2.nodes,
p_nom_extendable=True, p_nom_extendable=True,
carrier="Sabatier", 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"], efficiency=costs.at["methanation", "efficiency"],
efficiency2=-costs.at["methanation", "efficiency"] efficiency2=-costs.at["methanation", "efficiency"]
* costs.at["gas", "CO2 intensity"], * costs.at["gas", "CO2 intensity"],
@ -1691,7 +1693,7 @@ def add_storage_and_grids(n, costs):
lifetime=costs.at["methanation", "lifetime"], lifetime=costs.at["methanation", "lifetime"],
) )
if options.get("coal_cc"): if options["coal_cc"]:
n.madd( n.madd(
"Link", "Link",
spatial.nodes, spatial.nodes,
@ -1835,7 +1837,7 @@ def add_EVs(
p_set=profile, 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( n.madd(
"Link", "Link",
@ -1847,7 +1849,7 @@ def add_EVs(
carrier="BEV charger", carrier="BEV charger",
p_max_pu=avail_profile[spatial.nodes], p_max_pu=avail_profile[spatial.nodes],
lifetime=1, lifetime=1,
efficiency=options.get("bev_charge_efficiency", 0.9), efficiency=options["bev_charge_efficiency"],
) )
if options["v2g"]: if options["v2g"]:
@ -1861,13 +1863,13 @@ def add_EVs(
carrier="V2G", carrier="V2G",
p_max_pu=avail_profile[spatial.nodes], p_max_pu=avail_profile[spatial.nodes],
lifetime=1, lifetime=1,
efficiency=options.get("bev_charge_efficiency", 0.9), efficiency=options["bev_charge_efficiency"],
) )
if options["bev_dsm"]: if options["bev_dsm"]:
e_nom = ( e_nom = (
number_cars number_cars
* options.get("bev_energy", 0.05) * options["bev_energy"]
* options["bev_availability"] * options["bev_availability"]
* electric_share * electric_share
) )
@ -2116,7 +2118,7 @@ def add_heat(n: pypsa.Network, costs: pd.DataFrame, cop: xr.DataArray):
unit="MWh_th", 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( n.madd(
"Generator", "Generator",
nodes + f" {heat_system} heat vent", nodes + f" {heat_system} heat vent",
@ -2786,7 +2788,7 @@ def add_biomass(n, costs):
p_nom_extendable=True, 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 # 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 # 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 # 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"], 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 # 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 # process (Methanol) and that electricity demand for this is included in the base process
n.madd( n.madd(
@ -3044,7 +3048,7 @@ def add_biomass(n, costs):
bus1=spatial.oil.nodes, bus1=spatial.oil.nodes,
bus2="co2 atmosphere", bus2="co2 atmosphere",
bus3=spatial.co2.nodes, bus3=spatial.co2.nodes,
carrier="biomass to liquid", carrier="biomass to liquid CC",
lifetime=costs.at["BtL", "lifetime"], lifetime=costs.at["BtL", "lifetime"],
efficiency=costs.at["BtL", "efficiency"], efficiency=costs.at["BtL", "efficiency"],
efficiency2=-costs.at["solid biomass", "CO2 intensity"] 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"], 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 # 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 # process (Methanol) and that electricity demand for this is included in the base process
n.madd( n.madd(
@ -3122,7 +3128,7 @@ def add_biomass(n, costs):
bus1=spatial.gas.nodes, bus1=spatial.gas.nodes,
bus2=spatial.co2.nodes, bus2=spatial.co2.nodes,
bus3="co2 atmosphere", bus3="co2 atmosphere",
carrier="BioSNG", carrier="BioSNG CC",
lifetime=costs.at["BioSNG", "lifetime"], lifetime=costs.at["BioSNG", "lifetime"],
efficiency=costs.at["BioSNG", "efficiency"], efficiency=costs.at["BioSNG", "efficiency"],
efficiency2=costs.at["BioSNG", "CO2 stored"] efficiency2=costs.at["BioSNG", "CO2 stored"]
@ -3162,10 +3168,6 @@ def add_biomass(n, costs):
* costs.at["solid biomass to hydrogen", "efficiency"] * costs.at["solid biomass to hydrogen", "efficiency"]
+ costs.at["biomass CHP capture", "fixed"] + costs.at["biomass CHP capture", "fixed"]
* costs.at["solid biomass", "CO2 intensity"], * 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, marginal_cost=0.0,
) )
@ -3356,7 +3358,7 @@ def add_industry(n, costs):
bus3=spatial.co2.nodes, bus3=spatial.co2.nodes,
carrier="methanolisation", carrier="methanolisation",
p_nom_extendable=True, 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"] capital_cost=costs.at["methanolisation", "fixed"]
* options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a
marginal_cost=options["MWh_MeOH_per_MWh_H2"] marginal_cost=options["MWh_MeOH_per_MWh_H2"]
@ -3552,12 +3554,12 @@ def add_industry(n, costs):
efficiency2=-costs.at["oil", "CO2 intensity"] efficiency2=-costs.at["oil", "CO2 intensity"]
* costs.at["Fischer-Tropsch", "efficiency"], * costs.at["Fischer-Tropsch", "efficiency"],
p_nom_extendable=True, 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"], lifetime=costs.at["Fischer-Tropsch", "lifetime"],
) )
# naphtha # naphtha
demand_factor = options.get("HVC_demand_factor", 1) demand_factor = options["HVC_demand_factor"]
if demand_factor != 1: if demand_factor != 1:
logger.warning(f"Changing HVC demand by {demand_factor*100-100:+.2f}%.") 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, 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( n.madd(
"Link", "Link",
spatial.msw.locations, spatial.msw.locations,
@ -3722,7 +3724,7 @@ def add_industry(n, costs):
) )
# aviation # aviation
demand_factor = options.get("aviation_demand_factor", 1) demand_factor = options["aviation_demand_factor"]
if demand_factor != 1: if demand_factor != 1:
logger.warning(f"Changing aviation demand by {demand_factor*100-100:+.2f}%.") 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"], lifetime=costs.at["cement capture", "lifetime"],
) )
if options.get("ammonia"): if options["ammonia"]:
if options["ammonia"] == "regional": if options["ammonia"] == "regional":
p_set = ( p_set = (
industrial_demand.loc[spatial.ammonia.locations, "ammonia"].rename( industrial_demand.loc[spatial.ammonia.locations, "ammonia"].rename(
@ -4639,8 +4641,8 @@ if __name__ == "__main__":
if options["co2network"]: if options["co2network"]:
add_co2_network(n, costs) add_co2_network(n, costs)
if options["allam_cycle"]: if options["allam_cycle_gas"]:
add_allam(n, costs) add_allam_gas(n, costs)
n = set_temporal_aggregation( n = set_temporal_aggregation(
n, snakemake.params.time_resolution, snakemake.input.snapshot_weightings n, snakemake.params.time_resolution, snakemake.input.snapshot_weightings
@ -4701,7 +4703,7 @@ if __name__ == "__main__":
snakemake.params.planning_horizons[0] == investment_year 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) cluster_heat_buses(n)
maybe_adjust_costs_and_potentials( maybe_adjust_costs_and_potentials(