carbon management
This commit is contained in:
parent
8bafaf25f7
commit
71e2a4943c
20
Snakefile
20
Snakefile
@ -280,6 +280,23 @@ else:
|
|||||||
build_biomass_transport_costs_output = {}
|
build_biomass_transport_costs_output = {}
|
||||||
|
|
||||||
|
|
||||||
|
if config["sector"].get("sequestration_potential", False):
|
||||||
|
rule build_sequestration_potentials:
|
||||||
|
input:
|
||||||
|
sequestration_potential="data/complete_map_2020_unit_Mt.geojson",
|
||||||
|
regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"),
|
||||||
|
regions_offshore=pypsaeur("resources/regions_offshore_elec_s{simpl}_{clusters}.geojson"),
|
||||||
|
output:
|
||||||
|
sequestration_potential="resources/co2_sequestration_potential_elec_s{simpl}_{clusters}.csv"
|
||||||
|
threads: 1
|
||||||
|
resources: mem_mb=4000
|
||||||
|
benchmark: "benchmarks/build_sequestration_potentials_s{simpl}_{clusters}"
|
||||||
|
script: "scripts/build_sequestration_potentials.py"
|
||||||
|
build_sequestration_potentials_output = rules.build_sequestration_potentials.output
|
||||||
|
else:
|
||||||
|
build_sequestration_potentials_output = {}
|
||||||
|
|
||||||
|
|
||||||
rule build_salt_cavern_potentials:
|
rule build_salt_cavern_potentials:
|
||||||
input:
|
input:
|
||||||
salt_caverns="data/h2_salt_caverns_GWh_per_sqkm.geojson",
|
salt_caverns="data/h2_salt_caverns_GWh_per_sqkm.geojson",
|
||||||
@ -512,7 +529,8 @@ rule prepare_sector_network:
|
|||||||
solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc" if config["sector"]["solar_thermal"] else [],
|
solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc" if config["sector"]["solar_thermal"] else [],
|
||||||
**build_retro_cost_output,
|
**build_retro_cost_output,
|
||||||
**build_biomass_transport_costs_output,
|
**build_biomass_transport_costs_output,
|
||||||
**gas_infrastructure
|
**gas_infrastructure,
|
||||||
|
**build_sequestration_potentials_output
|
||||||
output: RDIR + '/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc'
|
output: RDIR + '/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc'
|
||||||
threads: 1
|
threads: 1
|
||||||
resources: mem_mb=2000
|
resources: mem_mb=2000
|
||||||
|
@ -250,9 +250,11 @@ sector:
|
|||||||
dac: true
|
dac: true
|
||||||
co2_vent: false
|
co2_vent: false
|
||||||
SMR: true
|
SMR: true
|
||||||
|
sequestration_potential: true # geological co2 storage potential
|
||||||
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
|
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
|
||||||
co2_sequestration_cost: 10 #EUR/tCO2 for sequestration of CO2
|
co2_sequestration_cost: 10 #EUR/tCO2 for sequestration of CO2
|
||||||
co2_network: false
|
co2_spatial: false
|
||||||
|
co2network: false
|
||||||
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
|
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
|
||||||
hydrogen_underground_storage: true
|
hydrogen_underground_storage: true
|
||||||
hydrogen_underground_storage_locations:
|
hydrogen_underground_storage_locations:
|
||||||
@ -275,6 +277,7 @@ sector:
|
|||||||
gas_network_connectivity_upgrade: 1 # https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation
|
gas_network_connectivity_upgrade: 1 # https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation
|
||||||
gas_distribution_grid: true
|
gas_distribution_grid: true
|
||||||
gas_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
|
gas_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
|
||||||
|
biomass_spatial: false # biomass transport between nodes
|
||||||
biomass_transport: false # biomass transport between nodes
|
biomass_transport: false # biomass transport between nodes
|
||||||
conventional_generation: # generator : carrier
|
conventional_generation: # generator : carrier
|
||||||
OCGT: gas
|
OCGT: gas
|
||||||
|
48
scripts/build_sequestration_potentials.py
Normal file
48
scripts/build_sequestration_potentials.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import geopandas as gpd
|
||||||
|
|
||||||
|
def area(gdf):
|
||||||
|
"""Returns area of GeoDataFrame geometries in square kilometers."""
|
||||||
|
return gdf.to_crs(epsg=3035).area.div(1e6)
|
||||||
|
|
||||||
|
|
||||||
|
def allocate_sequestration_potential(gdf, regions, attr='conservative estimate Mt', threshold=3):
|
||||||
|
gdf = gdf.loc[gdf[attr] > threshold, [attr, "geometry"]]
|
||||||
|
gdf["area_sqkm"] = area(gdf)
|
||||||
|
overlay = gpd.overlay(regions, gdf, keep_geom_type=True)
|
||||||
|
overlay["share"] = area(overlay) / overlay["area_sqkm"]
|
||||||
|
adjust_cols = overlay.columns.difference({"name", "area_sqkm", "geometry", "share"})
|
||||||
|
overlay[adjust_cols] = overlay[adjust_cols].multiply(overlay["share"], axis=0)
|
||||||
|
gdf_regions = overlay.groupby("name").sum()
|
||||||
|
gdf_regions.drop(["area_sqkm", "share"], axis=1, inplace=True)
|
||||||
|
return gdf_regions.squeeze()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if 'snakemake' not in globals():
|
||||||
|
from helper import mock_snakemake
|
||||||
|
snakemake = mock_snakemake(
|
||||||
|
'build_sequestration_potentials',
|
||||||
|
simpl='',
|
||||||
|
clusters="181"
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO move to config.yaml
|
||||||
|
threshold = 3
|
||||||
|
include_onshore = False
|
||||||
|
|
||||||
|
gdf = gpd.read_file(snakemake.input.sequestration_potential)
|
||||||
|
|
||||||
|
regions = gpd.read_file(snakemake.input.regions_offshore)
|
||||||
|
if include_onshore:
|
||||||
|
onregions = gpd.read_file(snakemake.input.regions_onshore)
|
||||||
|
regions = pd.concat([regions, onregions]).dissolve(by='name').reset_index()
|
||||||
|
|
||||||
|
attr = snakemake.config['sector']["sequestration_potential"]
|
||||||
|
kwargs = dict(attr=attr, threshold=threshold) if isinstance(attr, str) else {}
|
||||||
|
|
||||||
|
s = allocate_sequestration_potential(gdf, regions, **kwargs)
|
||||||
|
|
||||||
|
s = s.where(s>threshold).dropna()
|
||||||
|
|
||||||
|
s.to_csv(snakemake.output.sequestration_potential)
|
@ -138,6 +138,6 @@ def parse(l):
|
|||||||
|
|
||||||
def update_config_with_sector_opts(config, sector_opts):
|
def update_config_with_sector_opts(config, sector_opts):
|
||||||
for o in sector_opts.split("-"):
|
for o in sector_opts.split("-"):
|
||||||
if o.startswith("CF:"):
|
if o.startswith("CF+"):
|
||||||
l = o.split("+")[1:]
|
l = o.split("+")[1:]
|
||||||
update_config(config, parse(l))
|
update_config(config, parse(l))
|
@ -44,7 +44,7 @@ def define_spatial(nodes, options):
|
|||||||
|
|
||||||
spatial.biomass = SimpleNamespace()
|
spatial.biomass = SimpleNamespace()
|
||||||
|
|
||||||
if options["biomass_transport"]:
|
if options.get("biomass_spatial", options["biomass_transport"]):
|
||||||
spatial.biomass.nodes = nodes + " solid biomass"
|
spatial.biomass.nodes = nodes + " solid biomass"
|
||||||
spatial.biomass.locations = nodes
|
spatial.biomass.locations = nodes
|
||||||
spatial.biomass.industry = nodes + " solid biomass for industry"
|
spatial.biomass.industry = nodes + " solid biomass for industry"
|
||||||
@ -61,7 +61,7 @@ def define_spatial(nodes, options):
|
|||||||
|
|
||||||
spatial.co2 = SimpleNamespace()
|
spatial.co2 = SimpleNamespace()
|
||||||
|
|
||||||
if options["co2_network"]:
|
if options.get("co2_spatial", options["co2network"]):
|
||||||
spatial.co2.nodes = nodes + " co2 stored"
|
spatial.co2.nodes = nodes + " co2 stored"
|
||||||
spatial.co2.locations = nodes
|
spatial.co2.locations = nodes
|
||||||
spatial.co2.vents = nodes + " co2 vent"
|
spatial.co2.vents = nodes + " co2 vent"
|
||||||
@ -88,8 +88,11 @@ def define_spatial(nodes, options):
|
|||||||
spatial.gas.locations = ["EU"]
|
spatial.gas.locations = ["EU"]
|
||||||
spatial.gas.biogas = ["EU biogas"]
|
spatial.gas.biogas = ["EU biogas"]
|
||||||
spatial.gas.industry = ["gas for industry"]
|
spatial.gas.industry = ["gas for industry"]
|
||||||
spatial.gas.industry_cc = ["gas for industry CC"]
|
|
||||||
spatial.gas.biogas_to_gas = ["EU biogas to gas"]
|
spatial.gas.biogas_to_gas = ["EU biogas to gas"]
|
||||||
|
if options.get("co2_spatial", options["co2network"]):
|
||||||
|
spatial.gas.industry_cc = nodes + " gas for industry CC"
|
||||||
|
else:
|
||||||
|
spatial.gas.industry_cc = ["gas for industry CC"]
|
||||||
|
|
||||||
spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes)
|
spatial.gas.df = pd.DataFrame(vars(spatial.gas), index=nodes)
|
||||||
|
|
||||||
@ -507,10 +510,18 @@ def add_co2_tracking(n, options):
|
|||||||
unit="t_co2"
|
unit="t_co2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if options["sequestration_potential"]:
|
||||||
|
# TODO make configurable options
|
||||||
|
upper_limit = 25000 # Mt
|
||||||
|
annualiser = 25 # TODO research suitable value
|
||||||
|
e_nom_max = pd.read_csv(snakemake.input.sequestration_potential, index_col=0).squeeze()
|
||||||
|
e_nom_max = e_nom_max.reindex(spatial.co2.locations).fillna(0.).clip(upper=upper_limit).mul(1e6) / annualiser # t
|
||||||
|
e_nom_max = e_nom_max.rename(index=lambda x: x + " co2 stored")
|
||||||
|
|
||||||
n.madd("Store",
|
n.madd("Store",
|
||||||
spatial.co2.nodes,
|
spatial.co2.nodes,
|
||||||
e_nom_extendable=True,
|
e_nom_extendable=True,
|
||||||
e_nom_max=np.inf,
|
e_nom_max=e_nom_max,
|
||||||
capital_cost=options['co2_sequestration_cost'],
|
capital_cost=options['co2_sequestration_cost'],
|
||||||
carrier="co2 stored",
|
carrier="co2 stored",
|
||||||
bus=spatial.co2.nodes
|
bus=spatial.co2.nodes
|
||||||
@ -1218,7 +1229,7 @@ def add_storage_and_grids(n, costs):
|
|||||||
bus0=spatial.coal.nodes,
|
bus0=spatial.coal.nodes,
|
||||||
bus1=spatial.nodes,
|
bus1=spatial.nodes,
|
||||||
bus2="co2 atmosphere",
|
bus2="co2 atmosphere",
|
||||||
bus3="co2 stored",
|
bus3=spatial.co2.nodes,
|
||||||
marginal_cost=costs.at['coal', 'efficiency'] * costs.at['coal', 'VOM'], #NB: VOM is per MWel
|
marginal_cost=costs.at['coal', 'efficiency'] * costs.at['coal', 'VOM'], #NB: VOM is per MWel
|
||||||
capital_cost=costs.at['coal', 'efficiency'] * costs.at['coal', 'fixed'] + costs.at['biomass CHP capture', 'fixed'] * costs.at['coal', 'CO2 intensity'], #NB: fixed cost is per MWel
|
capital_cost=costs.at['coal', 'efficiency'] * costs.at['coal', 'fixed'] + costs.at['biomass CHP capture', 'fixed'] * costs.at['coal', 'CO2 intensity'], #NB: fixed cost is per MWel
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
@ -1828,7 +1839,7 @@ def add_biomass(n, costs):
|
|||||||
else:
|
else:
|
||||||
biogas_potentials_spatial = biomass_potentials["biogas"].sum()
|
biogas_potentials_spatial = biomass_potentials["biogas"].sum()
|
||||||
|
|
||||||
if options["biomass_transport"]:
|
if options.get("biomass_spatial", options["biomass_transport"]):
|
||||||
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename(index=lambda x: x + " solid biomass")
|
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].rename(index=lambda x: x + " solid biomass")
|
||||||
else:
|
else:
|
||||||
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum()
|
solid_biomass_potentials_spatial = biomass_potentials["solid biomass"].sum()
|
||||||
@ -2052,7 +2063,7 @@ def add_industry(n, costs):
|
|||||||
unit="MWh_LHV"
|
unit="MWh_LHV"
|
||||||
)
|
)
|
||||||
|
|
||||||
if options["biomass_transport"]:
|
if options.get("biomass_spatial", options["biomass_transport"]):
|
||||||
p_set = industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760
|
p_set = industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760
|
||||||
else:
|
else:
|
||||||
p_set = industrial_demand["solid biomass"].sum() / 8760
|
p_set = industrial_demand["solid biomass"].sum() / 8760
|
||||||
@ -2775,7 +2786,7 @@ if __name__ == "__main__":
|
|||||||
if "noH2network" in opts:
|
if "noH2network" in opts:
|
||||||
remove_h2_network(n)
|
remove_h2_network(n)
|
||||||
|
|
||||||
if options["co2_network"]:
|
if options["co2network"]:
|
||||||
add_co2_network(n, costs)
|
add_co2_network(n, costs)
|
||||||
|
|
||||||
solver_name = snakemake.config["solving"]["solver"]["name"]
|
solver_name = snakemake.config["solving"]["solver"]["name"]
|
||||||
|
Loading…
Reference in New Issue
Block a user