resolve merge conflicts

This commit is contained in:
Fabian Neumann 2023-02-16 18:34:24 +01:00
commit 82939ca546
12 changed files with 348 additions and 52 deletions

6
.gitignore vendored
View File

@ -31,6 +31,8 @@ data/costs_*.csv
dask-worker-space dask-worker-space
dask-worker-space/
publications.jrc.ec.europa.eu/
*.org *.org
@ -53,3 +55,7 @@ doc/_build
*.xls *.xls
*.geojson *.geojson
*.ipynb
data/costs_*

View File

@ -146,10 +146,9 @@ if config["sector"]["gas_network"] or config["sector"]["H2_retrofit"]:
rule build_gas_input_locations: rule build_gas_input_locations:
input: input:
lng="data/gas_network/scigrid-gas/data/IGGIELGN_LNGs.geojson", lng=HTTP.remote("https://globalenergymonitor.org/wp-content/uploads/2022/09/Europe-Gas-Tracker-August-2022.xlsx", keep_local=True),
entry="data/gas_network/scigrid-gas/data/IGGIELGN_BorderPoints.geojson", entry="data/gas_network/scigrid-gas/data/IGGIELGN_BorderPoints.geojson",
production="data/gas_network/scigrid-gas/data/IGGIELGN_Productions.geojson", production="data/gas_network/scigrid-gas/data/IGGIELGN_Productions.geojson",
planned_lng="data/gas_network/planned_LNGs.csv",
regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"), regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"),
regions_offshore=pypsaeur('resources/regions_offshore_elec_s{simpl}_{clusters}.geojson') regions_offshore=pypsaeur('resources/regions_offshore_elec_s{simpl}_{clusters}.geojson')
output: output:
@ -288,6 +287,23 @@ else:
build_biomass_transport_costs_output = {} build_biomass_transport_costs_output = {}
if config["sector"]["regional_co2_sequestration_potential"]["enable"]:
rule build_sequestration_potentials:
input:
sequestration_potential=HTTP.remote("https://raw.githubusercontent.com/ericzhou571/Co2Storage/main/resources/complete_map_2020_unit_Mt.geojson", keep_local=True),
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",
@ -520,7 +536,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

View File

@ -66,7 +66,7 @@ snapshots:
# arguments to pd.date_range # arguments to pd.date_range
start: "2013-01-01" start: "2013-01-01"
end: "2014-01-01" end: "2014-01-01"
closed: left # end is not inclusive inclusive: left # end is not inclusive
atlite: atlite:
cutout: ../pypsa-eur/cutouts/europe-2013-era5.nc cutout: ../pypsa-eur/cutouts/europe-2013-era5.nc
@ -250,10 +250,19 @@ sector:
coal_cc: false coal_cc: false
dac: true dac: true
co2_vent: false co2_vent: false
allam_cycle: false
SMR: true SMR: true
regional_co2_sequestration_potential:
enable: false # enable regionally resolved geological co2 storage potential
attribute: 'conservative estimate Mt'
include_onshore: false # include onshore sequestration potentials
min_size: 3 # Gt, sites with lower potential will be excluded
max_size: 25 # Gt, max sequestration potential for any one site, TODO research suitable value
years_of_storage: 25 # years until potential exhausted at optimised annual rate
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:
@ -261,8 +270,11 @@ sector:
- nearshore # within 50 km of sea - nearshore # within 50 km of sea
# - offshore # - offshore
ammonia: false # can be false (no NH3 carrier), true (copperplated NH3), "regional" (regionalised NH3 without network) ammonia: false # can be false (no NH3 carrier), true (copperplated NH3), "regional" (regionalised NH3 without network)
min_part_load_fischer_tropsch: 0.9 # p_min_pu
min_part_load_methanolisation: 0.5 # p_min_pu
use_fischer_tropsch_waste_heat: true use_fischer_tropsch_waste_heat: true
use_fuel_cell_waste_heat: true use_fuel_cell_waste_heat: true
use_electrolysis_waste_heat: false
electricity_distribution_grid: true electricity_distribution_grid: true
electricity_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv electricity_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
electricity_grid_connection: true # only applies to onshore wind and utility PV electricity_grid_connection: true # only applies to onshore wind and utility PV
@ -276,7 +288,8 @@ 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_transport: false # biomass transport between nodes biomass_spatial: false # regionally resolve biomass (e.g. potentials)
biomass_transport: false # allow transport of solid biomass between nodes
conventional_generation: # generator : carrier conventional_generation: # generator : carrier
OCGT: gas OCGT: gas
biomass_to_liquid: false biomass_to_liquid: false

