Merge branch 'master' of github.com:nworbmot/PyPSA-Eur-Sec
This commit is contained in:
commit
8cf8a49b48
25
Snakefile
25
Snakefile
@ -1,4 +1,7 @@
|
|||||||
|
|
||||||
|
from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider
|
||||||
|
HTTP = HTTPRemoteProvider()
|
||||||
|
|
||||||
configfile: "config.yaml"
|
configfile: "config.yaml"
|
||||||
|
|
||||||
|
|
||||||
@ -180,6 +183,21 @@ rule build_biomass_potentials:
|
|||||||
script: 'scripts/build_biomass_potentials.py'
|
script: 'scripts/build_biomass_potentials.py'
|
||||||
|
|
||||||
|
|
||||||
|
if config["sector"]["biomass_transport"]:
|
||||||
|
rule build_biomass_transport_costs:
|
||||||
|
input:
|
||||||
|
transport_cost_data=HTTP.remote("publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass potentials in europe_web rev.pdf", keep_local=True)
|
||||||
|
output:
|
||||||
|
biomass_transport_costs="resources/biomass_transport_costs.csv",
|
||||||
|
threads: 1
|
||||||
|
resources: mem_mb=1000
|
||||||
|
benchmark: "benchmarks/build_biomass_transport_costs"
|
||||||
|
script: 'scripts/build_biomass_transport_costs.py'
|
||||||
|
build_biomass_transport_costs_output = rules.build_biomass_transport_costs.output
|
||||||
|
else:
|
||||||
|
build_biomass_transport_costs_output = {}
|
||||||
|
|
||||||
|
|
||||||
rule build_ammonia_production:
|
rule build_ammonia_production:
|
||||||
input:
|
input:
|
||||||
usgs="data/myb1-2017-nitro.xls"
|
usgs="data/myb1-2017-nitro.xls"
|
||||||
@ -321,8 +339,8 @@ rule prepare_sector_network:
|
|||||||
energy_totals_name='resources/energy_totals.csv',
|
energy_totals_name='resources/energy_totals.csv',
|
||||||
co2_totals_name='resources/co2_totals.csv',
|
co2_totals_name='resources/co2_totals.csv',
|
||||||
transport_name='resources/transport_data.csv',
|
transport_name='resources/transport_data.csv',
|
||||||
traffic_data_KFZ = "data/emobility/KFZ__count",
|
traffic_data_KFZ="data/emobility/KFZ__count",
|
||||||
traffic_data_Pkw = "data/emobility/Pkw__count",
|
traffic_data_Pkw="data/emobility/Pkw__count",
|
||||||
biomass_potentials='resources/biomass_potentials.csv',
|
biomass_potentials='resources/biomass_potentials.csv',
|
||||||
heat_profile="data/heat_load_profile_BDEW.csv",
|
heat_profile="data/heat_load_profile_BDEW.csv",
|
||||||
costs=CDIR + "costs_{planning_horizons}.csv",
|
costs=CDIR + "costs_{planning_horizons}.csv",
|
||||||
@ -352,7 +370,8 @@ rule prepare_sector_network:
|
|||||||
solar_thermal_total="resources/solar_thermal_total_elec_s{simpl}_{clusters}.nc",
|
solar_thermal_total="resources/solar_thermal_total_elec_s{simpl}_{clusters}.nc",
|
||||||
solar_thermal_urban="resources/solar_thermal_urban_elec_s{simpl}_{clusters}.nc",
|
solar_thermal_urban="resources/solar_thermal_urban_elec_s{simpl}_{clusters}.nc",
|
||||||
solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc",
|
solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc",
|
||||||
**build_retro_cost_output
|
**build_retro_cost_output,
|
||||||
|
**build_biomass_transport_costs_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
|
||||||
|
@ -223,7 +223,8 @@ sector:
|
|||||||
co2_vent: true
|
co2_vent: true
|
||||||
SMR: true
|
SMR: true
|
||||||
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
|
co2_sequestration_potential: 200 #MtCO2/a sequestration potential for Europe
|
||||||
co2_sequestration_cost: 20 #EUR/tCO2 for transport and sequestration of CO2
|
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
|
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
|
||||||
hydrogen_underground_storage: true
|
hydrogen_underground_storage: true
|
||||||
use_fischer_tropsch_waste_heat: true
|
use_fischer_tropsch_waste_heat: true
|
||||||
@ -233,6 +234,7 @@ sector:
|
|||||||
electricity_grid_connection: true # only applies to onshore wind and utility PV
|
electricity_grid_connection: true # only applies to onshore wind and utility PV
|
||||||
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
|
||||||
conventional_generation: # generator : carrier
|
conventional_generation: # generator : carrier
|
||||||
OCGT: gas
|
OCGT: gas
|
||||||
|
|
||||||
@ -480,6 +482,7 @@ plotting:
|
|||||||
hot water storage: '#BBBBBB'
|
hot water storage: '#BBBBBB'
|
||||||
hot water charging: '#BBBBBB'
|
hot water charging: '#BBBBBB'
|
||||||
hot water discharging: '#999999'
|
hot water discharging: '#999999'
|
||||||
|
CO2 pipeline: '#999999'
|
||||||
CHP: r
|
CHP: r
|
||||||
CHP heat: r
|
CHP heat: r
|
||||||
CHP electric: r
|
CHP electric: r
|
||||||
@ -520,5 +523,6 @@ plotting:
|
|||||||
shipping oil: "#6495ED"
|
shipping oil: "#6495ED"
|
||||||
shipping oil emissions: "#6495ED"
|
shipping oil emissions: "#6495ED"
|
||||||
electricity distribution grid: '#333333'
|
electricity distribution grid: '#333333'
|
||||||
|
solid biomass transport: green
|
||||||
H2 for industry: "#222222"
|
H2 for industry: "#222222"
|
||||||
H2 for shipping: "#6495ED"
|
H2 for shipping: "#6495ED"
|
||||||
|
@ -62,12 +62,26 @@ Future release
|
|||||||
These are included in the environment specifications of PyPSA-Eur.
|
These are included in the environment specifications of PyPSA-Eur.
|
||||||
* Consistent use of ``__main__`` block and further unspecific code cleaning.
|
* Consistent use of ``__main__`` block and further unspecific code cleaning.
|
||||||
* Distinguish costs for home battery storage and inverter from utility-scale battery costs.
|
* Distinguish costs for home battery storage and inverter from utility-scale battery costs.
|
||||||
|
* Add option to regionally resolve CO2 storage and add CO2 pipeline transport because geological storage potential,
|
||||||
|
CO2 utilisation sites and CO2 capture sites may be separated.
|
||||||
|
The CO2 network is built from zero based on the topology of the electricity grid (greenfield).
|
||||||
|
Pipelines are assumed to be bidirectional and lossless.
|
||||||
|
Furthermore, neither retrofitting of natural gas pipelines (required pressures are too high, 80-160 bar vs <80 bar)
|
||||||
|
nor other modes of CO2 transport (by ship, road or rail) are considered.
|
||||||
|
The regional representation of CO2 is activated with the config setting ``sector: co2_network: true`` but is deactivated by default.
|
||||||
|
The global limit for CO2 sequestration now applies to the sum of all CO2 stores via an ``extra_functionality`` constraint.
|
||||||
* Added option for hydrogen liquefaction costs for hydrogen demand in shipping.
|
* Added option for hydrogen liquefaction costs for hydrogen demand in shipping.
|
||||||
This introduces a new ``H2 liquid`` bus at each location.
|
This introduces a new ``H2 liquid`` bus at each location.
|
||||||
It is activated via ``sector: shipping_hydrogen_liquefaction: true``.
|
It is activated via ``sector: shipping_hydrogen_liquefaction: true``.
|
||||||
* The share of shipping transformed into hydrogen fuel cell can be now defined for different years in the ``config.yaml`` file. The carbon emission from the remaining share is treated as a negative load on the atmospheric carbon dioxide bus, just like aviation and land transport emissions.
|
* The share of shipping transformed into hydrogen fuel cell can be now defined for different years in the ``config.yaml`` file. The carbon emission from the remaining share is treated as a negative load on the atmospheric carbon dioxide bus, just like aviation and land transport emissions.
|
||||||
* The transformation of the Steel and Aluminium production can be now defined for different years in the ``config.yaml`` file.
|
* The transformation of the Steel and Aluminium production can be now defined for different years in the ``config.yaml`` file.
|
||||||
* Include the option to alter the maximum energy capacity of a store via the ``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for sensitivity analyses. Example: ``co2 stored+e2`` multiplies the ``e_nom_max`` by factor 2. In this example, ``e_nom_max`` represents the CO2 sequestration potential in Europe.
|
* Include the option to alter the maximum energy capacity of a store via the ``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for sensitivity analyses. Example: ``co2 stored+e2`` multiplies the ``e_nom_max`` by factor 2. In this example, ``e_nom_max`` represents the CO2 sequestration potential in Europe.
|
||||||
|
* Add option to regionally disaggregate biomass potential to individual nodes
|
||||||
|
(currently given per country, then distributed by population density within)
|
||||||
|
and allow the transport of solid biomass.
|
||||||
|
The transport costs are determined based on the `JRC-EU-Times Bioenergy report <http://dx.doi.org/10.2790/01017>`_
|
||||||
|
in the new optional rule ``build_biomass_transport_costs``.
|
||||||
|
Biomass transport can be activated with the setting ``sector: biomass_transport: true``.
|
||||||
* Compatibility with ``xarray`` version 0.19.
|
* Compatibility with ``xarray`` version 0.19.
|
||||||
* Separate basic chemicals into HVC, chlorine, methanol and ammonia [`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
|
* Separate basic chemicals into HVC, chlorine, methanol and ammonia [`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
|
||||||
* Add option to specify reuse, primary production, and mechanical and chemical recycling fraction of platics [`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
|
* Add option to specify reuse, primary production, and mechanical and chemical recycling fraction of platics [`#166 <https://github.com/PyPSA/PyPSA-Eur-Sec/pull/166>`_].
|
||||||
|
@ -44,11 +44,13 @@ Hydrogen network: nodal.
|
|||||||
Methane network: single node for Europe, since future demand is so
|
Methane network: single node for Europe, since future demand is so
|
||||||
low and no bottlenecks are expected.
|
low and no bottlenecks are expected.
|
||||||
|
|
||||||
Solid biomass: single node for Europe, until transport costs can be
|
Solid biomass: choice between single node for Europe and nodal where biomass
|
||||||
incorporated.
|
potential is regionally disaggregated (currently given per country,
|
||||||
|
then distributed by population density within)
|
||||||
|
and transport of solid biomass is possible.
|
||||||
|
|
||||||
CO2: single node for Europe, but a transport and storage cost is added for
|
CO2: single node for Europe, but a transport and storage cost is added for
|
||||||
sequestered CO2.
|
sequestered CO2. Optionally: nodal, with CO2 transport via pipelines.
|
||||||
|
|
||||||
Liquid hydrocarbons: single node for Europe, since transport costs for
|
Liquid hydrocarbons: single node for Europe, since transport costs for
|
||||||
liquids are low.
|
liquids are low.
|
||||||
|
90
scripts/build_biomass_transport_costs.py
Normal file
90
scripts/build_biomass_transport_costs.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
"""
|
||||||
|
Reads biomass transport costs for different countries of the JRC report
|
||||||
|
|
||||||
|
"The JRC-EU-TIMES model.
|
||||||
|
Bioenergy potentials
|
||||||
|
for EU and neighbouring countries."
|
||||||
|
(2015)
|
||||||
|
|
||||||
|
converts them from units 'EUR per km/ton' -> 'EUR/ (km MWh)'
|
||||||
|
|
||||||
|
assuming as an approximation energy content of wood pellets
|
||||||
|
|
||||||
|
@author: bw0928
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import tabula as tbl
|
||||||
|
|
||||||
|
ENERGY_CONTENT = 4.8 # unit MWh/t (wood pellets)
|
||||||
|
|
||||||
|
def get_countries():
|
||||||
|
|
||||||
|
pandas_options = dict(
|
||||||
|
skiprows=range(6),
|
||||||
|
header=None,
|
||||||
|
index_col=0
|
||||||
|
)
|
||||||
|
|
||||||
|
return tbl.read_pdf(
|
||||||
|
str(snakemake.input.transport_cost_data),
|
||||||
|
pages="145",
|
||||||
|
multiple_tables=False,
|
||||||
|
pandas_options=pandas_options
|
||||||
|
)[0].index
|
||||||
|
|
||||||
|
|
||||||
|
def get_cost_per_tkm(page, countries):
|
||||||
|
|
||||||
|
pandas_options = dict(
|
||||||
|
skiprows=range(6),
|
||||||
|
header=0,
|
||||||
|
sep=' |,',
|
||||||
|
engine='python',
|
||||||
|
index_col=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
sc = tbl.read_pdf(
|
||||||
|
str(snakemake.input.transport_cost_data),
|
||||||
|
pages=page,
|
||||||
|
multiple_tables=False,
|
||||||
|
pandas_options=pandas_options
|
||||||
|
)[0]
|
||||||
|
sc.index = countries
|
||||||
|
sc.columns = sc.columns.str.replace("€", "EUR")
|
||||||
|
|
||||||
|
return sc
|
||||||
|
|
||||||
|
|
||||||
|
def build_biomass_transport_costs():
|
||||||
|
|
||||||
|
countries = get_countries()
|
||||||
|
|
||||||
|
sc1 = get_cost_per_tkm(146, countries)
|
||||||
|
sc2 = get_cost_per_tkm(147, countries)
|
||||||
|
|
||||||
|
# take mean of both supply chains
|
||||||
|
to_concat = [sc1["EUR/km/ton"], sc2["EUR/km/ton"]]
|
||||||
|
transport_costs = pd.concat(to_concat, axis=1).mean(axis=1)
|
||||||
|
|
||||||
|
# convert tonnes to MWh
|
||||||
|
transport_costs /= ENERGY_CONTENT
|
||||||
|
transport_costs.name = "EUR/km/MWh"
|
||||||
|
|
||||||
|
# rename country names
|
||||||
|
to_rename = {
|
||||||
|
"UK": "GB",
|
||||||
|
"XK": "KO",
|
||||||
|
"EL": "GR"
|
||||||
|
}
|
||||||
|
transport_costs.rename(to_rename, inplace=True)
|
||||||
|
|
||||||
|
# add missing Norway with data from Sweden
|
||||||
|
transport_costs["NO"] = transport_costs["SE"]
|
||||||
|
|
||||||
|
transport_costs.to_csv(snakemake.output[0])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
build_biomass_transport_costs()
|
@ -19,10 +19,60 @@ from helper import override_component_attrs
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from types import SimpleNamespace
|
||||||
|
spatial = SimpleNamespace()
|
||||||
|
|
||||||
|
|
||||||
|
def define_spatial(nodes):
|
||||||
|
"""
|
||||||
|
Namespace for spatial
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
nodes : list-like
|
||||||
|
"""
|
||||||
|
|
||||||
|
global spatial
|
||||||
|
global options
|
||||||
|
|
||||||
|
spatial.nodes = nodes
|
||||||
|
|
||||||
|
# biomass
|
||||||
|
|
||||||
|
spatial.biomass = SimpleNamespace()
|
||||||
|
|
||||||
|
if options["biomass_transport"]:
|
||||||
|
spatial.biomass.nodes = nodes + " solid biomass"
|
||||||
|
spatial.biomass.locations = nodes
|
||||||
|
spatial.biomass.industry = nodes + " solid biomass for industry"
|
||||||
|
spatial.biomass.industry_cc = nodes + " solid biomass for industry CC"
|
||||||
|
else:
|
||||||
|
spatial.biomass.nodes = ["EU solid biomass"]
|
||||||
|
spatial.biomass.locations = ["EU"]
|
||||||
|
spatial.biomass.industry = ["solid biomass for industry"]
|
||||||
|
spatial.biomass.industry_cc = ["solid biomass for industry CC"]
|
||||||
|
|
||||||
|
spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes)
|
||||||
|
|
||||||
|
# co2
|
||||||
|
|
||||||
|
spatial.co2 = SimpleNamespace()
|
||||||
|
|
||||||
|
if options["co2_network"]:
|
||||||
|
spatial.co2.nodes = nodes + " co2 stored"
|
||||||
|
spatial.co2.locations = nodes
|
||||||
|
spatial.co2.vents = nodes + " co2 vent"
|
||||||
|
else:
|
||||||
|
spatial.co2.nodes = ["co2 stored"]
|
||||||
|
spatial.co2.locations = ["EU"]
|
||||||
|
spatial.co2.vents = ["co2 vent"]
|
||||||
|
|
||||||
|
spatial.co2.df = pd.DataFrame(vars(spatial.co2), index=nodes)
|
||||||
|
|
||||||
|
|
||||||
def emission_sectors_from_opts(opts):
|
def emission_sectors_from_opts(opts):
|
||||||
|
|
||||||
sectors = ["electricity"]
|
sectors = ["electricity"]
|
||||||
if "T" in opts:
|
if "T" in opts:
|
||||||
sectors += [
|
sectors += [
|
||||||
"rail non-elec",
|
"rail non-elec",
|
||||||
@ -54,6 +104,40 @@ def get(item, investment_year=None):
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def create_network_topology(n, prefix, connector=" -> "):
|
||||||
|
"""
|
||||||
|
Create a network topology like the power transmission network.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
n : pypsa.Network
|
||||||
|
prefix : str
|
||||||
|
connector : str
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pd.DataFrame with columns bus0, bus1 and length
|
||||||
|
"""
|
||||||
|
|
||||||
|
ln_attrs = ["bus0", "bus1", "length"]
|
||||||
|
lk_attrs = ["bus0", "bus1", "length", "underwater_fraction"]
|
||||||
|
|
||||||
|
candidates = pd.concat([
|
||||||
|
n.lines[ln_attrs],
|
||||||
|
n.links.loc[n.links.carrier == "DC", lk_attrs]
|
||||||
|
]).fillna(0)
|
||||||
|
|
||||||
|
positive_order = candidates.bus0 < candidates.bus1
|
||||||
|
candidates_p = candidates[positive_order]
|
||||||
|
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
|
||||||
|
candidates_n = candidates[~positive_order].rename(columns=swap_buses)
|
||||||
|
candidates = pd.concat([candidates_p, candidates_n])
|
||||||
|
|
||||||
|
topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean()
|
||||||
|
topo.index = topo.apply(lambda c: prefix + c.bus0 + connector + c.bus1, axis=1)
|
||||||
|
return topo
|
||||||
|
|
||||||
|
|
||||||
def co2_emissions_year(countries, opts, year):
|
def co2_emissions_year(countries, opts, year):
|
||||||
"""
|
"""
|
||||||
Calculate CO2 emissions in one specific year (e.g. 1990 or 2018).
|
Calculate CO2 emissions in one specific year (e.g. 1990 or 2018).
|
||||||
@ -141,6 +225,53 @@ def add_lifetime_wind_solar(n, costs):
|
|||||||
n.generators.loc[gen_i, "lifetime"] = costs.at[carrier, 'lifetime']
|
n.generators.loc[gen_i, "lifetime"] = costs.at[carrier, 'lifetime']
|
||||||
|
|
||||||
|
|
||||||
|
def create_network_topology(n, prefix, connector=" -> ", bidirectional=True):
|
||||||
|
"""
|
||||||
|
Create a network topology like the power transmission network.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
n : pypsa.Network
|
||||||
|
prefix : str
|
||||||
|
connector : str
|
||||||
|
bidirectional : bool, default True
|
||||||
|
True: one link for each connection
|
||||||
|
False: one link for each connection and direction (back and forth)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
pd.DataFrame with columns bus0, bus1 and length
|
||||||
|
"""
|
||||||
|
|
||||||
|
ln_attrs = ["bus0", "bus1", "length"]
|
||||||
|
lk_attrs = ["bus0", "bus1", "length", "underwater_fraction"]
|
||||||
|
|
||||||
|
candidates = pd.concat([
|
||||||
|
n.lines[ln_attrs],
|
||||||
|
n.links.loc[n.links.carrier == "DC", lk_attrs]
|
||||||
|
]).fillna(0)
|
||||||
|
|
||||||
|
positive_order = candidates.bus0 < candidates.bus1
|
||||||
|
candidates_p = candidates[positive_order]
|
||||||
|
swap_buses = {"bus0": "bus1", "bus1": "bus0"}
|
||||||
|
candidates_n = candidates[~positive_order].rename(columns=swap_buses)
|
||||||
|
candidates = pd.concat([candidates_p, candidates_n])
|
||||||
|
|
||||||
|
def make_index(c):
|
||||||
|
return prefix + c.bus0 + connector + c.bus1
|
||||||
|
|
||||||
|
topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean()
|
||||||
|
topo.index = topo.apply(make_index, axis=1)
|
||||||
|
|
||||||
|
if not bidirectional:
|
||||||
|
topo_reverse = topo.copy()
|
||||||
|
topo_reverse.rename(columns=swap_buses, inplace=True)
|
||||||
|
topo_reverse.index = topo_reverse.apply(make_index, axis=1)
|
||||||
|
topo = topo.append(topo_reverse)
|
||||||
|
|
||||||
|
return topo
|
||||||
|
|
||||||
|
|
||||||
# TODO merge issue with PyPSA-Eur
|
# TODO merge issue with PyPSA-Eur
|
||||||
def update_wind_solar_costs(n, costs):
|
def update_wind_solar_costs(n, costs):
|
||||||
"""
|
"""
|
||||||
@ -299,26 +430,26 @@ def add_co2_tracking(n, options):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# this tracks CO2 stored, e.g. underground
|
# this tracks CO2 stored, e.g. underground
|
||||||
n.add("Bus",
|
n.madd("Bus",
|
||||||
"co2 stored",
|
spatial.co2.nodes,
|
||||||
location="EU",
|
location=spatial.co2.locations,
|
||||||
carrier="co2 stored"
|
carrier="co2 stored"
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Store",
|
n.madd("Store",
|
||||||
"co2 stored",
|
spatial.co2.nodes,
|
||||||
e_nom_extendable=True,
|
e_nom_extendable=True,
|
||||||
e_nom_max=options['co2_sequestration_potential'] * 1e6,
|
e_nom_max=np.inf,
|
||||||
capital_cost=options['co2_sequestration_cost'],
|
capital_cost=options['co2_sequestration_cost'],
|
||||||
carrier="co2 stored",
|
carrier="co2 stored",
|
||||||
bus="co2 stored"
|
bus=spatial.co2.nodes
|
||||||
)
|
)
|
||||||
|
|
||||||
if options['co2_vent']:
|
if options['co2_vent']:
|
||||||
|
|
||||||
n.add("Link",
|
n.madd("Link",
|
||||||
"co2 vent",
|
spatial.co2.vents,
|
||||||
bus0="co2 stored",
|
bus0=spatial.co2.nodes,
|
||||||
bus1="co2 atmosphere",
|
bus1="co2 atmosphere",
|
||||||
carrier="co2 vent",
|
carrier="co2 vent",
|
||||||
efficiency=1.,
|
efficiency=1.,
|
||||||
@ -326,6 +457,28 @@ def add_co2_tracking(n, options):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_co2_network(n, costs):
|
||||||
|
|
||||||
|
logger.info("Adding CO2 network.")
|
||||||
|
co2_links = create_network_topology(n, "CO2 pipeline ")
|
||||||
|
|
||||||
|
cost_onshore = (1 - co2_links.underwater_fraction) * costs.at['CO2 pipeline', 'fixed'] * co2_links.length
|
||||||
|
cost_submarine = co2_links.underwater_fraction * costs.at['CO2 submarine pipeline', 'fixed'] * co2_links.length
|
||||||
|
capital_cost = cost_onshore + cost_submarine
|
||||||
|
|
||||||
|
n.madd("Link",
|
||||||
|
co2_links.index,
|
||||||
|
bus0=co2_links.bus0.values + " co2 stored",
|
||||||
|
bus1=co2_links.bus1.values + " co2 stored",
|
||||||
|
p_min_pu=-1,
|
||||||
|
p_nom_extendable=True,
|
||||||
|
length=co2_links.length.values,
|
||||||
|
capital_cost=capital_cost.values,
|
||||||
|
carrier="CO2 pipeline",
|
||||||
|
lifetime=costs.at['CO2 pipeline', 'lifetime']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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"]
|
||||||
@ -339,7 +492,7 @@ def add_dac(n, costs):
|
|||||||
locations,
|
locations,
|
||||||
suffix=" DAC",
|
suffix=" DAC",
|
||||||
bus0="co2 atmosphere",
|
bus0="co2 atmosphere",
|
||||||
bus1="co2 stored",
|
bus1=spatial.co2.df.loc[locations, "nodes"].values,
|
||||||
bus2=locations.values,
|
bus2=locations.values,
|
||||||
bus3=heat_buses,
|
bus3=heat_buses,
|
||||||
carrier="DAC",
|
carrier="DAC",
|
||||||
@ -990,10 +1143,11 @@ def add_storage(n, costs):
|
|||||||
if options['methanation']:
|
if options['methanation']:
|
||||||
|
|
||||||
n.madd("Link",
|
n.madd("Link",
|
||||||
nodes + " Sabatier",
|
spatial.nodes,
|
||||||
|
suffix=" Sabatier",
|
||||||
bus0=nodes + " H2",
|
bus0=nodes + " H2",
|
||||||
bus1="EU gas",
|
bus1="EU gas",
|
||||||
bus2="co2 stored",
|
bus2=spatial.co2.nodes,
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
carrier="Sabatier",
|
carrier="Sabatier",
|
||||||
efficiency=costs.at["methanation", "efficiency"],
|
efficiency=costs.at["methanation", "efficiency"],
|
||||||
@ -1005,10 +1159,11 @@ def add_storage(n, costs):
|
|||||||
if options['helmeth']:
|
if options['helmeth']:
|
||||||
|
|
||||||
n.madd("Link",
|
n.madd("Link",
|
||||||
nodes + " helmeth",
|
spatial.nodes,
|
||||||
|
suffix=" helmeth",
|
||||||
bus0=nodes,
|
bus0=nodes,
|
||||||
bus1="EU gas",
|
bus1="EU gas",
|
||||||
bus2="co2 stored",
|
bus2=spatial.co2.nodes,
|
||||||
carrier="helmeth",
|
carrier="helmeth",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
efficiency=costs.at["helmeth", "efficiency"],
|
efficiency=costs.at["helmeth", "efficiency"],
|
||||||
@ -1021,11 +1176,12 @@ def add_storage(n, costs):
|
|||||||
if options['SMR']:
|
if options['SMR']:
|
||||||
|
|
||||||
n.madd("Link",
|
n.madd("Link",
|
||||||
nodes + " SMR CC",
|
spatial.nodes,
|
||||||
|
suffix=" SMR CC",
|
||||||
bus0="EU gas",
|
bus0="EU gas",
|
||||||
bus1=nodes + " H2",
|
bus1=nodes + " H2",
|
||||||
bus2="co2 atmosphere",
|
bus2="co2 atmosphere",
|
||||||
bus3="co2 stored",
|
bus3=spatial.co2.nodes,
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
carrier="SMR CC",
|
carrier="SMR CC",
|
||||||
efficiency=costs.at["SMR CC", "efficiency"],
|
efficiency=costs.at["SMR CC", "efficiency"],
|
||||||
@ -1374,7 +1530,7 @@ def add_heat(n, costs):
|
|||||||
bus1=nodes[name],
|
bus1=nodes[name],
|
||||||
bus2=nodes[name] + " urban central heat",
|
bus2=nodes[name] + " urban central heat",
|
||||||
bus3="co2 atmosphere",
|
bus3="co2 atmosphere",
|
||||||
bus4="co2 stored",
|
bus4=spatial.co2.df.loc[nodes[name], "nodes"].values,
|
||||||
carrier="urban central gas CHP CC",
|
carrier="urban central gas CHP CC",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at['central gas CHP', 'fixed']*costs.at['central gas CHP', 'efficiency'] + costs.at['biomass CHP capture', 'fixed']*costs.at['gas', 'CO2 intensity'],
|
capital_cost=costs.at['central gas CHP', 'fixed']*costs.at['central gas CHP', 'efficiency'] + costs.at['biomass CHP capture', 'fixed']*costs.at['gas', 'CO2 intensity'],
|
||||||
@ -1533,8 +1689,16 @@ def add_biomass(n, costs):
|
|||||||
|
|
||||||
biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0)
|
biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0)
|
||||||
|
|
||||||
n.add("Carrier", "biogas")
|
if options["biomass_transport"]:
|
||||||
|
# potential per node distributed within country by population
|
||||||
|
biomass_potentials_spatial = (biomass_potentials.loc[pop_layout.ct]
|
||||||
|
.set_index(pop_layout.index)
|
||||||
|
.mul(pop_layout.fraction, axis="index")
|
||||||
|
.rename(index=lambda x: x + " solid biomass"))
|
||||||
|
else:
|
||||||
|
biomass_potentials_spatial = biomass_potentials.sum()
|
||||||
|
|
||||||
|
n.add("Carrier", "biogas")
|
||||||
n.add("Carrier", "solid biomass")
|
n.add("Carrier", "solid biomass")
|
||||||
|
|
||||||
n.add("Bus",
|
n.add("Bus",
|
||||||
@ -1543,9 +1707,9 @@ def add_biomass(n, costs):
|
|||||||
carrier="biogas"
|
carrier="biogas"
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Bus",
|
n.madd("Bus",
|
||||||
"EU solid biomass",
|
spatial.biomass.nodes,
|
||||||
location="EU",
|
location=spatial.biomass.locations,
|
||||||
carrier="solid biomass"
|
carrier="solid biomass"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1558,13 +1722,13 @@ def add_biomass(n, costs):
|
|||||||
e_initial=biomass_potentials.loc[countries, "biogas"].sum()
|
e_initial=biomass_potentials.loc[countries, "biogas"].sum()
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Store",
|
n.madd("Store",
|
||||||
"EU solid biomass",
|
spatial.biomass.nodes,
|
||||||
bus="EU solid biomass",
|
bus=spatial.biomass.nodes,
|
||||||
carrier="solid biomass",
|
carrier="solid biomass",
|
||||||
e_nom=biomass_potentials.loc[countries, "solid biomass"].sum(),
|
e_nom=biomass_potentials_spatial["solid biomass"],
|
||||||
marginal_cost=costs.at['solid biomass', 'fuel'],
|
marginal_cost=costs.at['solid biomass', 'fuel'],
|
||||||
e_initial=biomass_potentials.loc[countries, "solid biomass"].sum()
|
e_initial=biomass_potentials_spatial["solid biomass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Link",
|
n.add("Link",
|
||||||
@ -1579,6 +1743,32 @@ def add_biomass(n, costs):
|
|||||||
p_nom_extendable=True
|
p_nom_extendable=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if options["biomass_transport"]:
|
||||||
|
|
||||||
|
transport_costs = pd.read_csv(
|
||||||
|
snakemake.input.biomass_transport_costs,
|
||||||
|
index_col=0,
|
||||||
|
squeeze=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# add biomass transport
|
||||||
|
biomass_transport = create_network_topology(n, "biomass transport ", bidirectional=False)
|
||||||
|
|
||||||
|
# costs
|
||||||
|
bus0_costs = biomass_transport.bus0.apply(lambda x: transport_costs[x[:2]])
|
||||||
|
bus1_costs = biomass_transport.bus1.apply(lambda x: transport_costs[x[:2]])
|
||||||
|
biomass_transport["costs"] = pd.concat([bus0_costs, bus1_costs], axis=1).mean(axis=1)
|
||||||
|
|
||||||
|
n.madd("Link",
|
||||||
|
biomass_transport.index,
|
||||||
|
bus0=biomass_transport.bus0 + " solid biomass",
|
||||||
|
bus1=biomass_transport.bus1 + " solid biomass",
|
||||||
|
p_nom_extendable=True,
|
||||||
|
length=biomass_transport.length.values,
|
||||||
|
marginal_cost=biomass_transport.costs * biomass_transport.length.values,
|
||||||
|
capital_cost=1,
|
||||||
|
carrier="solid biomass transport"
|
||||||
|
)
|
||||||
|
|
||||||
#AC buses with district heating
|
#AC buses with district heating
|
||||||
urban_central = n.buses.index[n.buses.carrier == "urban central heat"]
|
urban_central = n.buses.index[n.buses.carrier == "urban central heat"]
|
||||||
@ -1589,7 +1779,7 @@ def add_biomass(n, costs):
|
|||||||
|
|
||||||
n.madd("Link",
|
n.madd("Link",
|
||||||
urban_central + " urban central solid biomass CHP",
|
urban_central + " urban central solid biomass CHP",
|
||||||
bus0="EU solid biomass",
|
bus0=spatial.biomass.df.loc[urban_central, "nodes"].values,
|
||||||
bus1=urban_central,
|
bus1=urban_central,
|
||||||
bus2=urban_central + " urban central heat",
|
bus2=urban_central + " urban central heat",
|
||||||
carrier="urban central solid biomass CHP",
|
carrier="urban central solid biomass CHP",
|
||||||
@ -1603,11 +1793,11 @@ def add_biomass(n, costs):
|
|||||||
|
|
||||||
n.madd("Link",
|
n.madd("Link",
|
||||||
urban_central + " urban central solid biomass CHP CC",
|
urban_central + " urban central solid biomass CHP CC",
|
||||||
bus0="EU solid biomass",
|
bus0=spatial.biomass.df.loc[urban_central, "nodes"].values,
|
||||||
bus1=urban_central,
|
bus1=urban_central,
|
||||||
bus2=urban_central + " urban central heat",
|
bus2=urban_central + " urban central heat",
|
||||||
bus3="co2 atmosphere",
|
bus3="co2 atmosphere",
|
||||||
bus4="co2 stored",
|
bus4=spatial.co2.df.loc[urban_central, "nodes"].values,
|
||||||
carrier="urban central solid biomass CHP CC",
|
carrier="urban central solid biomass CHP CC",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at[key, 'fixed'] * costs.at[key, 'efficiency'] + costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'],
|
capital_cost=costs.at[key, 'fixed'] * costs.at[key, 'efficiency'] + costs.at['biomass CHP capture', 'fixed'] * costs.at['solid biomass', 'CO2 intensity'],
|
||||||
@ -1629,36 +1819,39 @@ def add_industry(n, costs):
|
|||||||
# 1e6 to convert TWh to MWh
|
# 1e6 to convert TWh to MWh
|
||||||
industrial_demand = pd.read_csv(snakemake.input.industrial_demand, index_col=0) * 1e6
|
industrial_demand = pd.read_csv(snakemake.input.industrial_demand, index_col=0) * 1e6
|
||||||
|
|
||||||
solid_biomass_by_country = industrial_demand["solid biomass"].groupby(pop_layout.ct).sum()
|
n.madd("Bus",
|
||||||
|
spatial.biomass.industry,
|
||||||
n.add("Bus",
|
location=spatial.biomass.locations,
|
||||||
"solid biomass for industry",
|
|
||||||
location="EU",
|
|
||||||
carrier="solid biomass for industry"
|
carrier="solid biomass for industry"
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Load",
|
if options["biomass_transport"]:
|
||||||
"solid biomass for industry",
|
p_set = industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760
|
||||||
bus="solid biomass for industry",
|
else:
|
||||||
|
p_set = industrial_demand["solid biomass"].sum() / 8760
|
||||||
|
|
||||||
|
n.madd("Load",
|
||||||
|
spatial.biomass.industry,
|
||||||
|
bus=spatial.biomass.industry,
|
||||||
carrier="solid biomass for industry",
|
carrier="solid biomass for industry",
|
||||||
p_set=solid_biomass_by_country.sum() / 8760
|
p_set=p_set
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Link",
|
n.madd("Link",
|
||||||
"solid biomass for industry",
|
spatial.biomass.industry,
|
||||||
bus0="EU solid biomass",
|
bus0=spatial.biomass.nodes,
|
||||||
bus1="solid biomass for industry",
|
bus1=spatial.biomass.industry,
|
||||||
carrier="solid biomass for industry",
|
carrier="solid biomass for industry",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
efficiency=1.
|
efficiency=1.
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Link",
|
n.madd("Link",
|
||||||
"solid biomass for industry CC",
|
spatial.biomass.industry_cc,
|
||||||
bus0="EU solid biomass",
|
bus0=spatial.biomass.nodes,
|
||||||
bus1="solid biomass for industry",
|
bus1=spatial.biomass.industry,
|
||||||
bus2="co2 atmosphere",
|
bus2="co2 atmosphere",
|
||||||
bus3="co2 stored",
|
bus3=spatial.co2.nodes,
|
||||||
carrier="solid biomass for industry CC",
|
carrier="solid biomass for industry CC",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at["cement capture", "fixed"] * costs.at['solid biomass', 'CO2 intensity'],
|
capital_cost=costs.at["cement capture", "fixed"] * costs.at['solid biomass', 'CO2 intensity'],
|
||||||
@ -1691,12 +1884,13 @@ def add_industry(n, costs):
|
|||||||
efficiency2=costs.at['gas', 'CO2 intensity']
|
efficiency2=costs.at['gas', 'CO2 intensity']
|
||||||
)
|
)
|
||||||
|
|
||||||
n.add("Link",
|
n.madd("Link",
|
||||||
"gas for industry CC",
|
spatial.co2.locations,
|
||||||
|
suffix=" gas for industry CC",
|
||||||
bus0="EU gas",
|
bus0="EU gas",
|
||||||
bus1="gas for industry",
|
bus1="gas for industry",
|
||||||
bus2="co2 atmosphere",
|
bus2="co2 atmosphere",
|
||||||
bus3="co2 stored",
|
bus3=spatial.co2.nodes,
|
||||||
carrier="gas for industry CC",
|
carrier="gas for industry CC",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at["cement capture", "fixed"] * costs.at['gas', 'CO2 intensity'],
|
capital_cost=costs.at["cement capture", "fixed"] * costs.at['gas', 'CO2 intensity'],
|
||||||
@ -1827,7 +2021,7 @@ def add_industry(n, costs):
|
|||||||
nodes + " Fischer-Tropsch",
|
nodes + " Fischer-Tropsch",
|
||||||
bus0=nodes + " H2",
|
bus0=nodes + " H2",
|
||||||
bus1="EU oil",
|
bus1="EU oil",
|
||||||
bus2="co2 stored",
|
bus2=spatial.co2.nodes,
|
||||||
carrier="Fischer-Tropsch",
|
carrier="Fischer-Tropsch",
|
||||||
efficiency=costs.at["Fischer-Tropsch", 'efficiency'],
|
efficiency=costs.at["Fischer-Tropsch", 'efficiency'],
|
||||||
capital_cost=costs.at["Fischer-Tropsch", 'fixed'],
|
capital_cost=costs.at["Fischer-Tropsch", 'fixed'],
|
||||||
@ -1916,11 +2110,12 @@ def add_industry(n, costs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
#assume enough local waste heat for CC
|
#assume enough local waste heat for CC
|
||||||
n.add("Link",
|
n.madd("Link",
|
||||||
"process emissions CC",
|
spatial.co2.locations,
|
||||||
|
suffix=" process emissions CC",
|
||||||
bus0="process emissions",
|
bus0="process emissions",
|
||||||
bus1="co2 atmosphere",
|
bus1="co2 atmosphere",
|
||||||
bus2="co2 stored",
|
bus2=spatial.co2.nodes,
|
||||||
carrier="process emissions CC",
|
carrier="process emissions CC",
|
||||||
p_nom_extendable=True,
|
p_nom_extendable=True,
|
||||||
capital_cost=costs.at["cement capture", "fixed"],
|
capital_cost=costs.at["cement capture", "fixed"],
|
||||||
@ -2039,6 +2234,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
patch_electricity_network(n)
|
patch_electricity_network(n)
|
||||||
|
|
||||||
|
define_spatial(pop_layout.index)
|
||||||
|
|
||||||
if snakemake.config["foresight"] == 'myopic':
|
if snakemake.config["foresight"] == 'myopic':
|
||||||
|
|
||||||
add_lifetime_wind_solar(n, costs)
|
add_lifetime_wind_solar(n, costs)
|
||||||
@ -2061,6 +2258,8 @@ if __name__ == "__main__":
|
|||||||
if o[:4] == "dist":
|
if o[:4] == "dist":
|
||||||
options['electricity_distribution_grid'] = True
|
options['electricity_distribution_grid'] = True
|
||||||
options['electricity_distribution_grid_cost_factor'] = float(o[4:].replace("p", ".").replace("m", "-"))
|
options['electricity_distribution_grid_cost_factor'] = float(o[4:].replace("p", ".").replace("m", "-"))
|
||||||
|
if o == "biomasstransport":
|
||||||
|
options["biomass_transport"] = True
|
||||||
|
|
||||||
nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data = prepare_data(n)
|
nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, nodal_transport_data = prepare_data(n)
|
||||||
|
|
||||||
@ -2091,6 +2290,9 @@ if __name__ == "__main__":
|
|||||||
if "noH2network" in opts:
|
if "noH2network" in opts:
|
||||||
remove_h2_network(n)
|
remove_h2_network(n)
|
||||||
|
|
||||||
|
if options["co2_network"]:
|
||||||
|
add_co2_network(n, costs)
|
||||||
|
|
||||||
for o in opts:
|
for o in opts:
|
||||||
m = re.match(r'^\d+h$', o, re.IGNORECASE)
|
m = re.match(r'^\d+h$', o, re.IGNORECASE)
|
||||||
if m is not None:
|
if m is not None:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import pypsa
|
import pypsa
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
from pypsa.linopt import get_var, linexpr, define_constraints
|
from pypsa.linopt import get_var, linexpr, define_constraints
|
||||||
|
|
||||||
@ -150,8 +151,26 @@ def add_chp_constraints(n):
|
|||||||
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
|
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
|
||||||
|
|
||||||
|
|
||||||
|
def add_co2_sequestration_limit(n, sns):
|
||||||
|
|
||||||
|
co2_stores = n.stores.loc[n.stores.carrier=='co2 stored'].index
|
||||||
|
|
||||||
|
if co2_stores.empty or ('Store', 'e') not in n.variables.index:
|
||||||
|
return
|
||||||
|
|
||||||
|
vars_final_co2_stored = get_var(n, 'Store', 'e').loc[sns[-1], co2_stores]
|
||||||
|
|
||||||
|
lhs = linexpr((1, vars_final_co2_stored)).sum()
|
||||||
|
rhs = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6
|
||||||
|
|
||||||
|
name = 'co2_sequestration_limit'
|
||||||
|
define_constraints(n, lhs, "<=", rhs, 'GlobalConstraint',
|
||||||
|
'mu', axes=pd.Index([name]), spec=name)
|
||||||
|
|
||||||
|
|
||||||
def extra_functionality(n, snapshots):
|
def extra_functionality(n, snapshots):
|
||||||
add_battery_constraints(n)
|
add_battery_constraints(n)
|
||||||
|
add_co2_sequestration_limit(n, snapshots)
|
||||||
|
|
||||||
|
|
||||||
def solve_network(n, config, opts='', **kwargs):
|
def solve_network(n, config, opts='', **kwargs):
|
||||||
|
Loading…
Reference in New Issue
Block a user