View File

@ -1,8 +0,0 @@
name,geometry,max_cap_store2pipe_M_m3_per_d,source
Wilhelmshaven,"POINT(8.133 53.516)",27.4,https://www.gem.wiki/Wilhelmshaven_LNG_Terminal
Brunsbüttel,"POINT(8.976 53.914)",19.2,https://www.gem.wiki/Brunsb%C3%BCttel_LNG_Terminal
Stade,"POINT(9.510 53.652)",32.9,https://www.gem.wiki/Stade_LNG_Terminal
Alexandroupolis,"POINT(25.843 40.775)",16.7,https://www.gem.wiki/Alexandroupolis_LNG_Terminal
Shannon,"POINT(-9.442 52.581)",22.5,https://www.gem.wiki/Shannon_LNG_Terminal
Gothenburg,"POINT(11.948 57.702)",1.4,https://www.gem.wiki/Gothenburg_LNG_Terminal
Cork,"POINT(-8.323 51.831)",11.0,https://www.gem.wiki/Cork_LNG_Terminal
1 name geometry max_cap_store2pipe_M_m3_per_d source
2 Wilhelmshaven POINT(8.133 53.516) 27.4 https://www.gem.wiki/Wilhelmshaven_LNG_Terminal
3 Brunsbüttel POINT(8.976 53.914) 19.2 https://www.gem.wiki/Brunsb%C3%BCttel_LNG_Terminal
4 Stade POINT(9.510 53.652) 32.9 https://www.gem.wiki/Stade_LNG_Terminal
5 Alexandroupolis POINT(25.843 40.775) 16.7 https://www.gem.wiki/Alexandroupolis_LNG_Terminal
6 Shannon POINT(-9.442 52.581) 22.5 https://www.gem.wiki/Shannon_LNG_Terminal
7 Gothenburg POINT(11.948 57.702) 1.4 https://www.gem.wiki/Gothenburg_LNG_Terminal
8 Cork POINT(-8.323 51.831) 11.0 https://www.gem.wiki/Cork_LNG_Terminal

View File

@ -65,10 +65,14 @@ incorporates retrofitting options to hydrogen.
* 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
* Add option for minimum part load for Fischer-Tropsch plants (default: 90%) and methanolisation plants (default: 50%).
* 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. * 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.
* Option ``retrieve_sector_databundle`` to automatically retrieve and extract data bundle. * Option ``retrieve_sector_databundle`` to automatically retrieve and extract data bundle.
* Add option to use waste heat of electrolysis in district heating networks (``use_electrolysis_waste_heat``).
* Add regionalised hydrogen salt cavern storage potentials from `Technical Potential of Salt Caverns for Hydrogen Storage in Europe <https://doi.org/10.20944/preprints201910.0187.v1>`_. * Add regionalised hydrogen salt cavern storage potentials from `Technical Potential of Salt Caverns for Hydrogen Storage in Europe <https://doi.org/10.20944/preprints201910.0187.v1>`_.
* 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 sweep the global CO2 sequestration potentials with keyword ``seq200`` in the ``{sector_opts}`` wildcard (for limit of 200 Mt CO2).
@ -93,6 +97,30 @@ incorporates retrofitting options to hydrogen.
as explicit ICE shares for land transport (``land_transport_ice_share``) and as explicit ICE shares for land transport (``land_transport_ice_share``) and
agriculture machinery (``agriculture_machinery_oil_share``). agriculture machinery (``agriculture_machinery_oil_share``).
* Add option to spatially resolve carrier representing stored carbon dioxide
(``co2_spatial``). This allows for more detailed modelling of CCUTS, e.g.
regarding the capturing of industrial process emissions, usage as feedstock
for electrofuels, transport of carbon dioxide, and geological sequestration sites.
* Add option for planning a new carbon dioxide network (``co2network``).
* Add option for regionally-resolved geological carbon dioxide sequestration
potentials through new rule ``build_sequestration_potentials`` based on
`CO2StoP <https://setis.ec.europa.eu/european-co2-storage-database_en>`_. This
can be controlled in the section ``regional_co2_sequestration_potential`` of
the ``config.yaml``. It includes options to select the level of conservatism,
whether onshore potentials should be included, the respective upper and lower
limits per region, and an annualisation parameter for the cumulative
potential. The defaults are preliminary and will be validated the next
release.
* Separate option to regionally resolve biomass (``biomass_spatial``) from
option to allow biomass transport (``biomass_transport``).
* Add option to include `Allam cycle gas power plants
<https://en.wikipedia.org/wiki/Allam_power_cycle>`_ (``allam_cycle``).
**Bugfixes** **Bugfixes**
* The CO2 sequestration limit implemented as GlobalConstraint (introduced in the previous version) * The CO2 sequestration limit implemented as GlobalConstraint (introduced in the previous version)

View File

@ -19,14 +19,29 @@ def read_scigrid_gas(fn):
return df return df
def build_gas_input_locations(lng_fn, planned_lng_fn, entry_fn, prod_fn, countries): def build_gem_lng_data(lng_fn):
df = pd.read_excel(lng_fn[0], sheet_name='LNG terminals - data')
df = df.set_index("ComboID")
remove_status = ['Cancelled']
remove_country = ['Cyprus','Turkey']
remove_terminal = ['Puerto de la Luz LNG Terminal', 'Gran Canaria LNG Terminal']
df = df.query("Status != 'Cancelled' \
& Country != @remove_country \
& TerminalName != @remove_terminal \
& CapacityInMtpa != '--'")
df.CapacityInMtpa = df.CapacityInMtpa.astype(float)
geometry = gpd.points_from_xy(df['Longitude'], df['Latitude'])
return gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326")
def build_gas_input_locations(lng_fn, entry_fn, prod_fn, countries):
# LNG terminals # LNG terminals
lng = read_scigrid_gas(lng_fn) lng = build_gem_lng_data(lng_fn)
planned_lng = pd.read_csv(planned_lng_fn)
planned_lng.geometry = planned_lng.geometry.apply(wkt.loads)
planned_lng = gpd.GeoDataFrame(planned_lng, crs=4326)
lng = pd.concat([lng, planned_lng], ignore_index=True)
# Entry points from outside the model scope # Entry points from outside the model scope
entry = read_scigrid_gas(entry_fn) entry = read_scigrid_gas(entry_fn)
@ -45,10 +60,11 @@ def build_gas_input_locations(lng_fn, planned_lng_fn, entry_fn, prod_fn, countri
(prod.country_code != "DE") (prod.country_code != "DE")
] ]
conversion_factor = 437.5 # MCM/day to MWh/h mcm_per_day_to_mw = 437.5 # MCM/day to MWh/h
lng["p_nom"] = lng["max_cap_store2pipe_M_m3_per_d"] * conversion_factor mtpa_to_mw = 1649.224 # mtpa to MWh/h
entry["p_nom"] = entry["max_cap_from_to_M_m3_per_d"] * conversion_factor lng["p_nom"] = lng["CapacityInMtpa"] * mtpa_to_mw
prod["p_nom"] = prod["max_supply_M_m3_per_d"] * conversion_factor entry["p_nom"] = entry["max_cap_from_to_M_m3_per_d"] * mcm_per_day_to_mw
prod["p_nom"] = prod["max_supply_M_m3_per_d"] * mcm_per_day_to_mw
lng["type"] = "lng" lng["type"] = "lng"
entry["type"] = "pipeline" entry["type"] = "pipeline"
@ -64,7 +80,7 @@ if __name__ == "__main__":
if 'snakemake' not in globals(): if 'snakemake' not in globals():
from helper import mock_snakemake from helper import mock_snakemake
snakemake = mock_snakemake( snakemake = mock_snakemake(
'build_gas_import_locations', 'build_gas_input_locations',
simpl='', simpl='',
clusters='37', clusters='37',
) )
@ -87,7 +103,6 @@ if __name__ == "__main__":
gas_input_locations = build_gas_input_locations( gas_input_locations = build_gas_input_locations(
snakemake.input.lng, snakemake.input.lng,
snakemake.input.planned_lng,
snakemake.input.entry, snakemake.input.entry,
snakemake.input.production, snakemake.input.production,
countries countries

View File

@ -0,0 +1,43 @@
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"
)
cf = snakemake.config["sector"]["regional_co2_sequestration_potential"]
gdf = gpd.read_file(snakemake.input.sequestration_potential[0])
regions = gpd.read_file(snakemake.input.regions_offshore)
if cf["include_onshore"]:
onregions = gpd.read_file(snakemake.input.regions_onshore)
regions = pd.concat([regions, onregions]).dissolve(by='name').reset_index()
s = allocate_sequestration_potential(gdf, regions, attr=cf["attribute"], threshold=cf["min_size"])
s = s.where(s>cf["min_size"]).dropna()
s.to_csv(snakemake.output.sequestration_potential)

View File

@ -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))

View File

@ -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"]: 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=None)] items = c.df.index[c.df["bus" + end].map(bus_map).fillna(False)]
if len(items) == 0: if len(items) == 0:
continue 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"]: 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=None)] items = c.df.index[c.df["bus" + str(end)].map(bus_map).fillna(False)]
if len(items) == 0: if len(items) == 0:
continue continue

View File

@ -48,7 +48,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"
@ -65,14 +65,16 @@ def define_spatial(nodes, options):
spatial.co2 = SimpleNamespace() spatial.co2 = SimpleNamespace()
if options["co2_network"]: if options["co2_spatial"]:
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"
spatial.co2.process_emissions = nodes + " process emissions"
else: else:
spatial.co2.nodes = ["co2 stored"] spatial.co2.nodes = ["co2 stored"]
spatial.co2.locations = ["EU"] spatial.co2.locations = ["EU"]
spatial.co2.vents = ["co2 vent"] spatial.co2.vents = ["co2 vent"]
spatial.co2.process_emissions = ["process emissions"]
spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes) spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes)
@ -92,8 +94,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)
@ -432,6 +437,7 @@ def add_carrier_buses(n, carrier, nodes=None):
e_nom_extendable=True, e_nom_extendable=True,
e_cyclic=True, e_cyclic=True,
carrier=carrier, carrier=carrier,
capital_cost=0.2 * costs.at[carrier, "discount rate"] # preliminary value to avoid zeros
) )
n.madd("Generator", n.madd("Generator",
@ -515,10 +521,17 @@ def add_co2_tracking(n, options):
unit="t_co2" unit="t_co2"
) )
if options["regional_co2_sequestration_potential"]["enable"]:
upper_limit = options["regional_co2_sequestration_potential"]["max_size"] * 1e3 # Mt
annualiser = options["regional_co2_sequestration_potential"]["years_of_storage"]
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
@ -560,6 +573,29 @@ def add_co2_network(n, costs):
) )
def add_allam(n, costs):
logger.info("Adding Allam cycle gas power plants.")
nodes = pop_layout.index
n.madd("Link",
nodes,
suffix=" allam",
bus0=spatial.gas.df.loc[nodes, "nodes"].values,
bus1=nodes,
bus2=spatial.co2.df.loc[nodes, "nodes"].values,
carrier="allam",
p_nom_extendable=True,
# TODO: add costs to technology-data
capital_cost=0.6*1.5e6*0.1, # efficiency * EUR/MW * annuity
marginal_cost=2,
efficiency=0.6,
efficiency2=costs.at['gas', 'CO2 intensity'],
lifetime=30.,
)
def add_dac(n, costs): def add_dac(n, costs):
heat_carriers = ["urban central heat", "services urban decentral heat"] heat_carriers = ["urban central heat", "services urban decentral heat"]
@ -1228,7 +1264,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,
@ -1837,7 +1873,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()
@ -1909,10 +1945,10 @@ def add_biomass(n, costs):
biomass_transport.index, biomass_transport.index,
bus0=biomass_transport.bus0 + " solid biomass", bus0=biomass_transport.bus0 + " solid biomass",
bus1=biomass_transport.bus1 + " solid biomass", bus1=biomass_transport.bus1 + " solid biomass",
p_nom_extendable=True, p_nom_extendable=False,
p_nom=5e4,
length=biomass_transport.length.values, length=biomass_transport.length.values,
marginal_cost=biomass_transport.costs * biomass_transport.length.values, marginal_cost=biomass_transport.costs * biomass_transport.length.values,
capital_cost=1,
carrier="solid biomass transport" carrier="solid biomass transport"
) )
@ -2061,7 +2097,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
@ -2232,6 +2268,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),
capital_cost=costs.at["methanolisation", 'fixed'] * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a capital_cost=costs.at["methanolisation", 'fixed'] * options["MWh_MeOH_per_MWh_H2"], # EUR/MW_H2/a
lifetime=costs.at["methanolisation", 'lifetime'], lifetime=costs.at["methanolisation", 'lifetime'],
efficiency=options["MWh_MeOH_per_MWh_H2"], efficiency=options["MWh_MeOH_per_MWh_H2"],
@ -2339,6 +2376,7 @@ def add_industry(n, costs):
capital_cost=costs.at["Fischer-Tropsch", 'fixed'] * costs.at["Fischer-Tropsch", 'efficiency'], # EUR/MW_H2/a capital_cost=costs.at["Fischer-Tropsch", 'fixed'] * costs.at["Fischer-Tropsch", 'efficiency'], # EUR/MW_H2/a
efficiency2=-costs.at["oil", 'CO2 intensity'] * costs.at["Fischer-Tropsch", 'efficiency'], efficiency2=-costs.at["oil", 'CO2 intensity'] * costs.at["Fischer-Tropsch", 'efficiency'],
p_nom_extendable=True, p_nom_extendable=True,
p_min_pu=options.get("min_part_load_fischer_tropsch", 0),
lifetime=costs.at['Fischer-Tropsch', 'lifetime'] lifetime=costs.at['Fischer-Tropsch', 'lifetime']
) )
@ -2405,25 +2443,31 @@ def add_industry(n, costs):
p_set=industrial_demand.loc[nodes, "electricity"] / 8760 p_set=industrial_demand.loc[nodes, "electricity"] / 8760
) )
n.add("Bus", n.madd("Bus",
"process emissions", spatial.co2.process_emissions,
location="EU", location=spatial.co2.locations,
carrier="process emissions", carrier="process emissions",
unit="t_co2" unit="t_co2"
) )
sel = ["process emission", "process emission from feedstock"]
if options["co2_spatial"] or options["co2network"]:
p_set = -industrial_demand.loc[nodes, sel].sum(axis=1).rename(index=lambda x: x + " process emissions") / 8760
else:
p_set = -industrial_demand.loc[nodes, sel].sum(axis=1).sum() / 8760
# this should be process emissions fossil+feedstock # this should be process emissions fossil+feedstock
# then need load on atmosphere for feedstock emissions that are currently going to atmosphere via Link Fischer-Tropsch demand # then need load on atmosphere for feedstock emissions that are currently going to atmosphere via Link Fischer-Tropsch demand
n.add("Load", n.madd("Load",
"process emissions", spatial.co2.process_emissions,
bus="process emissions", bus=spatial.co2.process_emissions,
carrier="process emissions", carrier="process emissions",
p_set=-industrial_demand.loc[nodes,["process emission", "process emission from feedstock"]].sum(axis=1).sum() / 8760 p_set=p_set,
) )
n.add("Link", n.madd("Link",
"process emissions", spatial.co2.process_emissions,
bus0="process emissions", bus0=spatial.co2.process_emissions,
bus1="co2 atmosphere", bus1="co2 atmosphere",
carrier="process emissions", carrier="process emissions",
p_nom_extendable=True, p_nom_extendable=True,
@ -2434,7 +2478,7 @@ def add_industry(n, costs):
n.madd("Link", n.madd("Link",
spatial.co2.locations, spatial.co2.locations,
suffix=" process emissions CC", suffix=" process emissions CC",
bus0="process emissions", bus0=spatial.co2.process_emissions,
bus1="co2 atmosphere", bus1="co2 atmosphere",
bus2=spatial.co2.nodes, bus2=spatial.co2.nodes,
carrier="process emissions CC", carrier="process emissions CC",
@ -2475,6 +2519,11 @@ def add_waste_heat(n):
n.links.loc[urban_central + " Fischer-Tropsch", "bus3"] = urban_central + " urban central heat" n.links.loc[urban_central + " Fischer-Tropsch", "bus3"] = urban_central + " urban central heat"
n.links.loc[urban_central + " Fischer-Tropsch", "efficiency3"] = 0.95 - n.links.loc[urban_central + " Fischer-Tropsch", "efficiency"] n.links.loc[urban_central + " Fischer-Tropsch", "efficiency3"] = 0.95 - n.links.loc[urban_central + " Fischer-Tropsch", "efficiency"]
# TODO integrate useable waste heat efficiency into technology-data from DEA
if options.get('use_electrolysis_waste_heat', False):
n.links.loc[urban_central + " H2 Electrolysis", "bus2"] = urban_central + " urban central heat"
n.links.loc[urban_central + " H2 Electrolysis", "efficiency2"] = 0.84 - n.links.loc[urban_central + " H2 Electrolysis", "efficiency"]
if options['use_fuel_cell_waste_heat']: if options['use_fuel_cell_waste_heat']:
n.links.loc[urban_central + " H2 Fuel Cell", "bus2"] = urban_central + " urban central heat" n.links.loc[urban_central + " H2 Fuel Cell", "bus2"] = urban_central + " urban central heat"
n.links.loc[urban_central + " H2 Fuel Cell", "efficiency2"] = 0.95 - n.links.loc[urban_central + " H2 Fuel Cell", "efficiency"] n.links.loc[urban_central + " H2 Fuel Cell", "efficiency2"] = 0.95 - n.links.loc[urban_central + " H2 Fuel Cell", "efficiency"]
@ -2877,9 +2926,12 @@ 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)
if options["allam_cycle"]:
add_allam(n, costs)
solver_name = snakemake.config["solving"]["solver"]["name"] solver_name = snakemake.config["solving"]["solver"]["name"]
n = set_temporal_aggregation(n, opts, solver_name) n = set_temporal_aggregation(n, opts, solver_name)

View File

@ -17,7 +17,7 @@ snapshots:
# arguments to pd.date_range # arguments to pd.date_range
start: "2013-03-01" start: "2013-03-01"
end: "2013-04-01" end: "2013-04-01"
closed: left # end is not inclusive inclusive: left # end is not inclusive
atlite: atlite:
cutout: ../pypsa-eur/cutouts/be-03-2013-era5.nc cutout: ../pypsa-eur/cutouts/be-03-2013-era5.nc
@ -27,6 +27,118 @@ existing_capacities:
sector: sector:
co2_vent: true co2_vent: true
SMR: true
regional_co2_sequestration_potential:
enable: false
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
co2_sequestration_cost: 10 #EUR/tCO2 for sequestration of CO2
co2_network: false
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
hydrogen_underground_storage: true
hydrogen_underground_storage_locations:
# - onshore # more than 50 km from sea
- nearshore # within 50 km of sea
# - offshore
use_fischer_tropsch_waste_heat: true
use_fuel_cell_waste_heat: true
electricity_distribution_grid: true
electricity_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
electricity_grid_connection: true # only applies to onshore wind and utility PV
H2_network: true
gas_network: false
H2_retrofit: false # if set to True existing gas pipes can be retrofitted to H2 pipes
# according to hydrogen backbone strategy (April, 2020) p.15
# https://gasforclimate2050.eu/wp-content/uploads/2020/07/2020_European-Hydrogen-Backbone_Report.pdf
# 60% of original natural gas capacity could be used in cost-optimal case as H2 capacity
H2_retrofit_capacity_per_CH4: 0.6 # ratio for H2 capacity per original CH4 capacity of retrofitted pipelines
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_cost_factor: 1.0 #multiplies cost in data/costs.csv
biomass_transport: false # biomass transport between nodes
conventional_generation: # generator : carrier
OCGT: gas
biomass_boiler: false
biomass_to_liquid: false
biosng: false
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
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
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)
MWh_elec_per_tNH3_electrolysis: 1.17 # from https://doi.org/10.1016/j.joule.2018.04.017 Table 13 (air separation and HB)
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
HVC_mechanical_recycling_fraction: 0. # fraction of today's HVC produced via mechanical recycling
HVC_chemical_recycling_fraction: 0. # fraction of today's HVC produced via chemical recycling
HVC_production_today: 52. # MtHVC/a from DECHEMA (2017), Figure 16, page 107; includes ethylene, propylene and BTX
MWh_elec_per_tHVC_mechanical_recycling: 0.547 # from SI of https://doi.org/10.1016/j.resconrec.2020.105010, Table S5, for HDPE, PP, PS, PET. LDPE would be 0.756.
MWh_elec_per_tHVC_chemical_recycling: 6.9 # Material Economics (2019), page 125; based on pyrolysis and electric steam cracking
chlorine_production_today: 9.58 # MtCl/a from DECHEMA (2017), Table 7, page 43
MWh_elec_per_tCl: 3.6 # DECHEMA (2017), Table 6, page 43
MWh_H2_per_tCl: -0.9372 # DECHEMA (2017), page 43; negative since hydrogen produced in chloralkali process
methanol_production_today: 1.5 # MtMeOH/a from DECHEMA (2017), page 62
MWh_elec_per_tMeOH: 0.167 # DECHEMA (2017), Table 14, page 65
MWh_CH4_per_tMeOH: 10.25 # DECHEMA (2017), Table 14, page 65
hotmaps_locate_missing: false
reference_year: 2015
# references:
# DECHEMA (2017): https://dechema.de/dechema_media/Downloads/Positionspapiere/Technology_study_Low_carbon_energy_and_feedstock_for_the_European_chemical_industry-p-20002750.pdf
# Material Economics (2019): https://materialeconomics.com/latest-updates/industrial-transformation-2050
costs:
year: 2030
version: v0.5.0
lifetime: 25 #default lifetime
# From a Lion Hirth paper, also reflects average of Noothout et al 2016
discountrate: 0.07
# [EUR/USD] ECB: https://www.ecb.europa.eu/stats/exchange/eurofxref/html/eurofxref-graph-usd.en.html # noqa: E501
USD2013_to_EUR2013: 0.7532
# Marginal and capital costs can be overwritten
# capital_cost:
# onwind: 500
marginal_cost:
solar: 0.01
onwind: 0.015
offwind: 0.015
hydro: 0.
H2: 0.
battery: 0.
emission_prices: # only used with the option Ep (emission prices)
co2: 0.
lines:
length_factor: 1.25 #to estimate offwind connection costs
solving: solving:
solver: solver:

View File

@ -16,13 +16,31 @@ snapshots:
# arguments to pd.date_range # arguments to pd.date_range
start: "2013-03-01" start: "2013-03-01"
end: "2013-04-01" end: "2013-04-01"
closed: left # end is not inclusive inclusive: left # end is not inclusive
atlite: atlite:
cutout: ../pypsa-eur/cutouts/be-03-2013-era5.nc cutout: ../pypsa-eur/cutouts/be-03-2013-era5.nc
sector: sector:
co2_vent: true co2_vent: true
SMR: true
regional_co2_sequestration_potential:
enable: false
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
co2_sequestration_cost: 10 #EUR/tCO2 for sequestration of CO2
co2_network: false
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
hydrogen_underground_storage: true
hydrogen_underground_storage_locations:
# - onshore # more than 50 km from sea
- nearshore # within 50 km of sea
# - offshore
use_fischer_tropsch_waste_heat: true
use_fuel_cell_waste_heat: true
electricity_distribution_grid: true
electricity_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv
electricity_grid_connection: true # only applies to onshore wind and utility PV
H2_network: true
gas_network: true gas_network: true
H2_retrofit: true # if set to True existing gas pipes can be retrofitted to H2 pipes H2_retrofit: true # if set to True existing gas pipes can be retrofitted to H2 pipes
biomass_boiler: false biomass_boiler: false