update master branch
Merge branch 'master' of https://github.com/PyPSA/pypsa-eur-sec
This commit is contained in:
commit
8cabeb5871
@ -26,7 +26,7 @@ the energy system and includes all greenhouse gas emitters except
|
||||
waste management, agriculture, forestry and land use.
|
||||
|
||||
Please see the [documentation](https://pypsa-eur-sec.readthedocs.io/)
|
||||
for installation instructions and other useful information.
|
||||
for installation instructions and other useful information about the snakemake workflow.
|
||||
|
||||
This diagram gives an overview of the sectors and the links between
|
||||
them:
|
||||
|
@ -8,7 +8,7 @@ wildcard_constraints:
|
||||
clusters="[0-9]+m?",
|
||||
sectors="[+a-zA-Z0-9]+",
|
||||
opts="[-+a-zA-Z0-9]*",
|
||||
sector_opts="[-+a-zA-Z0-9]*"
|
||||
sector_opts="[-+a-zA-Z0-9\.\s]*"
|
||||
|
||||
|
||||
|
||||
@ -292,6 +292,7 @@ rule build_retro_cost:
|
||||
output:
|
||||
retro_cost="resources/retro_cost_{network}_s{simpl}_{clusters}.csv",
|
||||
floor_area="resources/floor_area_{network}_s{simpl}_{clusters}.csv"
|
||||
resources: mem_mb=1000
|
||||
script: "scripts/build_retro_cost.py"
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
|
||||
logging_level: INFO
|
||||
|
||||
@ -15,20 +15,27 @@ scenario:
|
||||
lv: [1.0,1.5] # allowed transmission line volume expansion, can be any float >= 1.0 (today) or "opt"
|
||||
clusters: [45,50] # number of nodes in Europe, any integer between 37 (1 node per country-zone) and several hundred
|
||||
opts: [''] # only relevant for PyPSA-Eur
|
||||
sector_opts: [Co2L0-3H-T-H-B-I-solar3-dist1] # this is where the main scenario settings are
|
||||
sector_opts: [Co2L0-3H-T-H-B-I-solar+p3-dist1] # this is where the main scenario settings are
|
||||
# to really understand the options here, look in scripts/prepare_sector_network.py
|
||||
# Co2Lx specifies the CO2 target in x% of the 1990 values; default will give default (5%);
|
||||
# Co2L0p25 will give 25% CO2 emissions; Co2Lm0p05 will give 5% negative emissions
|
||||
# xH is the temporal resolution; 3H is 3-hourly, i.e. one snapshot every 3 hours
|
||||
# single letters are sectors: T for land transport, H for building heating,
|
||||
# B for biomass supply, I for industry, shipping and aviation
|
||||
# solarx or onwindx changes the available installable potential by factor x
|
||||
# solar+c0.5 reduces the capital cost of solar to 50\% of reference value
|
||||
# solar+p3 multiplies the available installable potential by factor 3
|
||||
# dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv
|
||||
# for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative
|
||||
# emissions throughout the transition path in the timeframe determined by the
|
||||
# planning_horizons), be:beta decay; ex:exponential decay
|
||||
# cb40ex0 distributes a carbon budget of 40 GtCO2 following an exponential
|
||||
# decay with initial growth rate 0
|
||||
planning_horizons : [2030] # investment years for myopic and perfect; or costs year for overnight
|
||||
# for example, set to [2020, 2030, 2040, 2050] for myopic foresight
|
||||
|
||||
# CO2 budget as a fraction of 1990 emissions
|
||||
# this is over-ridden if CO2Lx is set in sector_opts
|
||||
# this is also over-ridden if cb is set in sector_opts
|
||||
co2_budget:
|
||||
2020: 0.7011648746
|
||||
2025: 0.5241935484
|
||||
@ -56,11 +63,21 @@ electricity:
|
||||
battery: 6
|
||||
H2: 168
|
||||
|
||||
# regulate what components with which carriers are kept from PyPSA-Eur;
|
||||
# some technologies are removed because they are implemented differently
|
||||
# or have different year-dependent costs in PyPSA-Eur-Sec
|
||||
pypsa_eur:
|
||||
"Bus": ["AC"]
|
||||
"Link": ["DC"]
|
||||
"Generator": ["onwind", "offwind-ac", "offwind-dc", "solar", "ror"]
|
||||
"StorageUnit": ["PHS","hydro"]
|
||||
"Store": []
|
||||
|
||||
biomass:
|
||||
year: 2030
|
||||
scenario: "Med"
|
||||
classes:
|
||||
solid biomass: ['Primary agricultural residues', 'Forestry energy residue', 'Secondary forestry residues', 'Secondary Forestry residues – sawdust', 'Forestry residues from landscape care biomass', 'Municipal waste']
|
||||
solid biomass: ['Primary agricultural residues', 'Forestry energy residue', 'Secondary forestry residues', 'Secondary Forestry residues sawdust', 'Forestry residues from landscape care biomass', 'Municipal waste']
|
||||
not included: ['Bioethanol sugar beet biomass', 'Rapeseeds for biodiesel', 'sunflower and soya for Biodiesel', 'Starchy crops biomass', 'Grassy crops biomass', 'Willow biomass', 'Poplar biomass potential', 'Roundwood fuelwood', 'Roundwood Chips & Pellets']
|
||||
biogas: ['Manure biomass potential', 'Sludge biomass']
|
||||
|
||||
@ -85,7 +102,7 @@ sector:
|
||||
'bev_dsm' : True #turns on EV battery
|
||||
'bev_availability' : 0.5 #How many cars do smart charging
|
||||
'v2g' : True #allows feed-in to grid from EV battery
|
||||
#what is not EV or FCEV is fossil-fuelled
|
||||
#what is not EV or FCEV is oil-fuelled ICE
|
||||
'land_transport_fuel_cell_share': # 1 means all FCEVs
|
||||
2020: 0
|
||||
2030: 0.05
|
||||
@ -99,7 +116,9 @@ sector:
|
||||
'transport_fuel_cell_efficiency': 0.5
|
||||
'transport_internal_combustion_efficiency': 0.3
|
||||
'shipping_average_efficiency' : 0.4 #For conversion of fuel oil to propulsion in 2011
|
||||
'time_dep_hp_cop' : True
|
||||
'time_dep_hp_cop' : True #time dependent heat pump coefficient of performance
|
||||
'heat_pump_sink_T' : 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py
|
||||
# conservatively high to cover hot water and space heating in poorly-insulated buildings
|
||||
'retrofitting' :
|
||||
'retro_exogen': True # space heat demand savings exogenously
|
||||
'dE': # reduction of space heat demand (applied before losses in DH)
|
||||
@ -128,7 +147,9 @@ sector:
|
||||
'dac' : True
|
||||
'co2_vent' : True
|
||||
'SMR' : True
|
||||
'ccs_fraction' : 0.9
|
||||
'co2_sequestration_potential' : 200 #MtCO2/a sequestration potential for Europe
|
||||
'co2_sequestration_cost' : 20 #EUR/tCO2 for transport and sequestration of CO2
|
||||
'cc_fraction' : 0.9 # default fraction of CO2 captured with post-combustion capture
|
||||
'hydrogen_underground_storage' : True
|
||||
'use_fischer_tropsch_waste_heat' : True
|
||||
'use_fuel_cell_waste_heat' : True
|
||||
@ -303,7 +324,7 @@ plotting:
|
||||
"DAC" : "#E74C3C"
|
||||
"co2 stored" : "#123456"
|
||||
"CO2 sequestration" : "#123456"
|
||||
"CCS" : "k"
|
||||
"CC" : "k"
|
||||
"co2" : "#123456"
|
||||
"co2 vent" : "#654321"
|
||||
"solid biomass for industry co2 from atmosphere" : "#654321"
|
||||
@ -313,7 +334,7 @@ plotting:
|
||||
"Fischer-Tropsch" : "#44DD33"
|
||||
"kerosene for aviation": "#44BB11"
|
||||
"naphtha for industry" : "#44FF55"
|
||||
"land transport fossil" : "#44DD33"
|
||||
"land transport oil" : "#44DD33"
|
||||
"water tanks" : "#BBBBBB"
|
||||
"hot water storage" : "#BBBBBB"
|
||||
"hot water charging" : "#BBBBBB"
|
||||
@ -348,6 +369,7 @@ plotting:
|
||||
"process emissions to stored" : "#444444"
|
||||
"process emissions to atmosphere" : "#888888"
|
||||
"process emissions" : "#222222"
|
||||
"oil emissions" : "#666666"
|
||||
"land transport fuel cell" : "#AAAAAA"
|
||||
"biogas" : "#800000"
|
||||
"solid biomass" : "#DAA520"
|
||||
|
@ -70,9 +70,9 @@ author = u'2019-2020 Tom Brown (KIT), Marta Victoria (Aarhus University), Lisa Z
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'0.3'
|
||||
version = u'0.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'0.3.0'
|
||||
release = u'0.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -2,7 +2,7 @@ description,file/folder,licence,source
|
||||
JRC IDEES database,jrc-idees-2015/,CC BY 4.0,https://ec.europa.eu/jrc/en/potencia/jrc-idees
|
||||
urban/rural fraction,urban_percent.csv,unknown,unknown
|
||||
JRC biomass potentials,biomass/,unknown,https://doi.org/10.2790/39014
|
||||
EEA emission statistics,eea/,unknown,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-14
|
||||
EEA emission statistics,eea/UNFCCC_v23.csv,EEA standard re-use policy,https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
|
||||
Eurostat Energy Balances,eurostat-energy_balances-*/,Eurostat,https://ec.europa.eu/eurostat/web/energy/data/energy-balances
|
||||
Swiss energy statistics from Swiss Federal Office of Energy,switzerland-sfoe/,unknown,http://www.bfe.admin.ch/themen/00526/00541/00542/02167/index.html?dossier_id=02169
|
||||
BASt emobility statistics,emobility/,unknown,http://www.bast.de/DE/Verkehrstechnik/Fachthemen/v2-verkehrszaehlung/Stundenwerte.html?nn=626916
|
||||
@ -17,3 +17,10 @@ IRENA existing VRE capacities,existing_infrastructure/{solar|onwind|offwind}_cap
|
||||
USGS ammonia production,myb1-2017-nitro.xls,unknown,https://www.usgs.gov/centers/nmic/nitrogen-statistics-and-information
|
||||
hydrogen salt cavern potentials,hydrogen_salt_cavern_potentials.csv,CC BY 4.0,https://doi.org/10.1016/j.ijhydene.2019.12.161
|
||||
hotmaps industrial site database,Industrial_Database.csv,CC BY 4.0,https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database
|
||||
Hotmaps building stock data,data_building_stock.csv,CC BY 4.0,https://gitlab.com/hotmaps/building-stock
|
||||
U-values Poland,u_values_poland.csv,unknown,https://data.europa.eu/euodp/de/data/dataset/building-stock-observatory
|
||||
Floor area missing in hotmaps building stock data,floor_area_missing.csv,unknown,https://data.europa.eu/euodp/de/data/dataset/building-stock-observatory
|
||||
Comparative level investment,comparative_level_investment.csv,Eurostat,https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Comparative_price_levels_for_investment
|
||||
Electricity taxes,electricity_taxes_eu.csv,Eurostat,https://appsso.eurostat.ec.europa.eu/nui/show.do?dataset=nrg_pc_204&lang=en
|
||||
Average surface components,average_surface_components.csv,unknown,http://webtool.building-typology.eu/#bm
|
||||
Retrofitting thermal envelope costs for Germany,retro_cost_germany.csv,unkown,https://www.iwu.de/forschung/handlungslogiken/kosten-energierelevanter-bau-und-anlagenteile-bei-modernisierung/
|
||||
|
|
@ -66,41 +66,6 @@ PyPSA-Eur-Sec is designed to be imported into the open toolbox `PyPSA <https://w
|
||||
This project is maintained by the `Energy System Modelling group <https://www.iai.kit.edu/english/2338.php>`_ at the `Institute for Automation and Applied Informatics <https://www.iai.kit.edu/english/index.php>`_ at the `Karlsruhe Institute of Technology <http://www.kit.edu/english/index.php>`_. The group is funded by the `Helmholtz Association <https://www.helmholtz.de/en/>`_ until 2024. Previous versions were developed by the `Renewable Energy Group <https://fias.uni-frankfurt.de/physics/schramm/renewable-energy-system-and-network-analysis/>`_ at `FIAS <https://fias.uni-frankfurt.de/>`_ to carry out simulations for the `CoNDyNet project <http://condynet.de/>`_, financed by the `German Federal Ministry for Education and Research (BMBF) <https://www.bmbf.de/en/index.html>`_ as part of the `Stromnetze Research Initiative <http://forschung-stromnetze.info/projekte/grundlagen-und-konzepte-fuer-effiziente-dezentrale-stromnetze/>`_.
|
||||
|
||||
|
||||
Spatial resolution of sectors
|
||||
=============================
|
||||
|
||||
Not all of the sectors are at the full nodal resolution, and some are
|
||||
distributed to nodes using heuristics that need to be corrected. Some
|
||||
networks are copper-plated to reduce computational times.
|
||||
|
||||
For example:
|
||||
|
||||
Electricity network: nodal.
|
||||
|
||||
Electricity demand: nodal, distributed in each country based on
|
||||
population, GDP and location of industrial facilities.
|
||||
|
||||
Building heating demand: nodal, distributed in each country based on
|
||||
population.
|
||||
|
||||
Industry demand: nodal, distributed in each country based on
|
||||
locations of industry from `HotMaps database <https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_.
|
||||
|
||||
Hydrogen network: nodal.
|
||||
|
||||
Methane network: single node for Europe, since future demand is so
|
||||
low and no bottlenecks are expected.
|
||||
|
||||
Solid biomass: single node for Europe, until transport costs can be
|
||||
incorporated.
|
||||
|
||||
CO2: single node for Europe, but a transport and storage cost is added for
|
||||
sequestered CO2.
|
||||
|
||||
Liquid hydrocarbons: single node for Europe, since transport costs are low.
|
||||
|
||||
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
@ -115,6 +80,20 @@ Documentation
|
||||
|
||||
installation
|
||||
|
||||
**Implementation details**
|
||||
|
||||
* :doc:`spatial_resolution`
|
||||
* :doc:`supply_demand`
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Implementation details
|
||||
|
||||
spatial_resolution
|
||||
supply_demand
|
||||
|
||||
|
||||
**Foresight options**
|
||||
|
||||
* :doc:`overnight`
|
||||
|
@ -16,7 +16,7 @@ its dependencies. Clone the repository:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
projects % git clone git@github.com:PyPSA/pypsa-eur.git
|
||||
projects % git clone https://github.com/PyPSA/pypsa-eur.git
|
||||
|
||||
then download and unpack all the PyPSA-Eur data files by running the following snakemake rule:
|
||||
|
||||
@ -32,7 +32,7 @@ Next install the technology assumptions database `technology-data <https://githu
|
||||
|
||||
.. code:: bash
|
||||
|
||||
projects % git clone git@github.com:PyPSA/technology-data.git
|
||||
projects % git clone https://github.com/PyPSA/technology-data.git
|
||||
|
||||
|
||||
Clone PyPSA-Eur-Sec repository
|
||||
@ -42,7 +42,7 @@ Create a parallel directory for `PyPSA-Eur-Sec <https://github.com/PyPSA/pypsa-e
|
||||
|
||||
.. code:: bash
|
||||
|
||||
projects % git clone git@github.com:PyPSA/pypsa-eur-sec.git
|
||||
projects % git clone https://github.com/PyPSA/pypsa-eur-sec.git
|
||||
|
||||
Environment/package requirements
|
||||
================================
|
||||
@ -54,6 +54,13 @@ The requirements are the same as `PyPSA-Eur <https://github.com/PyPSA/pypsa-eur>
|
||||
xarray version >= 0.15.1, you will need the latest master branch of
|
||||
atlite version 0.0.2.
|
||||
|
||||
You can create an enviroment using the environment.yaml file in pypsa-eur/envs:
|
||||
|
||||
.../pypsa-eur % conda env create -f envs/environment.yaml
|
||||
|
||||
.../pypsa-eur % conda activate pypsa-eur
|
||||
|
||||
See details in `PyPSA-Eur Installation <https://pypsa-eur.readthedocs.io/en/latest/installation.html>`_
|
||||
|
||||
Data requirements
|
||||
=================
|
||||
@ -66,8 +73,8 @@ To download and extract the data bundle on the command line:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
projects/pypsa-eur-sec/data % wget "https://nworbmot.org/pypsa-eur-sec-data-bundle-201012.tar.gz"
|
||||
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle-201012.tar.gz
|
||||
projects/pypsa-eur-sec/data % wget "https://nworbmot.org/pypsa-eur-sec-data-bundle-210125.tar.gz"
|
||||
projects/pypsa-eur-sec/data % tar xvzf pypsa-eur-sec-data-bundle-210125.tar.gz
|
||||
|
||||
|
||||
The data licences and sources are given in the following table.
|
||||
|
@ -6,7 +6,7 @@ Myopic transition path
|
||||
|
||||
The myopic code can be used to investigate progressive changes in a network, for instance, those taking place throughout a transition path. The capacities installed in a certain time step are maintained in the network until their operational lifetime expires.
|
||||
|
||||
The myopic approach was initially developed and used in the paper `Early decarbonisation of the European Energy system pays off (2020) <https://arxiv.org/abs/2004.11009>`__ but the current implementation complies with the pypsa-eur-sec standard working flow and is compatible with using the higher resolution electricity transmission model `PyPSA-Eur <https://github.com/PyPSA/pypsa-eur>`__ rather than a one-node-per-country model.
|
||||
The myopic approach was initially developed and used in the paper `Early decarbonisation of the European Energy system pays off (2020) <https://www.nature.com/articles/s41467-020-20015-4>`__ but the current implementation complies with the pypsa-eur-sec standard working flow and is compatible with using the higher resolution electricity transmission model `PyPSA-Eur <https://github.com/PyPSA/pypsa-eur>`__ rather than a one-node-per-country model.
|
||||
|
||||
The current code applies the myopic approach to generators, storage technologies and links in the power sector and the space and water heating sector.
|
||||
|
||||
@ -61,12 +61,15 @@ Wildcards
|
||||
The {planning_horizons} wildcard indicates the timesteps in which the network is optimized, e.g. planning_horizons: [2020, 2030, 2040, 2050]
|
||||
|
||||
|
||||
Options
|
||||
=============
|
||||
The total carbon budget for the entire transition path can be indicated in the ``scenario.sector_opts`` in ``config.yaml``.
|
||||
The carbon budget can be split among the ``planning_horizons`` following an exponential or beta decay.
|
||||
E.g. ``'cb40ex0'`` splits the a carbon budget equal to 40 GtCO_2 following an exponential decay whose initial linear growth rate $r$ is zero
|
||||
|
||||
**{co2_budget_name} wildcard**
|
||||
$e(t) = e_0 (1+ (r+m)t) e^(-mt)$
|
||||
|
||||
The {co2_budget_name} wildcard indicates the name of the co2 budget.
|
||||
|
||||
A csv file is used as input including the planning_horizons as index, the name of co2_budget as column name, and the maximum co2 emissions (relative to 1990) as values.
|
||||
See details in Supplementary Note 1 of the paper `Early decarbonisation of the European Energy system pays off (2020) <https://www.nature.com/articles/s41467-020-20015-4>`__
|
||||
|
||||
Rules overview
|
||||
=================
|
||||
@ -74,17 +77,17 @@ Rules overview
|
||||
General myopic code structure
|
||||
===============================
|
||||
|
||||
The myopic code solves the network for the time steps included in planning_horizons in a recursive loop, so that:
|
||||
The myopic code solves the network for the time steps included in ``planning_horizons`` in a recursive loop, so that:
|
||||
|
||||
1.The existing capacities (those installed before the base year are added as fixed capacities with p_nom=value, p_nom_extendable=False). E.g. for baseyear=2020, capacities installed before 2020 are added. In addition, the network comprises additional generator, storage, and link capacities with p_nom_extendable=True. The non-solved network is saved in ``results/run_name/networks/prenetworks-brownfield``.
|
||||
|
||||
The base year is the first element in planning_horizons. Step 1 is implemented with the rule add_baseyear for the base year and with the rule add_brownfield for the remaining planning_horizons.
|
||||
The base year is the first element in ``planning_horizons``. Step 1 is implemented with the rule add_baseyear for the base year and with the rule add_brownfield for the remaining planning_horizons.
|
||||
|
||||
2.The 2020 network is optimized. The solved network is saved in ‘results/run_name/networks/postnetworks’
|
||||
2.The 2020 network is optimized. The solved network is saved in ``results/run_name/networks/postnetworks``
|
||||
|
||||
3.For the next planning horizon, e.g. 2030, the capacities from a previous time step are added if they are still in operation (i.e., if they fulfil planning horizon <= commissioned year + lifetime). In addition, the network comprises additional generator, storage, and link capacities with p_nom_extendable=True. The non-solved network is saved in ``results/run_name/networks/prenetworks-brownfield``.
|
||||
|
||||
Steps 2 and 3 are solved recursively for all the planning_horizons included in the configuration file.
|
||||
Steps 2 and 3 are solved recursively for all the planning_horizons included in ``config.yaml``.
|
||||
|
||||
|
||||
rule add_existing baseyear
|
||||
@ -110,8 +113,8 @@ Then, the resulting network is saved in ``results/run_name/networks/prenetworks-
|
||||
rule add_brownfield
|
||||
===================
|
||||
|
||||
The rule add_brownfield loads the network in ‘results/run_name/networks/prenetworks’ and performs the following operation:
|
||||
The rule add_brownfield loads the network in ``results/run_name/networks/prenetworks`` and performs the following operation:
|
||||
|
||||
1.Read the capacities optimized in the previous time step and add them to the network if they are still in operation (i.e., if they fulfil planning horizon < commissioned year + lifetime)
|
||||
1.Read the capacities optimized in the previous time step and add them to the network if they are still in operation (i.e., if they fulfill planning horizon < commissioned year + lifetime)
|
||||
|
||||
Then, the resulting network is saved in ``results/run_name/networks/prenetworks_brownfield``.
|
||||
|
@ -2,6 +2,38 @@
|
||||
Release Notes
|
||||
##########################################
|
||||
|
||||
Future release
|
||||
===================
|
||||
|
||||
* For the myopic investment option, a carbon budget and a type of decay (exponential or beta) can be selected in the ``config.yaml`` file to distribute the budget across the ``planning_horizons``. For example, ``cb40ex0`` in the ``{sector_opts}`` wildcard will distribute a carbon budget of 40 GtCO2 following an exponential decay with initial growth rate 0.
|
||||
* Added an option to alter the capital cost or maximum capacity of carriers by a factor via ``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for exploring uncertain cost parameters. Example: ``solar+c0.5`` reduces the ``capital_cost`` of solar to 50\% of original values. Similarly ``solar+p3`` multiplies the ``p_nom_max`` by 3.
|
||||
* Rename the bus for European liquid hydrocarbons from ``Fischer-Tropsch`` to ``EU oil``, since it can be supplied not just with the Fischer-Tropsch process, but also with fossil oil.
|
||||
* Bugfix: Fix reading in of ``pypsa-eur/resources/powerplants.csv`` to PyPSA-Eur Version 0.3.0 (use column attribute name ``DateIn`` instead of old ``YearDecommissioned``).
|
||||
* Bugfix: Make sure that ``Store`` components (battery and H2) are also removed from PyPSA-Eur, so they can be added later by PyPSA-Eur-Sec.
|
||||
|
||||
|
||||
PyPSA-Eur-Sec 0.4.0 (11th December 2020)
|
||||
=========================================
|
||||
|
||||
This release includes a more accurate nodal disaggregation of industry demand within each country, fixes to CHP and CCS representations, as well as changes to some configuration settings.
|
||||
|
||||
It has been released to coincide with `PyPSA-Eur <https://github.com/PyPSA/pypsa-eur>`_ Version 0.3.0 and `Technology Data <https://github.com/PyPSA/technology-data>`_ Version 0.2.0, and is known to work with these releases.
|
||||
|
||||
New features:
|
||||
|
||||
* The `Hotmaps Industrial Database <https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_ is used to disaggregate the industrial demand spatially to the nodes inside each country (previously it was distributed by population density).
|
||||
* Electricity demand from industry is now separated from the regular electricity demand and distributed according to the industry demand. Only the remaining regular electricity demand for households and services is distributed according to GDP and population.
|
||||
* A cost database for the retrofitting of the thermal envelope of residential and services buildings has been integrated, as well as endogenous optimisation of the level of retrofitting. This is described in the paper `Mitigating heat demand peaks in buildings in a highly renewable European energy system <https://arxiv.org/abs/2012.01831>`_. Retrofitting can be activated both exogenously and endogenously from the ``config.yaml``.
|
||||
* The biomass and gas combined heat and power (CHP) parameters ``c_v`` and ``c_b`` were read in assuming they were extraction plants rather than back pressure plants. The data is now corrected in `Technology Data <https://github.com/PyPSA/technology-data>`_ Version 0.2.0 to the correct DEA back pressure assumptions and they are now implemented as single links with a fixed ratio of electricity to heat output (even as extraction plants, they were always sitting on the backpressure line in simulations, so there was no point in modelling the full heat-electricity feasibility polygon). The old assumptions underestimated the heat output.
|
||||
* The Danish Energy Agency released `new assumptions for carbon capture <https://ens.dk/en/our-services/projections-and-models/technology-data/technology-data-industrial-process-heat-and>`_ in October 2020, which have now been incorporated in PyPSA-Eur-Sec, including direct air capture (DAC) and post-combustion capture on CHPs, cement kilns and other industrial facilities. The electricity and heat demand for DAC is modelled for each node (with heat coming from district heating), but currently the electricity and heat demand for industrial capture is not modelled very cleanly (for process heat, 10% of the energy is assumed to go to carbon capture) - a new issue will be opened on this.
|
||||
* Land transport is separated by energy carrier (fossil, hydrogen fuel cell electric vehicle, and electric vehicle), but still needs to be separated into heavy and light vehicles (the data is there, just not the code yet).
|
||||
* For assumptions that change with the investment year, there is a new time-dependent format in the ``config.yaml`` using a dictionary with keys for each year. Implemented examples include the CO2 budget, exogenous retrofitting share and land transport energy carrier; more parameters will be dynamised like this in future.
|
||||
* Some assumptions have been moved out of the code and into the ``config.yaml``, including the carbon sequestration potential and cost, the heat pump sink temperature, reductions in demand for high value chemicals, and some BEV DSM parameters and transport efficiencies.
|
||||
* Documentation on :doc:`supply_demand` options has been added.
|
||||
|
||||
Many thanks to Fraunhofer ISI for opening the hotmaps database and to Lisa Zeyen (KIT) for implementing the building retrofitting.
|
||||
|
||||
|
||||
PyPSA-Eur-Sec 0.3.0 (27th September 2020)
|
||||
=========================================
|
||||
|
||||
@ -92,6 +124,8 @@ Release Process
|
||||
|
||||
* Update version number in ``doc/conf.py`` and ``*config.*.yaml``.
|
||||
|
||||
* Make a ``git commit``.
|
||||
|
||||
* Tag a release by running ``git tag v0.x.x``, ``git push``, ``git push --tags``. Include release notes in the tag message.
|
||||
|
||||
* Make a `GitHub release <https://github.com/PyPSA/pypsa-eur-sec/releases>`_, which automatically triggers archiving by `zenodo <https://doi.org/10.5281/zenodo.3938042>`_.
|
||||
@ -102,4 +136,4 @@ To make a new release of the data bundle, make an archive of the files in ``data
|
||||
|
||||
.. code:: bash
|
||||
|
||||
data % tar pczf pypsa-eur-sec-data-bundle-date.tar.gz eea switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility urban_percent.csv timezone_mappings.csv heat_load_profile_DK_AdamJensen.csv WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv
|
||||
data % tar pczf pypsa-eur-sec-data-bundle-YYMMDD.tar.gz eea/UNFCCC_v23.csv switzerland-sfoe biomass eurostat-energy_balances-* jrc-idees-2015 emobility urban_percent.csv timezone_mappings.csv heat_load_profile_DK_AdamJensen.csv WindWaveWEC_GLTB.xlsx myb1-2017-nitro.xls Industrial_Database.csv
|
||||
|
54
doc/spatial_resolution.rst
Normal file
54
doc/spatial_resolution.rst
Normal file
@ -0,0 +1,54 @@
|
||||
.. _spatial_resolution:
|
||||
|
||||
##########################################
|
||||
Spatial resolution
|
||||
##########################################
|
||||
|
||||
The default nodal resolution of the model follows the electricity
|
||||
generation and transmission model `PyPSA-Eur
|
||||
<https://github.com/PyPSA/pypsa-eur>`_, which clusters down the
|
||||
electricity transmission substations in each European country based on
|
||||
the k-means algorithm. This gives nodes which correspond to major load
|
||||
and generation centres (typically cities).
|
||||
|
||||
The total number of nodes for Europe is set in the ``config.yaml`` file
|
||||
under ``clusters``. The number of nodes can vary between 37, the number
|
||||
of independent countries / synchronous areas, and several
|
||||
hundred. With 200-300 nodes the model needs 100-150 GB RAM to solve
|
||||
with a commerical solver like Gurobi.
|
||||
|
||||
|
||||
Not all of the sectors are at the full nodal resolution, and some
|
||||
demand for some sectors is distributed to nodes using heuristics that
|
||||
need to be corrected. Some networks are copper-plated to reduce
|
||||
computational times.
|
||||
|
||||
For example:
|
||||
|
||||
Electricity network: nodal.
|
||||
|
||||
Electricity residential and commercial demand: nodal, distributed in
|
||||
each country based on population and GDP.
|
||||
|
||||
Electricity demand in industry: based on the location of industrial
|
||||
facilities from `HotMaps database <https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_.
|
||||
|
||||
Building heating demand: nodal, distributed in each country based on
|
||||
population.
|
||||
|
||||
Industry demand: nodal, distributed in each country based on
|
||||
locations of industry from `HotMaps database <https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_.
|
||||
|
||||
Hydrogen network: nodal.
|
||||
|
||||
Methane network: single node for Europe, since future demand is so
|
||||
low and no bottlenecks are expected.
|
||||
|
||||
Solid biomass: single node for Europe, until transport costs can be
|
||||
incorporated.
|
||||
|
||||
CO2: single node for Europe, but a transport and storage cost is added for
|
||||
sequestered CO2.
|
||||
|
||||
Liquid hydrocarbons: single node for Europe, since transport costs for
|
||||
liquids are low.
|
193
doc/supply_demand.rst
Normal file
193
doc/supply_demand.rst
Normal file
@ -0,0 +1,193 @@
|
||||
.. _supply_demand:
|
||||
|
||||
##########################################
|
||||
Supply and demand
|
||||
##########################################
|
||||
|
||||
An initial orientation to the supply and demand options in the model
|
||||
PyPSA-Eur-Sec can be found in the description of the model
|
||||
PyPSA-Eur-Sec-30 in the paper `Synergies of sector coupling and
|
||||
transmission reinforcement in a cost-optimised, highly renewable
|
||||
European energy system <https://arxiv.org/abs/1801.05290>`_ (2018).
|
||||
The latest version of PyPSA-Eur-Sec differs by including biomass,
|
||||
industry, industrial feedstocks, aviation, shipping, better carbon
|
||||
management, carbon capture and usage/sequestration, and gas networks.
|
||||
|
||||
The basic supply (left column) and demand (right column) options in the model are described in this figure:
|
||||
|
||||
.. image:: ../graphics/multisector_figure.png
|
||||
|
||||
|
||||
|
||||
Electricity supply and demand
|
||||
=============================
|
||||
|
||||
Electricity supply and demand follows the electricity generation and
|
||||
transmission model `PyPSA-Eur <https://github.com/PyPSA/pypsa-eur>`_,
|
||||
except that hydrogen storage is integrated into the hydrogen supply,
|
||||
demand and network, and PyPSA-Eur-Sec includes CHPs.
|
||||
|
||||
Unlike PyPSA-Eur, PyPSA-Eur-Sec does not distribution electricity demand for industry according to population and GDP, but uses the
|
||||
geographical data from the `Hotmaps Industrial Database
|
||||
<https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_.
|
||||
|
||||
Also unlike PyPSA-Eur, PyPSA-Eur-Sec subtracts existing electrified heating from the existing electricity demand, so that power-to-heat can be optimised separately.
|
||||
|
||||
The remaining electricity demand for households and services is distributed inside each country proportional to GDP and population.
|
||||
|
||||
|
||||
Heat demand
|
||||
=============================
|
||||
|
||||
Heat demand is split into:
|
||||
|
||||
* ``urban central``: large-scale district heating networks in urban areas with dense heat demand
|
||||
* ``residential/services urban decentral``: heating for individual buildings in urban areas
|
||||
* ``residential/services rural``: heating for individual buildings in rural areas
|
||||
|
||||
|
||||
Heat supply
|
||||
=======================
|
||||
|
||||
Oil and gas boilers
|
||||
--------------------
|
||||
|
||||
Heat pumps
|
||||
-------------
|
||||
|
||||
Either air-to-water or ground-to-water heat pumps are implemented.
|
||||
|
||||
They have coefficient of performance (COP) based on either the
|
||||
external air or the soil hourly temperature.
|
||||
|
||||
Ground-source heat pumps are only allowed in rural areas because of
|
||||
space constraints.
|
||||
|
||||
Only air-source heat pumps are allowed in urban areas. This is a
|
||||
conservative assumption, since there are many possible sources of
|
||||
low-temperature heat that could be tapped in cities (waste water,
|
||||
rivers, lakes, seas, etc.).
|
||||
|
||||
Resistive heaters
|
||||
--------------------
|
||||
|
||||
|
||||
Large Combined Heat and Power (CHP) plants
|
||||
--------------------------------------------
|
||||
|
||||
A good summary of CHP options that can be implemented in PyPSA can be found in the paper `Cost sensitivity of optimal sector-coupled district heating production systems <https://doi.org/10.1016/j.energy.2018.10.044>`_.
|
||||
|
||||
PyPSA-Eur-Sec includes CHP plants fuelled by methane, hydrogen and solid biomass from waste and residues.
|
||||
|
||||
Hydrogen CHPs are fuel cells.
|
||||
|
||||
Methane and biomass CHPs are based on back pressure plants operating with a fixed ratio of electricity to heat output. The methane CHP is modelled on the Danish Energy Agency (DEA) "Gas turbine simple cycle (large)" while the solid biomass CHP is based on the DEA's "09b Wood Pellets Medium".
|
||||
|
||||
The efficiencies of each are given on the back pressure line, where the back pressure coefficient ``c_b`` is the electricity output divided by the heat output. The plants are not allowed to deviate from the back pressure line and are implement as ``Link`` objects with a fixed ratio of heat to electricity output.
|
||||
|
||||
|
||||
NB: The old PyPSA-Eur-Sec-30 model assumed an extraction plant (like the DEA coal CHP) for gas which has flexible production of heat and electricity within the feasibility diagram of Figure 4 in the `Synergies paper <https://arxiv.org/abs/1801.05290>`_. We have switched to the DEA back pressure plants since these are more common for smaller plants for biomass, and because the extraction plants were on the back pressure line for 99.5% of the time anyway. The plants were all changed to back pressure in PyPSA-Eur-Sec v0.4.0.
|
||||
|
||||
|
||||
Micro-CHP for individual buildings
|
||||
-----------------------------------
|
||||
|
||||
Optional.
|
||||
|
||||
Waste heat from Fuel Cells, Methanation and Fischer-Tropsch plants
|
||||
-------------------------------------------------------------------
|
||||
|
||||
|
||||
Solar thermal collectors
|
||||
-------------------------
|
||||
|
||||
Thermal energy storage using hot water tanks
|
||||
---------------------------------------------
|
||||
|
||||
Small for decentral applications.
|
||||
|
||||
Big water pit storage for district heating.
|
||||
|
||||
|
||||
Hydrogen demand
|
||||
==================
|
||||
|
||||
Stationary fuel cell CHP.
|
||||
|
||||
Transport applications.
|
||||
|
||||
Industry (ammonia, precursor to hydrocarbons for chemicals and iron/steel).
|
||||
|
||||
|
||||
Hydrogen supply
|
||||
=================
|
||||
|
||||
Steam Methane Reforming (SMR), SMR+CCS, electrolysers.
|
||||
|
||||
|
||||
Methane demand
|
||||
==================
|
||||
|
||||
Can be used in boilers, in CHPs, in industry for high temperature heat, in OCGT.
|
||||
|
||||
Not used in transport because of engine slippage.
|
||||
|
||||
Methane supply
|
||||
=================
|
||||
|
||||
Fossil, biogas, Sabatier (hydrogen to methane), HELMETH (directly power to methane with efficient heat integration).
|
||||
|
||||
|
||||
Solid biomass demand
|
||||
=====================
|
||||
|
||||
Solid biomass provides process heat up to 500 Celsius in industry, as well as feeding CHP plants in district heating networks.
|
||||
|
||||
Solid biomass supply
|
||||
=====================
|
||||
|
||||
Only wastes and residues from the JRC biomass dataset.
|
||||
|
||||
|
||||
Oil product demand
|
||||
=====================
|
||||
|
||||
Transport fuels and naphtha as a feedstock for the chemicals industry.
|
||||
|
||||
Oil product supply
|
||||
======================
|
||||
|
||||
Fossil or Fischer-Tropsch.
|
||||
|
||||
|
||||
Industry demand
|
||||
================
|
||||
|
||||
Based on materials demand from JRC-IDEES and other sources such as the USGS for ammonia.
|
||||
|
||||
Industry is split into many sectors, including iron and steel, ammonia, other basic chemicals, cement, non-metalic minerals, alumuninium, other non-ferrous metals, pulp, paper and printing, food, beverages and tobacco, and other more minor sectors.
|
||||
|
||||
Inside each country the industrial demand is distributed using the `Hotmaps Industrial Database <https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database>`_.
|
||||
|
||||
|
||||
Industry supply
|
||||
================
|
||||
|
||||
Process switching (e.g. from blast furnaces to direct reduction and electric arc furnaces for steel) is defined exogenously.
|
||||
|
||||
Fuel switching for process heat is mostly also done exogenously.
|
||||
|
||||
Solid biomass is used for up to 500 Celsius, mostly in paper and pulp and food and beverages.
|
||||
|
||||
Higher temperatures are met with methane.
|
||||
|
||||
|
||||
Carbon dioxide capture, usage and sequestration (CCU/S)
|
||||
=========================================================
|
||||
|
||||
Carbon dioxide can be captured from industry process emissions,
|
||||
emissions related to industry process heat, combined heat and power
|
||||
plants, and directly from the air (DAC).
|
||||
|
||||
Carbon dioxide can be used as an input for methanation and
|
||||
Fischer-Tropsch fuels, or it can be sequestered underground.
|
@ -125,7 +125,7 @@ def add_existing_renewables(df_agg):
|
||||
if capacity > 0.:
|
||||
df_agg.at[name,"Fueltype"] = tech
|
||||
df_agg.at[name,"Capacity"] = capacity
|
||||
df_agg.at[name,"YearCommissioned"] = year
|
||||
df_agg.at[name,"DateIn"] = year
|
||||
df_agg.at[name,"cluster_bus"] = node
|
||||
|
||||
def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, baseyear):
|
||||
@ -182,7 +182,7 @@ def add_power_capacities_installed_before_baseyear(n, grouping_years, costs, bas
|
||||
add_existing_renewables(df_agg)
|
||||
|
||||
df_agg["grouping_year"] = np.take(grouping_years,
|
||||
np.digitize(df_agg.YearCommissioned,
|
||||
np.digitize(df_agg.DateIn,
|
||||
grouping_years,
|
||||
right=True))
|
||||
|
||||
@ -249,7 +249,7 @@ def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years
|
||||
|
||||
grouping_years : intervals to group existing capacities
|
||||
|
||||
linear decomissioning of heating capacities from 2020 to 2045 is
|
||||
linear decommissioning of heating capacities from 2020 to 2045 is
|
||||
currently assumed
|
||||
|
||||
heating capacities split between residential and services proportional
|
||||
@ -408,18 +408,18 @@ if __name__ == "__main__":
|
||||
if 'snakemake' not in globals():
|
||||
from vresutils.snakemake import MockSnakemake
|
||||
snakemake = MockSnakemake(
|
||||
wildcards=dict(network='elec', simpl='', clusters='39', lv='1.0',
|
||||
sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1',
|
||||
co2_budget_name='b30b3',
|
||||
wildcards=dict(network='elec', simpl='', clusters='45', lv='1.0',
|
||||
sector_opts='Co2L0-3H-T-H-B-I-solar3-dist1',
|
||||
planning_horizons='2020'),
|
||||
input=dict(network='pypsa-eur-sec/results/test/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_{planning_horizons}.nc',
|
||||
input=dict(network='pypsa-eur-sec/results/version-2/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc',
|
||||
powerplants='pypsa-eur/resources/powerplants.csv',
|
||||
busmap_s='pypsa-eur/resources/busmap_{network}_s{simpl}.csv',
|
||||
busmap='pypsa-eur/resources/busmap_{network}_s{simpl}_{clusters}.csv',
|
||||
costs='pypsa-eur-sec/data/costs/costs_{planning_horizons}.csv',
|
||||
costs='technology_data/outputs/costs_{planning_horizons}.csv',
|
||||
cop_air_total="pypsa-eur-sec/resources/cop_air_total_{network}_s{simpl}_{clusters}.nc",
|
||||
cop_soil_total="pypsa-eur-sec/resources/cop_soil_total_{network}_s{simpl}_{clusters}.nc"),
|
||||
output=['pypsa-eur-sec/results/test/prenetworks_brownfield/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc'],
|
||||
cop_soil_total="pypsa-eur-sec/resources/cop_soil_total_{network}_s{simpl}_{clusters}.nc",
|
||||
clustered_pop_layout="pypsa-eur-sec/resources/pop_layout_{network}_s{simpl}_{clusters}.csv",),
|
||||
output=['pypsa-eur-sec/results/version-2/prenetworks_brownfield/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc'],
|
||||
)
|
||||
import yaml
|
||||
with open('config.yaml', encoding='utf8') as f:
|
||||
|
@ -1,3 +1,4 @@
|
||||
# coding: utf-8
|
||||
|
||||
import pandas as pd
|
||||
|
||||
@ -57,7 +58,15 @@ if __name__ == "__main__":
|
||||
snakemake.input['jrc_potentials'] = "data/biomass/JRC Biomass Potentials.xlsx"
|
||||
snakemake.output = Dict()
|
||||
snakemake.output['biomass_potentials'] = 'data/biomass_potentials.csv'
|
||||
snakemake.output['biomass_potentials_all']='resources/biomass_potentials_all.csv'
|
||||
with open('config.yaml', encoding='utf8') as f:
|
||||
snakemake.config = yaml.safe_load(f)
|
||||
|
||||
|
||||
# This is a hack, to be replaced once snakemake is unicode-conform
|
||||
|
||||
if 'Secondary Forestry residues sawdust' in snakemake.config['biomass']['classes']['solid biomass']:
|
||||
snakemake.config['biomass']['classes']['solid biomass'].remove('Secondary Forestry residues sawdust')
|
||||
snakemake.config['biomass']['classes']['solid biomass'].append('Secondary Forestry residues – sawdust')
|
||||
|
||||
build_biomass_potentials()
|
||||
|
@ -9,16 +9,13 @@ import xarray as xr
|
||||
cop_f = {"air" : lambda d_t: 6.81 -0.121*d_t + 0.000630*d_t**2,
|
||||
"soil" : lambda d_t: 8.77 -0.150*d_t + 0.000734*d_t**2}
|
||||
|
||||
sink_T = 55. # Based on DTU / large area radiators
|
||||
|
||||
|
||||
|
||||
for area in ["total", "urban", "rural"]:
|
||||
for source in ["air", "soil"]:
|
||||
|
||||
source_T = xr.open_dataarray(snakemake.input["temp_{}_{}".format(source,area)])
|
||||
|
||||
delta_T = sink_T - source_T
|
||||
delta_T = snakemake.config['sector']['heat_pump_sink_T'] - source_T
|
||||
|
||||
cop = cop_f[source](delta_T)
|
||||
|
||||
|
@ -390,12 +390,12 @@ def build_energy_totals():
|
||||
return clean_df
|
||||
|
||||
|
||||
def build_eea_co2():
|
||||
def build_eea_co2(year=1990):
|
||||
# see ../notebooks/compute_1990_Europe_emissions_for_targets.ipynb
|
||||
|
||||
#https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-14
|
||||
#downloaded 190222 (modified by EEA last on 181130)
|
||||
fn = "data/eea/UNFCCC_v21.csv"
|
||||
#https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
|
||||
#downloaded 201228 (modified by EEA last on 201221)
|
||||
fn = "data/eea/UNFCCC_v23.csv"
|
||||
df = pd.read_csv(fn, encoding="latin-1")
|
||||
df.loc[df["Year"] == "1985-1987","Year"] = 1986
|
||||
df["Year"] = df["Year"].astype(int)
|
||||
@ -418,16 +418,14 @@ def build_eea_co2():
|
||||
e['waste management'] = '5 - Waste management'
|
||||
e['other'] = '6 - Other Sector'
|
||||
e['indirect'] = 'ind_CO2 - Indirect CO2'
|
||||
e["total wL"] = "Total (with LULUCF, with indirect CO2)"
|
||||
e["total woL"] = "Total (without LULUCF, with indirect CO2)"
|
||||
e["total wL"] = "Total (with LULUCF)"
|
||||
e["total woL"] = "Total (without LULUCF)"
|
||||
|
||||
|
||||
pol = "CO2" #["All greenhouse gases - (CO2 equivalent)","CO2"]
|
||||
|
||||
cts = ["CH","EUA","NO"] + eu28_eea
|
||||
|
||||
year = 1990
|
||||
|
||||
emissions = df.loc[idx[cts,pol,year,e.values],"emissions"].unstack("Sector_name").rename(columns=pd.Series(e.index,e.values)).rename(index={"All greenhouse gases - (CO2 equivalent)" : "GHG"},level=1)
|
||||
|
||||
#only take level 0, since level 1 (pol) and level 2 (year) are trivial
|
||||
@ -467,7 +465,7 @@ def build_eurostat_co2(year=1990):
|
||||
return eurostat_co2
|
||||
|
||||
|
||||
def build_co2_totals(year=1990):
|
||||
def build_co2_totals(eea_co2, eurostat_co2, year=1990):
|
||||
|
||||
co2 = eea_co2.reindex(["EU28","NO","CH","BA","RS","AL","ME","MK"] + eu28)
|
||||
|
||||
@ -486,10 +484,6 @@ def build_co2_totals(year=1990):
|
||||
#doesn't include non-energy emissions
|
||||
co2.loc[ct,'agriculture'] = eurostat_co2[ct,"+","+","Agriculture / Forestry"].sum()
|
||||
|
||||
|
||||
|
||||
co2.to_csv(snakemake.output.co2_name)
|
||||
|
||||
return co2
|
||||
|
||||
|
||||
@ -547,7 +541,7 @@ if __name__ == "__main__":
|
||||
snakemake.output['transport_name'] = "data/transport_data.csv"
|
||||
|
||||
snakemake.input = Dict()
|
||||
snakemake.input['nuts3_shapes'] = 'resources/nuts3_shapes.geojson'
|
||||
snakemake.input['nuts3_shapes'] = '../pypsa-eur/resources/nuts3_shapes.geojson'
|
||||
|
||||
nuts3 = gpd.read_file(snakemake.input.nuts3_shapes).set_index('index')
|
||||
population = nuts3['pop'].groupby(nuts3.country).sum()
|
||||
@ -566,6 +560,7 @@ if __name__ == "__main__":
|
||||
|
||||
eurostat_co2 = build_eurostat_co2()
|
||||
|
||||
build_co2_totals()
|
||||
|
||||
co2=build_co2_totals(eea_co2, eurostat_co2, year)
|
||||
co2.to_csv(snakemake.output.co2_name)
|
||||
|
||||
build_transport_data()
|
||||
|
@ -23,7 +23,6 @@ Structure:
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
pd.options.mode.chained_assignment = None
|
||||
|
||||
#%% ************ FUCNTIONS ***************************************************
|
||||
|
||||
@ -175,9 +174,9 @@ def prepare_building_stock_data():
|
||||
area = building_data[(building_data.type == 'Heated area [Mm²]') &
|
||||
(building_data.subsector != "Total")]
|
||||
area_tot = area.groupby(["country", "sector"]).sum()
|
||||
area["weight"] = area.apply(lambda x: x.value /
|
||||
area = pd.concat([area, area.apply(lambda x: x.value /
|
||||
area_tot.value.loc[(x.country, x.sector)],
|
||||
axis=1)
|
||||
axis=1).rename("weight")],axis=1)
|
||||
area = area.groupby(['country', 'sector', 'subsector', 'bage']).sum()
|
||||
area_tot.rename(index=country_iso_dic, inplace=True)
|
||||
|
||||
@ -192,9 +191,9 @@ def prepare_building_stock_data():
|
||||
pop_layout["ct"] = pop_layout.index.str[:2]
|
||||
ct_total = pop_layout.total.groupby(pop_layout["ct"]).sum()
|
||||
|
||||
area_per_pop = area_tot.unstack().apply(lambda x: x / ct_total[x.index])
|
||||
area_per_pop = area_tot.unstack().reindex(index=ct_total.index).apply(lambda x: x / ct_total[x.index])
|
||||
missing_area_ct = ct_total.index.difference(area_tot.index.levels[0])
|
||||
for ct in missing_area_ct:
|
||||
for ct in (missing_area_ct & ct_total.index):
|
||||
averaged_data = pd.DataFrame(
|
||||
area_per_pop.value.reindex(map_for_missings[ct]).mean()
|
||||
* ct_total[ct],
|
||||
@ -233,7 +232,7 @@ def prepare_building_stock_data():
|
||||
# smallest possible today u values for windows 0.8 (passive house standard)
|
||||
# maybe the u values for the glass and not the whole window including frame
|
||||
# for those types assumed in the dataset
|
||||
u_values[(u_values.type=="Windows") & (u_values.value<0.8)]["value"] = 0.8
|
||||
u_values.loc[(u_values.type=="Windows") & (u_values.value<0.8), "value"] = 0.8
|
||||
# drop unnecessary columns
|
||||
u_values.drop(['topic', 'feature','detail', 'estimated','unit'],
|
||||
axis=1, inplace=True, errors="ignore")
|
||||
@ -314,8 +313,12 @@ def calculate_cost_energy_curve(u_values, l_strength, l_weight, average_surface_
|
||||
|
||||
for l in l_strength:
|
||||
u_values[l] = calculate_new_u(u_values, l, l_weight)
|
||||
energy_saved[l] = calculate_dE(u_values, l, average_surface_w)
|
||||
costs[l] = calculate_costs(u_values, l, cost_retro, average_surface)
|
||||
energy_saved = pd.concat([energy_saved,
|
||||
calculate_dE(u_values, l, average_surface_w).rename(l)],
|
||||
axis=1)
|
||||
costs = pd.concat([costs,
|
||||
calculate_costs(u_values, l, cost_retro, average_surface).rename(l)],
|
||||
axis=1)
|
||||
|
||||
# energy and costs per country, sector, subsector and year
|
||||
e_tot = energy_saved.groupby(['country', 'sector', 'subsector', 'bage']).sum()
|
||||
@ -334,11 +337,13 @@ def calculate_cost_energy_curve(u_values, l_strength, l_weight, average_surface_
|
||||
axis=1, keys=["dE", "cost"])
|
||||
res.rename(index=country_iso_dic, inplace=True)
|
||||
|
||||
res = res.loc[countries]
|
||||
res = res.reindex(index=countries, level=0)
|
||||
# reset index because otherwise not considered countries still in index.levels[0]
|
||||
res = res.reset_index().set_index(["country", "sector"])
|
||||
|
||||
# map missing countries
|
||||
for ct in map_for_missings.keys():
|
||||
averaged_data = pd.DataFrame(res.loc[map_for_missings[ct], :].mean(level=1))
|
||||
for ct in pd.Index(map_for_missings.keys()) & countries:
|
||||
averaged_data = res.reindex(index=map_for_missings[ct], level=0).mean(level=1)
|
||||
index = pd.MultiIndex.from_product([[ct], averaged_data.index.to_list()])
|
||||
averaged_data.index = index
|
||||
if ct not in res.index.levels[0]:
|
||||
@ -436,12 +441,14 @@ if __name__ == "__main__":
|
||||
|
||||
# for missing weighting of surfaces of building types assume MultiFamily houses
|
||||
u_values["assumed_subsector"] = u_values.subsector
|
||||
u_values.assumed_subsector[
|
||||
~u_values.subsector.isin(average_surface.index)] = 'Multifamily houses'
|
||||
u_values.loc[~u_values.subsector.isin(average_surface.index),
|
||||
"assumed_subsector"] = 'Multifamily houses'
|
||||
|
||||
dE_and_cost = calculate_cost_energy_curve(u_values, l_strength, l_weight,
|
||||
average_surface_w, average_surface, area,
|
||||
country_iso_dic, countries)
|
||||
# reset index because otherwise not considered countries still in index.levels[0]
|
||||
dE_and_cost = dE_and_cost.reset_index().set_index(["country", "sector"])
|
||||
|
||||
# weights costs after construction index
|
||||
if construction_index:
|
||||
|
@ -28,10 +28,13 @@ opt_name = {"Store": "e", "Line" : "s", "Transformer" : "s"}
|
||||
override_component_attrs = pypsa.descriptors.Dict({k : v.copy() for k,v in pypsa.components.component_attrs.items()})
|
||||
override_component_attrs["Link"].loc["bus2"] = ["string",np.nan,np.nan,"2nd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus3"] = ["string",np.nan,np.nan,"3rd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus4"] = ["string",np.nan,np.nan,"4th bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency2"] = ["static or series","per unit",1.,"2nd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per unit",1.,"3rd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency4"] = ["static or series","per unit",1.,"4th bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p4"] = ["series","MW",0.,"4th bus output","Output"]
|
||||
override_component_attrs["StorageUnit"].loc["p_dispatch"] = ["series","MW",0.,"Storage discharging.","Output"]
|
||||
override_component_attrs["StorageUnit"].loc["p_store"] = ["series","MW",0.,"Storage charging.","Output"]
|
||||
|
||||
@ -193,7 +196,25 @@ def calculate_costs(n,label,costs):
|
||||
|
||||
return costs
|
||||
|
||||
def calculate_cumulative_cost():
|
||||
planning_horizons = snakemake.config['scenario']['planning_horizons']
|
||||
|
||||
cumulative_cost = pd.DataFrame(index = df["costs"].sum().index,
|
||||
columns=pd.Series(data=np.arange(0,0.1, 0.01), name='social discount rate'))
|
||||
|
||||
#discount cost and express them in money value of planning_horizons[0]
|
||||
for r in cumulative_cost.columns:
|
||||
cumulative_cost[r]=[df["costs"].sum()[index]/((1+r)**(index[-1]-planning_horizons[0])) for index in cumulative_cost.index]
|
||||
|
||||
#integrate cost throughout the transition path
|
||||
for r in cumulative_cost.columns:
|
||||
for cluster in cumulative_cost.index.get_level_values(level=0).unique():
|
||||
for lv in cumulative_cost.index.get_level_values(level=1).unique():
|
||||
for sector_opts in cumulative_cost.index.get_level_values(level=2).unique():
|
||||
cumulative_cost.loc[(cluster, lv, sector_opts,'cumulative cost'),r] = np.trapz(cumulative_cost.loc[idx[cluster, lv, sector_opts,planning_horizons],r].values, x=planning_horizons)
|
||||
|
||||
return cumulative_cost
|
||||
|
||||
def calculate_nodal_capacities(n,label,nodal_capacities):
|
||||
#Beware this also has extraneous locations for country (e.g. biomass) or continent-wide (e.g. fossil gas/oil) stuff
|
||||
for c in n.iterate_components(n.branch_components|n.controllable_one_port_components^{"Load"}):
|
||||
@ -561,16 +582,17 @@ if __name__ == "__main__":
|
||||
snakemake.config = yaml.safe_load(f)
|
||||
|
||||
#overwrite some options
|
||||
snakemake.config["run"] = "test"
|
||||
snakemake.config["run"] = "version-8"
|
||||
snakemake.config["scenario"]["lv"] = [1.0]
|
||||
snakemake.config["scenario"]["sector_opts"] = ["Co2L0-168H-T-H-B-I-solar3-dist1"]
|
||||
snakemake.config["scenario"]["sector_opts"] = ["3H-T-H-B-I-solar3-dist1"]
|
||||
snakemake.config["planning_horizons"] = ['2020', '2030', '2040', '2050']
|
||||
snakemake.input = Dict()
|
||||
snakemake.input['heat_demand_name'] = 'data/heating/daily_heat_demand.h5'
|
||||
snakemake.input['costs'] = snakemake.config['costs_dir'] + "costs_{}.csv".format(snakemake.config['scenario']['planning_horizons'][0])
|
||||
snakemake.output = Dict()
|
||||
for item in outputs:
|
||||
snakemake.output[item] = snakemake.config['summary_dir'] + '/{name}/csvs/{item}.csv'.format(name=snakemake.config['run'],item=item)
|
||||
|
||||
snakemake.output['cumulative_cost'] = snakemake.config['summary_dir'] + '/{name}/csvs/cumulative_cost.csv'.format(name=snakemake.config['run'])
|
||||
networks_dict = {(cluster, lv, opt+sector_opt, planning_horizon) :
|
||||
snakemake.config['results_dir'] + snakemake.config['run'] + '/postnetworks/elec_s{simpl}_{cluster}_lv{lv}_{opt}_{sector_opt}_{planning_horizon}.nc'\
|
||||
.format(simpl=simpl,
|
||||
@ -589,6 +611,7 @@ if __name__ == "__main__":
|
||||
print(networks_dict)
|
||||
|
||||
Nyears = 1
|
||||
|
||||
costs_db = prepare_costs(snakemake.input.costs,
|
||||
snakemake.config['costs']['USD2013_to_EUR2013'],
|
||||
snakemake.config['costs']['discountrate'],
|
||||
@ -600,3 +623,10 @@ if __name__ == "__main__":
|
||||
df["metrics"].loc["total costs"] = df["costs"].sum()
|
||||
|
||||
to_csv(df)
|
||||
|
||||
if snakemake.config["foresight"]=='myopic':
|
||||
cumulative_cost=calculate_cumulative_cost()
|
||||
cumulative_cost.to_csv(snakemake.config['summary_dir'] + '/' + snakemake.config['run'] + '/csvs/cumulative_cost.csv')
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
#allow plotting without Xwindows
|
||||
@ -9,7 +9,7 @@ matplotlib.use('Agg')
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
from prepare_sector_network import co2_emissions_year
|
||||
|
||||
#consolidate and rename
|
||||
def rename_techs(label):
|
||||
@ -22,7 +22,7 @@ def rename_techs(label):
|
||||
"retrofitting" : "building retrofitting",
|
||||
"H2" : "hydrogen storage",
|
||||
"battery" : "battery storage",
|
||||
"CCS" : "CCS"}
|
||||
"CC" : "CC"}
|
||||
|
||||
rename = {"solar" : "solar PV",
|
||||
"Sabatier" : "methanation",
|
||||
@ -237,7 +237,137 @@ def plot_balances():
|
||||
|
||||
fig.savefig(snakemake.output.balances[:-10] + k + ".pdf",transparent=True)
|
||||
|
||||
def historical_emissions(cts):
|
||||
"""
|
||||
read historical emissions to add them to the carbon budget plot
|
||||
"""
|
||||
#https://www.eea.europa.eu/data-and-maps/data/national-emissions-reported-to-the-unfccc-and-to-the-eu-greenhouse-gas-monitoring-mechanism-16
|
||||
#downloaded 201228 (modified by EEA last on 201221)
|
||||
fn = "data/eea/UNFCCC_v23.csv"
|
||||
df = pd.read_csv(fn, encoding="latin-1")
|
||||
df.loc[df["Year"] == "1985-1987","Year"] = 1986
|
||||
df["Year"] = df["Year"].astype(int)
|
||||
df = df.set_index(['Year', 'Sector_name', 'Country_code', 'Pollutant_name']).sort_index()
|
||||
|
||||
e = pd.Series()
|
||||
e["electricity"] = '1.A.1.a - Public Electricity and Heat Production'
|
||||
e['residential non-elec'] = '1.A.4.b - Residential'
|
||||
e['services non-elec'] = '1.A.4.a - Commercial/Institutional'
|
||||
e['rail non-elec'] = "1.A.3.c - Railways"
|
||||
e["road non-elec"] = '1.A.3.b - Road Transportation'
|
||||
e["domestic navigation"] = "1.A.3.d - Domestic Navigation"
|
||||
e['international navigation'] = '1.D.1.b - International Navigation'
|
||||
e["domestic aviation"] = '1.A.3.a - Domestic Aviation'
|
||||
e["international aviation"] = '1.D.1.a - International Aviation'
|
||||
e['total energy'] = '1 - Energy'
|
||||
e['industrial processes'] = '2 - Industrial Processes and Product Use'
|
||||
e['agriculture'] = '3 - Agriculture'
|
||||
e['LULUCF'] = '4 - Land Use, Land-Use Change and Forestry'
|
||||
e['waste management'] = '5 - Waste management'
|
||||
e['other'] = '6 - Other Sector'
|
||||
e['indirect'] = 'ind_CO2 - Indirect CO2'
|
||||
e["total wL"] = "Total (with LULUCF)"
|
||||
e["total woL"] = "Total (without LULUCF)"
|
||||
|
||||
pol = ["CO2"] # ["All greenhouse gases - (CO2 equivalent)"]
|
||||
cts
|
||||
if "GB" in cts:
|
||||
cts.remove("GB")
|
||||
cts.append("UK")
|
||||
|
||||
year = np.arange(1990,2018).tolist()
|
||||
|
||||
idx = pd.IndexSlice
|
||||
co2_totals = df.loc[idx[year,e.values,cts,pol],"emissions"].unstack("Year").rename(index=pd.Series(e.index,e.values))
|
||||
|
||||
co2_totals = (1/1e6)*co2_totals.groupby(level=0, axis=0).sum() #Gton CO2
|
||||
|
||||
co2_totals.loc['industrial non-elec'] = co2_totals.loc['total energy'] - co2_totals.loc[['electricity', 'services non-elec','residential non-elec', 'road non-elec',
|
||||
'rail non-elec', 'domestic aviation', 'international aviation', 'domestic navigation',
|
||||
'international navigation']].sum()
|
||||
|
||||
emissions = co2_totals.loc["electricity"]
|
||||
if "T" in opts:
|
||||
emissions += co2_totals.loc[[i+ " non-elec" for i in ["rail","road"]]].sum()
|
||||
if "H" in opts:
|
||||
emissions += co2_totals.loc[[i+ " non-elec" for i in ["residential","services"]]].sum()
|
||||
if "I" in opts:
|
||||
emissions += co2_totals.loc[["industrial non-elec","industrial processes",
|
||||
"domestic aviation","international aviation",
|
||||
"domestic navigation","international navigation"]].sum()
|
||||
return emissions
|
||||
|
||||
|
||||
|
||||
def plot_carbon_budget_distribution():
|
||||
"""
|
||||
Plot historical carbon emissions in the EU and decarbonization path
|
||||
"""
|
||||
|
||||
import matplotlib.gridspec as gridspec
|
||||
import seaborn as sns; sns.set()
|
||||
sns.set_style('ticks')
|
||||
plt.style.use('seaborn-ticks')
|
||||
plt.rcParams['xtick.direction'] = 'in'
|
||||
plt.rcParams['ytick.direction'] = 'in'
|
||||
plt.rcParams['xtick.labelsize'] = 20
|
||||
plt.rcParams['ytick.labelsize'] = 20
|
||||
|
||||
plt.figure(figsize=(10, 7))
|
||||
gs1 = gridspec.GridSpec(1, 1)
|
||||
ax1 = plt.subplot(gs1[0,0])
|
||||
ax1.set_ylabel('CO$_2$ emissions (Gt per year)',fontsize=22)
|
||||
ax1.set_ylim([0,5])
|
||||
ax1.set_xlim([1990,snakemake.config['scenario']['planning_horizons'][-1]+1])
|
||||
|
||||
path_cb = snakemake.config['results_dir'] + snakemake.config['run'] + '/csvs/'
|
||||
countries=pd.read_csv(path_cb + 'countries.csv', index_col=1)
|
||||
cts=countries.index.to_list()
|
||||
e_1990 = co2_emissions_year(cts, opts, year=1990)
|
||||
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv',
|
||||
index_col=0)
|
||||
|
||||
|
||||
ax1.plot(e_1990*CO2_CAP[o],linewidth=3,
|
||||
color='dodgerblue', label=None)
|
||||
|
||||
emissions = historical_emissions(cts)
|
||||
|
||||
ax1.plot(emissions, color='black', linewidth=3, label=None)
|
||||
|
||||
#plot commited and uder-discussion targets
|
||||
#(notice that historical emissions include all countries in the
|
||||
# network, but targets refer to EU)
|
||||
ax1.plot([2020],[0.8*emissions[1990]],
|
||||
marker='*', markersize=12, markerfacecolor='black',
|
||||
markeredgecolor='black')
|
||||
|
||||
ax1.plot([2030],[0.45*emissions[1990]],
|
||||
marker='*', markersize=12, markerfacecolor='white',
|
||||
markeredgecolor='black')
|
||||
|
||||
ax1.plot([2030],[0.6*emissions[1990]],
|
||||
marker='*', markersize=12, markerfacecolor='black',
|
||||
markeredgecolor='black')
|
||||
|
||||
ax1.plot([2050, 2050],[x*emissions[1990] for x in [0.2, 0.05]],
|
||||
color='gray', linewidth=2, marker='_', alpha=0.5)
|
||||
|
||||
ax1.plot([2050],[0.01*emissions[1990]],
|
||||
marker='*', markersize=12, markerfacecolor='white',
|
||||
linewidth=0, markeredgecolor='black',
|
||||
label='EU under-discussion target', zorder=10,
|
||||
clip_on=False)
|
||||
|
||||
ax1.plot([2050],[0.125*emissions[1990]],'ro',
|
||||
marker='*', markersize=12, markerfacecolor='black',
|
||||
markeredgecolor='black', label='EU commited target')
|
||||
|
||||
ax1.legend(fancybox=True, fontsize=18, loc=(0.01,0.01),
|
||||
facecolor='white', frameon=True)
|
||||
|
||||
path_cb_plot = snakemake.config['results_dir'] + snakemake.config['run'] + '/graphs/'
|
||||
plt.savefig(path_cb_plot+'carbon_budget_plot.pdf', dpi=300)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Detect running outside of snakemake and mock snakemake for testing
|
||||
@ -249,13 +379,16 @@ if __name__ == "__main__":
|
||||
snakemake.config = yaml.safe_load(f)
|
||||
snakemake.input = Dict()
|
||||
snakemake.output = Dict()
|
||||
|
||||
snakemake.wildcards = Dict()
|
||||
#snakemake.wildcards['sector_opts']='3H-T-H-B-I-solar3-dist1-cb48be3'
|
||||
|
||||
for item in ["costs", "energy"]:
|
||||
snakemake.input[item] = snakemake.config['summary_dir'] + '/{name}/csvs/{item}.csv'.format(name=snakemake.config['run'],item=item)
|
||||
snakemake.output[item] = snakemake.config['summary_dir'] + '/{name}/graphs/{item}.pdf'.format(name=snakemake.config['run'],item=item)
|
||||
snakemake.input["balances"] = snakemake.config['summary_dir'] + '/test/csvs/supply_energy.csv'
|
||||
snakemake.output["balances"] = snakemake.config['summary_dir'] + '/test/graphs/balances-energy.csv'
|
||||
|
||||
snakemake.input["balances"] = snakemake.config['summary_dir'] + '/{name}/csvs/supply_energy.csv'.format(name=snakemake.config['run'],item=item)
|
||||
snakemake.output["balances"] = snakemake.config['summary_dir'] + '/{name}/graphs/balances-energy.csv'.format(name=snakemake.config['run'],item=item)
|
||||
|
||||
|
||||
n_header = 4
|
||||
|
||||
plot_costs()
|
||||
@ -263,3 +396,9 @@ if __name__ == "__main__":
|
||||
plot_energy()
|
||||
|
||||
plot_balances()
|
||||
|
||||
for sector_opts in snakemake.config['scenario']['sector_opts']:
|
||||
opts=sector_opts.split('-')
|
||||
for o in opts:
|
||||
if "cb" in o:
|
||||
plot_carbon_budget_distribution()
|
@ -20,6 +20,8 @@ import pytz
|
||||
|
||||
from vresutils.costdata import annuity
|
||||
|
||||
from scipy.stats import beta
|
||||
from build_energy_totals import build_eea_co2, build_eurostat_co2, build_co2_totals
|
||||
|
||||
#First tell PyPSA that links can have multiple outputs by
|
||||
#overriding the component_attrs. This can be done for
|
||||
@ -30,10 +32,13 @@ from vresutils.costdata import annuity
|
||||
override_component_attrs = pypsa.descriptors.Dict({k : v.copy() for k,v in pypsa.components.component_attrs.items()})
|
||||
override_component_attrs["Link"].loc["bus2"] = ["string",np.nan,np.nan,"2nd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus3"] = ["string",np.nan,np.nan,"3rd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus4"] = ["string",np.nan,np.nan,"4th bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency2"] = ["static or series","per unit",1.,"2nd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per unit",1.,"3rd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency4"] = ["static or series","per unit",1.,"4th bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p4"] = ["series","MW",0.,"4th bus output","Output"]
|
||||
|
||||
override_component_attrs["Link"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["lifetime"] = ["float","years",np.nan,"lifetime","Input (optional)"]
|
||||
@ -42,6 +47,90 @@ override_component_attrs["Generator"].loc["lifetime"] = ["float","years",np.nan,
|
||||
override_component_attrs["Store"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"]
|
||||
override_component_attrs["Store"].loc["lifetime"] = ["float","years",np.nan,"lifetime","Input (optional)"]
|
||||
|
||||
|
||||
|
||||
def co2_emissions_year(cts, opts, year):
|
||||
|
||||
"""
|
||||
calculate co2 emissions in one specific year (e.g. 1990 or 2018).
|
||||
"""
|
||||
eea_co2 = build_eea_co2(year)
|
||||
|
||||
#TODO: read Eurostat data from year>2014, this only affects the estimation of
|
||||
|
||||
# CO2 emissions for "BA","RS","AL","ME","MK"
|
||||
if year > 2014:
|
||||
eurostat_co2 = build_eurostat_co2(year=2014)
|
||||
else:
|
||||
eurostat_co2 = build_eurostat_co2(year)
|
||||
|
||||
co2_totals=build_co2_totals(eea_co2, eurostat_co2, year)
|
||||
|
||||
|
||||
co2_emissions = co2_totals.loc[cts, "electricity"].sum()
|
||||
|
||||
if "T" in opts:
|
||||
co2_emissions += co2_totals.loc[cts, [i+ " non-elec" for i in ["rail","road"]]].sum().sum()
|
||||
if "H" in opts:
|
||||
co2_emissions += co2_totals.loc[cts, [i+ " non-elec" for i in ["residential","services"]]].sum().sum()
|
||||
if "I" in opts:
|
||||
co2_emissions += co2_totals.loc[cts, ["industrial non-elec","industrial processes",
|
||||
"domestic aviation","international aviation",
|
||||
"domestic navigation","international navigation"]].sum().sum()
|
||||
co2_emissions *=0.001 #MtCO2 to GtCO2
|
||||
return co2_emissions
|
||||
|
||||
|
||||
|
||||
def build_carbon_budget(o):
|
||||
#distribute carbon budget following beta or exponential transition path
|
||||
if "be" in o:
|
||||
#beta decay
|
||||
carbon_budget = float(o[o.find("cb")+2:o.find("be")])
|
||||
be=float(o[o.find("be")+2:])
|
||||
if "ex" in o:
|
||||
#exponential decay
|
||||
carbon_budget = float(o[o.find("cb")+2:o.find("ex")])
|
||||
r=float(o[o.find("ex")+2:])
|
||||
|
||||
|
||||
pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0)
|
||||
pop_layout["ct"] = pop_layout.index.str[:2]
|
||||
cts = pop_layout.ct.value_counts().index
|
||||
|
||||
e_1990 = co2_emissions_year(cts, opts, year=1990)
|
||||
|
||||
#emissions at the beginning of the path (last year available 2018)
|
||||
e_0 = co2_emissions_year(cts, opts, year=2018)
|
||||
#emissions in 2019 and 2020 assumed equal to 2018 and substracted
|
||||
carbon_budget -= 2*e_0
|
||||
planning_horizons = snakemake.config['scenario']['planning_horizons']
|
||||
CO2_CAP = pd.DataFrame(index = pd.Series(data=planning_horizons,
|
||||
name='planning_horizon'),
|
||||
columns=pd.Series(data=[],
|
||||
name='paths',
|
||||
dtype='float'))
|
||||
t_0 = planning_horizons[0]
|
||||
if "be" in o:
|
||||
#beta decay
|
||||
t_f = t_0 + (2*carbon_budget/e_0).round(0) # final year in the path
|
||||
#emissions (relative to 1990)
|
||||
CO2_CAP[o] = [(e_0/e_1990)*(1-beta.cdf((t-t_0)/(t_f-t_0), be, be)) for t in planning_horizons]
|
||||
|
||||
if "ex" in o:
|
||||
#exponential decay without delay
|
||||
T=carbon_budget/e_0
|
||||
m=(1+np.sqrt(1+r*T))/T
|
||||
CO2_CAP[o] = [(e_0/e_1990)*(1+(m+r)*(t-t_0))*np.exp(-m*(t-t_0)) for t in planning_horizons]
|
||||
|
||||
|
||||
CO2_CAP.to_csv(path_cb + 'carbon_budget_distribution.csv', sep=',',
|
||||
line_terminator='\n', float_format='%.3f')
|
||||
countries=pd.Series(data=cts)
|
||||
countries.to_csv(path_cb + 'countries.csv', sep=',',
|
||||
line_terminator='\n', float_format='%.3f')
|
||||
|
||||
|
||||
def add_lifetime_wind_solar(n):
|
||||
"""
|
||||
Add lifetime for solar and wind generators
|
||||
@ -152,16 +241,15 @@ def remove_elec_base_techs(n):
|
||||
"""remove conventional generators (e.g. OCGT) and storage units (e.g. batteries and H2)
|
||||
from base electricity-only network, since they're added here differently using links
|
||||
"""
|
||||
to_keep = {"generators" : snakemake.config["plotting"]["vre_techs"],
|
||||
"storage_units" : snakemake.config["plotting"]["renewable_storage_techs"]}
|
||||
|
||||
n.carriers = n.carriers.loc[to_keep["generators"] + to_keep["storage_units"]]
|
||||
|
||||
for components, techs in iteritems(to_keep):
|
||||
df = getattr(n,components)
|
||||
to_remove = df.carrier.value_counts().index^techs
|
||||
print("removing {} with carrier {}".format(components,to_remove))
|
||||
df.drop(df.index[df.carrier.isin(to_remove)],inplace=True)
|
||||
for c in n.iterate_components(snakemake.config["pypsa_eur"]):
|
||||
to_keep = snakemake.config["pypsa_eur"][c.name]
|
||||
to_remove = pd.Index(c.df.carrier.unique())^to_keep
|
||||
print("Removing",c.list_name,"with carrier",to_remove)
|
||||
names = c.df.index[c.df.carrier.isin(to_remove)]
|
||||
print(names)
|
||||
n.mremove(c.name, names)
|
||||
n.carriers.drop(to_remove, inplace=True, errors="ignore")
|
||||
|
||||
|
||||
def remove_non_electric_buses(n):
|
||||
@ -199,12 +287,10 @@ def add_co2_tracking(n):
|
||||
location="EU",
|
||||
carrier="co2 stored")
|
||||
|
||||
#TODO move cost to data/costs.csv
|
||||
#TODO move maximum somewhere more transparent
|
||||
n.madd("Store",["co2 stored"],
|
||||
e_nom_extendable = True,
|
||||
e_nom_max=2e8,
|
||||
capital_cost=20.,
|
||||
e_nom_extendable=True,
|
||||
e_nom_max=options['co2_sequestration_potential']*1e6,
|
||||
capital_cost=options['co2_sequestration_cost'],
|
||||
carrier="co2 stored",
|
||||
bus="co2 stored")
|
||||
|
||||
@ -216,17 +302,26 @@ def add_co2_tracking(n):
|
||||
efficiency=1.,
|
||||
p_nom_extendable=True)
|
||||
|
||||
if options['dac']:
|
||||
#direct air capture consumes electricity to take CO2 from the air to the underground store
|
||||
#TODO do with cost from Breyer - later use elec and heat and capital cost
|
||||
n.madd("Link",["DAC"],
|
||||
bus0="co2 atmosphere",
|
||||
bus1="co2 stored",
|
||||
carrier="DAC",
|
||||
marginal_cost=75.,
|
||||
efficiency=1.,
|
||||
p_nom_extendable=True,
|
||||
lifetime=costs.at['DAC','lifetime'])
|
||||
def add_dac(n):
|
||||
|
||||
heat_buses = n.buses.index[n.buses.carrier.isin(["urban central heat",
|
||||
"services urban decentral heat"])]
|
||||
locations = n.buses.location[heat_buses]
|
||||
|
||||
n.madd("Link",
|
||||
locations,
|
||||
suffix=" DAC",
|
||||
bus0="co2 atmosphere",
|
||||
bus1="co2 stored",
|
||||
bus2=locations.values,
|
||||
bus3=heat_buses,
|
||||
carrier="DAC",
|
||||
capital_cost=costs.at['direct air capture','fixed'],
|
||||
efficiency=1.,
|
||||
efficiency2=-(costs.at['direct air capture','electricity-input'] + costs.at['direct air capture','compression-electricity-input']),
|
||||
efficiency3=-(costs.at['direct air capture','heat-input'] - costs.at['direct air capture','compression-heat-output']),
|
||||
p_nom_extendable=True,
|
||||
lifetime=costs.at['direct air capture','lifetime'])
|
||||
|
||||
|
||||
def add_co2limit(n, Nyears=1.,limit=0.):
|
||||
@ -596,7 +691,7 @@ def add_generation(network):
|
||||
capital_cost=0.) #could correct to e.g. 0.2 EUR/kWh * annuity and O&M
|
||||
|
||||
network.add("Generator",
|
||||
"EU fossil " + carrier,
|
||||
"EU " + carrier,
|
||||
bus="EU " + carrier,
|
||||
p_nom_extendable=True,
|
||||
carrier=carrier,
|
||||
@ -944,18 +1039,18 @@ def add_storage(network):
|
||||
|
||||
if options['SMR']:
|
||||
network.madd("Link",
|
||||
nodes + " SMR CCS",
|
||||
nodes + " SMR CC",
|
||||
bus0=["EU gas"]*len(nodes),
|
||||
bus1=nodes+" H2",
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
p_nom_extendable=True,
|
||||
carrier="SMR CCS",
|
||||
efficiency=costs.at["SMR CCS","efficiency"],
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"],
|
||||
capital_cost=costs.at["SMR CCS","fixed"],
|
||||
lifetime=costs.at['SMR CCS','lifetime'])
|
||||
carrier="SMR CC",
|
||||
efficiency=costs.at["SMR CC","efficiency"],
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-options["cc_fraction"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*options["cc_fraction"],
|
||||
capital_cost=costs.at["SMR CC","fixed"],
|
||||
lifetime=costs.at['SMR CC','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
nodes + " SMR",
|
||||
@ -976,14 +1071,14 @@ def add_land_transport(network):
|
||||
|
||||
fuel_cell_share = get_parameter(options["land_transport_fuel_cell_share"])
|
||||
electric_share = get_parameter(options["land_transport_electric_share"])
|
||||
fossil_share = 1 - fuel_cell_share - electric_share
|
||||
ice_share = 1 - fuel_cell_share - electric_share
|
||||
|
||||
print("shares of FCEV, EV and ICEV are",
|
||||
fuel_cell_share,
|
||||
electric_share,
|
||||
fossil_share)
|
||||
ice_share)
|
||||
|
||||
if fossil_share < 0:
|
||||
if ice_share < 0:
|
||||
print("Error, more FCEV and EV share than 1.")
|
||||
sys.exit()
|
||||
|
||||
@ -1061,14 +1156,14 @@ def add_land_transport(network):
|
||||
p_set=fuel_cell_share/options['transport_fuel_cell_efficiency']*transport[nodes])
|
||||
|
||||
|
||||
if fossil_share > 0:
|
||||
if ice_share > 0:
|
||||
|
||||
network.madd("Load",
|
||||
nodes,
|
||||
suffix=" land transport fossil",
|
||||
bus="Fischer-Tropsch",
|
||||
carrier="land transport fossil",
|
||||
p_set=fossil_share/options['transport_internal_combustion_efficiency']*transport[nodes])
|
||||
suffix=" land transport oil",
|
||||
bus="EU oil",
|
||||
carrier="land transport oil",
|
||||
p_set=ice_share/options['transport_internal_combustion_efficiency']*transport[nodes])
|
||||
|
||||
|
||||
|
||||
@ -1234,64 +1329,36 @@ def add_heat(network):
|
||||
if name == "urban central":
|
||||
#add gas CHP; biomass CHP is added in biomass section
|
||||
network.madd("Link",
|
||||
nodes[name] + " urban central gas CHP electric",
|
||||
nodes[name] + " urban central gas CHP",
|
||||
bus0="EU gas",
|
||||
bus1=nodes[name],
|
||||
bus2="co2 atmosphere",
|
||||
carrier="urban central gas CHP electric",
|
||||
bus2=nodes[name] + " urban central heat",
|
||||
bus3="co2 atmosphere",
|
||||
carrier="urban central gas CHP",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at['central gas CHP','fixed']*costs.at['central gas CHP','efficiency'],
|
||||
marginal_cost=costs.at['central gas CHP','VOM'],
|
||||
efficiency=costs.at['central gas CHP','efficiency'],
|
||||
efficiency2=costs.at['gas','CO2 intensity'],
|
||||
c_b=costs.at['central gas CHP','c_b'],
|
||||
c_v=costs.at['central gas CHP','c_v'],
|
||||
p_nom_ratio=costs.at['central gas CHP','p_nom_ratio'],
|
||||
efficiency2=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_b'],
|
||||
efficiency3=costs.at['gas','CO2 intensity'],
|
||||
lifetime=costs.at['central gas CHP','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
nodes[name] + " urban central gas CHP heat",
|
||||
bus0="EU gas",
|
||||
bus1=nodes[name] + " urban central heat",
|
||||
bus2="co2 atmosphere",
|
||||
carrier="urban central gas CHP heat",
|
||||
p_nom_extendable=True,
|
||||
marginal_cost=costs.at['central gas CHP','VOM'],
|
||||
efficiency=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_v'],
|
||||
efficiency2=costs.at['gas','CO2 intensity'],
|
||||
lifetime=costs.at['central gas CHP','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
nodes[name] + " urban central gas CHP CCS electric",
|
||||
nodes[name] + " urban central gas CHP CC",
|
||||
bus0="EU gas",
|
||||
bus1=nodes[name],
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="urban central gas CHP CCS electric",
|
||||
bus2=nodes[name] + " urban central heat",
|
||||
bus3="co2 atmosphere",
|
||||
bus4="co2 stored",
|
||||
carrier="urban central gas CHP CC",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at['central gas CHP CCS','fixed']*costs.at['central gas CHP CCS','efficiency'],
|
||||
marginal_cost=costs.at['central gas CHP CCS','VOM'],
|
||||
efficiency=costs.at['central gas CHP CCS','efficiency'],
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"],
|
||||
c_b=costs.at['central gas CHP CCS','c_b'],
|
||||
c_v=costs.at['central gas CHP CCS','c_v'],
|
||||
p_nom_ratio=costs.at['central gas CHP CCS','p_nom_ratio'],
|
||||
lifetime=costs.at['central gas CHP CCS','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
nodes[name] + " urban central gas CHP CCS heat",
|
||||
bus0="EU gas",
|
||||
bus1=nodes[name] + " urban central heat",
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="urban central gas CHP CCS heat",
|
||||
p_nom_extendable=True,
|
||||
marginal_cost=costs.at['central gas CHP CCS','VOM'],
|
||||
efficiency=costs.at['central gas CHP CCS','efficiency']/costs.at['central gas CHP CCS','c_v'],
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"],
|
||||
lifetime=costs.at['central gas CHP CCS','lifetime'])
|
||||
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'],
|
||||
marginal_cost=costs.at['central gas CHP','VOM'],
|
||||
efficiency=costs.at['central gas CHP','efficiency'] - costs.at['gas','CO2 intensity']*(costs.at['biomass CHP capture','electricity-input'] + costs.at['biomass CHP capture','compression-electricity-input']),
|
||||
efficiency2=costs.at['central gas CHP','efficiency']/costs.at['central gas CHP','c_b'] + costs.at['gas','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-input']),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*(1-costs.at['biomass CHP capture','capture_rate']),
|
||||
efficiency4=costs.at['gas','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
|
||||
lifetime=costs.at['central gas CHP','lifetime'])
|
||||
|
||||
else:
|
||||
if options["micro_chp"]:
|
||||
@ -1483,61 +1550,35 @@ def add_biomass(network):
|
||||
urban_central = urban_central.str[:-len(" urban central heat")]
|
||||
|
||||
network.madd("Link",
|
||||
urban_central + " urban central solid biomass CHP electric",
|
||||
urban_central + " urban central solid biomass CHP",
|
||||
bus0="EU solid biomass",
|
||||
bus1=urban_central,
|
||||
carrier="urban central solid biomass CHP electric",
|
||||
bus2=urban_central + " urban central heat",
|
||||
carrier="urban central solid biomass CHP",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at['central solid biomass CHP','fixed']*costs.at['central solid biomass CHP','efficiency'],
|
||||
marginal_cost=costs.at['central solid biomass CHP','VOM'],
|
||||
efficiency=costs.at['central solid biomass CHP','efficiency'],
|
||||
c_b=costs.at['central solid biomass CHP','c_b'],
|
||||
c_v=costs.at['central solid biomass CHP','c_v'],
|
||||
p_nom_ratio=costs.at['central solid biomass CHP','p_nom_ratio'],
|
||||
lifetime=costs.at['central solid biomass CHP','lifetime'])
|
||||
|
||||
|
||||
network.madd("Link",
|
||||
urban_central + " urban central solid biomass CHP heat",
|
||||
bus0="EU solid biomass",
|
||||
bus1=urban_central + " urban central heat",
|
||||
carrier="urban central solid biomass CHP heat",
|
||||
p_nom_extendable=True,
|
||||
marginal_cost=costs.at['central solid biomass CHP','VOM'],
|
||||
efficiency=costs.at['central solid biomass CHP','efficiency']/costs.at['central solid biomass CHP','c_v'],
|
||||
efficiency2=costs.at['central solid biomass CHP','efficiency-heat'],
|
||||
lifetime=costs.at['central solid biomass CHP','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
urban_central + " urban central solid biomass CHP CCS electric",
|
||||
urban_central + " urban central solid biomass CHP CC",
|
||||
bus0="EU solid biomass",
|
||||
bus1=urban_central,
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="urban central solid biomass CHP CCS electric",
|
||||
bus2=urban_central + " urban central heat",
|
||||
bus3="co2 atmosphere",
|
||||
bus4="co2 stored",
|
||||
carrier="urban central solid biomass CHP CC",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at['central solid biomass CHP CCS','fixed']*costs.at['central solid biomass CHP CCS','efficiency'],
|
||||
marginal_cost=costs.at['central solid biomass CHP CCS','VOM'],
|
||||
efficiency=costs.at['central solid biomass CHP CCS','efficiency'],
|
||||
efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
c_b=costs.at['central solid biomass CHP','c_b'],
|
||||
c_v=costs.at['central solid biomass CHP','c_v'],
|
||||
p_nom_ratio=costs.at['central solid biomass CHP','p_nom_ratio'],
|
||||
lifetime=costs.at['central solid biomass CHP CCS','lifetime'])
|
||||
capital_cost=costs.at['central solid biomass CHP','fixed']*costs.at['central solid biomass CHP','efficiency'] + costs.at['biomass CHP capture','fixed']*costs.at['solid biomass','CO2 intensity'],
|
||||
marginal_cost=costs.at['central solid biomass CHP','VOM'],
|
||||
efficiency=costs.at['central solid biomass CHP','efficiency'] - costs.at['solid biomass','CO2 intensity']*(costs.at['biomass CHP capture','electricity-input'] + costs.at['biomass CHP capture','compression-electricity-input']),
|
||||
efficiency2=costs.at['central solid biomass CHP','efficiency-heat'] + costs.at['solid biomass','CO2 intensity']*(costs.at['biomass CHP capture','heat-output'] + costs.at['biomass CHP capture','compression-heat-output'] - costs.at['biomass CHP capture','heat-input']),
|
||||
efficiency3=-costs.at['solid biomass','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
|
||||
efficiency4=costs.at['solid biomass','CO2 intensity']*costs.at['biomass CHP capture','capture_rate'],
|
||||
lifetime=costs.at['central solid biomass CHP','lifetime'])
|
||||
|
||||
network.madd("Link",
|
||||
urban_central + " urban central solid biomass CHP CCS heat",
|
||||
bus0="EU solid biomass",
|
||||
bus1=urban_central + " urban central heat",
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="urban central solid biomass CHP CCS heat",
|
||||
p_nom_extendable=True,
|
||||
marginal_cost=costs.at['central solid biomass CHP CCS','VOM'],
|
||||
efficiency=costs.at['central solid biomass CHP CCS','efficiency']/costs.at['central solid biomass CHP CCS','c_v'],
|
||||
efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
lifetime=costs.at['central solid biomass CHP CCS','lifetime'])
|
||||
|
||||
|
||||
def add_industry(network):
|
||||
@ -1573,18 +1614,18 @@ def add_industry(network):
|
||||
efficiency=1.)
|
||||
|
||||
network.madd("Link",
|
||||
["solid biomass for industry CCS"],
|
||||
["solid biomass for industry CC"],
|
||||
bus0="EU solid biomass",
|
||||
bus1="solid biomass for industry",
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="solid biomass for industry CCS",
|
||||
carrier="solid biomass for industry CC",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at["industry CCS","fixed"]*costs.at['solid biomass','CO2 intensity']*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h)
|
||||
capital_cost=costs.at["cement capture","fixed"]*costs.at['solid biomass','CO2 intensity'],
|
||||
efficiency=0.9,
|
||||
efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"],
|
||||
lifetime=costs.at['industry CCS','lifetime'])
|
||||
efficiency2=-costs.at['solid biomass','CO2 intensity']*costs.at["cement capture","capture_rate"],
|
||||
efficiency3=costs.at['solid biomass','CO2 intensity']*costs.at["cement capture","capture_rate"],
|
||||
lifetime=costs.at['cement capture','lifetime'])
|
||||
|
||||
|
||||
network.madd("Bus",
|
||||
@ -1609,18 +1650,18 @@ def add_industry(network):
|
||||
efficiency2=costs.at['gas','CO2 intensity'])
|
||||
|
||||
network.madd("Link",
|
||||
["gas for industry CCS"],
|
||||
["gas for industry CC"],
|
||||
bus0="EU gas",
|
||||
bus1="gas for industry",
|
||||
bus2="co2 atmosphere",
|
||||
bus3="co2 stored",
|
||||
carrier="gas for industry CCS",
|
||||
carrier="gas for industry CC",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at["industry CCS","fixed"]*costs.at['gas','CO2 intensity']*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h)
|
||||
capital_cost=costs.at["cement capture","fixed"]*costs.at['gas','CO2 intensity'],
|
||||
efficiency=0.9,
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-options["ccs_fraction"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']*options["ccs_fraction"],
|
||||
lifetime=costs.at['industry CCS','lifetime'])
|
||||
efficiency2=costs.at['gas','CO2 intensity']*(1-costs.at["cement capture","capture_rate"]),
|
||||
efficiency3=costs.at['gas','CO2 intensity']**costs.at["cement capture","capture_rate"],
|
||||
lifetime=costs.at['cement capture','lifetime'])
|
||||
|
||||
|
||||
network.madd("Load",
|
||||
@ -1638,27 +1679,30 @@ def add_industry(network):
|
||||
carrier="H2 for shipping",
|
||||
p_set = nodal_energy_totals.loc[nodes,["total international navigation","total domestic navigation"]].sum(axis=1)*1e6*options['shipping_average_efficiency']/costs.at["fuel cell","efficiency"]/8760.)
|
||||
|
||||
network.madd("Bus",
|
||||
["Fischer-Tropsch"],
|
||||
location="EU",
|
||||
carrier="Fischer-Tropsch")
|
||||
if "EU oil" not in network.buses.index:
|
||||
network.madd("Bus",
|
||||
["EU oil"],
|
||||
location="EU",
|
||||
carrier="oil")
|
||||
|
||||
#use madd to get carrier inserted
|
||||
network.madd("Store",
|
||||
["Fischer-Tropsch Store"],
|
||||
bus="Fischer-Tropsch",
|
||||
e_nom_extendable=True,
|
||||
e_cyclic=True,
|
||||
carrier="Fischer-Tropsch",
|
||||
capital_cost=0.) #could correct to e.g. 0.001 EUR/kWh * annuity and O&M
|
||||
if "EU oil Store" not in network.stores.index:
|
||||
network.madd("Store",
|
||||
["EU oil Store"],
|
||||
bus="EU oil",
|
||||
e_nom_extendable=True,
|
||||
e_cyclic=True,
|
||||
carrier="oil",
|
||||
capital_cost=0.) #could correct to e.g. 0.001 EUR/kWh * annuity and O&M
|
||||
|
||||
network.add("Generator",
|
||||
"fossil oil",
|
||||
bus="Fischer-Tropsch",
|
||||
p_nom_extendable=True,
|
||||
carrier="oil",
|
||||
capital_cost=0.,
|
||||
marginal_cost=costs.at["oil",'fuel'])
|
||||
if "EU oil" not in network.generators.index:
|
||||
network.add("Generator",
|
||||
"EU oil",
|
||||
bus="EU oil",
|
||||
p_nom_extendable=True,
|
||||
carrier="oil",
|
||||
capital_cost=0.,
|
||||
marginal_cost=costs.at["oil",'fuel'])
|
||||
|
||||
if options["oil_boilers"]:
|
||||
|
||||
@ -1668,7 +1712,7 @@ def add_industry(network):
|
||||
network.madd("Link",
|
||||
nodes_heat[name] + " " + name + " oil boiler",
|
||||
p_nom_extendable=True,
|
||||
bus0=["Fischer-Tropsch"] * len(nodes_heat[name]),
|
||||
bus0="EU oil",
|
||||
bus1=nodes_heat[name] + " " + name + " heat",
|
||||
bus2="co2 atmosphere",
|
||||
carrier=name + " oil boiler",
|
||||
@ -1681,7 +1725,7 @@ def add_industry(network):
|
||||
network.madd("Link",
|
||||
nodes + " Fischer-Tropsch",
|
||||
bus0=nodes + " H2",
|
||||
bus1="Fischer-Tropsch",
|
||||
bus1="EU oil",
|
||||
bus2="co2 stored",
|
||||
carrier="Fischer-Tropsch",
|
||||
efficiency=costs.at["Fischer-Tropsch",'efficiency'],
|
||||
@ -1692,13 +1736,13 @@ def add_industry(network):
|
||||
|
||||
network.madd("Load",
|
||||
["naphtha for industry"],
|
||||
bus="Fischer-Tropsch",
|
||||
bus="EU oil",
|
||||
carrier="naphtha for industry",
|
||||
p_set = industrial_demand.loc[nodes,"naphtha"].sum()/8760.)
|
||||
|
||||
network.madd("Load",
|
||||
["kerosene for aviation"],
|
||||
bus="Fischer-Tropsch",
|
||||
bus="EU oil",
|
||||
carrier="kerosene for aviation",
|
||||
p_set = nodal_energy_totals.loc[nodes,["total international aviation","total domestic aviation"]].sum(axis=1).sum()*1e6/8760.)
|
||||
|
||||
@ -1708,9 +1752,9 @@ def add_industry(network):
|
||||
co2 = network.loads.loc[["naphtha for industry","kerosene for aviation"],"p_set"].sum()*costs.at["oil",'CO2 intensity'] - industrial_demand.loc[nodes,"process emission from feedstock"].sum()/8760.
|
||||
|
||||
network.madd("Load",
|
||||
["Fischer-Tropsch emissions"],
|
||||
["oil emissions"],
|
||||
bus="co2 atmosphere",
|
||||
carrier="Fischer-Tropsch emissions",
|
||||
carrier="oil emissions",
|
||||
p_set=-co2)
|
||||
|
||||
network.madd("Load",
|
||||
@ -1754,18 +1798,18 @@ def add_industry(network):
|
||||
p_nom_extendable=True,
|
||||
efficiency=1.)
|
||||
|
||||
#assume enough local waste heat for CCS
|
||||
#assume enough local waste heat for CC
|
||||
network.madd("Link",
|
||||
["process emissions CCS"],
|
||||
["process emissions CC"],
|
||||
bus0="process emissions",
|
||||
bus1="co2 atmosphere",
|
||||
bus2="co2 stored",
|
||||
carrier="process emissions CCS",
|
||||
carrier="process emissions CC",
|
||||
p_nom_extendable=True,
|
||||
capital_cost=costs.at["industry CCS","fixed"]*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h)
|
||||
efficiency=(1-options["ccs_fraction"]),
|
||||
efficiency2=options["ccs_fraction"],
|
||||
lifetime=costs.at['industry CCS','lifetime'])
|
||||
capital_cost=costs.at["cement capture","fixed"],
|
||||
efficiency=(1-costs.at["cement capture","capture_rate"]),
|
||||
efficiency2=costs.at["cement capture","capture_rate"],
|
||||
lifetime=costs.at['cement capture','lifetime'])
|
||||
|
||||
|
||||
|
||||
@ -1786,13 +1830,6 @@ def add_waste_heat(network):
|
||||
network.links.loc[urban_central + " H2 Fuel Cell","bus2"] = urban_central + " urban central heat"
|
||||
network.links.loc[urban_central + " H2 Fuel Cell","efficiency2"] = 0.95 - network.links.loc[urban_central + " H2 Fuel Cell","efficiency"]
|
||||
|
||||
|
||||
def restrict_technology_potential(n,tech,limit):
|
||||
print("restricting potentials (p_nom_max) for {} to {} of technical potential".format(tech,limit))
|
||||
gens = n.generators.index[n.generators.carrier.str.contains(tech)]
|
||||
#beware if limit is 0 and p_nom_max is np.inf, 0*np.inf is nan
|
||||
n.generators.loc[gens,"p_nom_max"] *=limit
|
||||
|
||||
def decentral(n):
|
||||
n.lines.drop(n.lines.index,inplace=True)
|
||||
n.links.drop(n.links.index[n.links.carrier.isin(["DC","B2B"])],inplace=True)
|
||||
@ -1828,15 +1865,16 @@ def get_parameter(item):
|
||||
return item
|
||||
|
||||
|
||||
#%%
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Detect running outside of snakemake and mock snakemake for testing
|
||||
if 'snakemake' not in globals():
|
||||
from vresutils.snakemake import MockSnakemake
|
||||
snakemake = MockSnakemake(
|
||||
wildcards=dict(network='elec', simpl='', clusters='37', lv='1.0',
|
||||
opts='', planning_horizons='2030', co2_budget_name="go",
|
||||
sector_opts='Co2L0-120H-T-H-B-I-solar3-dist1'),
|
||||
opts='', planning_horizons='2020',
|
||||
sector_opts='120H-T-H-B-I-onwind+p3-dist1-cb48be3'),
|
||||
|
||||
input=dict( network='../pypsa-eur/networks/{network}_s{simpl}_{clusters}_ec_lv{lv}_{opts}.nc',
|
||||
energy_totals_name='resources/energy_totals.csv',
|
||||
co2_totals_name='resources/co2_totals.csv',
|
||||
@ -1872,10 +1910,11 @@ if __name__ == "__main__":
|
||||
solar_thermal_total="resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc",
|
||||
solar_thermal_urban="resources/solar_thermal_urban_{network}_s{simpl}_{clusters}.nc",
|
||||
solar_thermal_rural="resources/solar_thermal_rural_{network}_s{simpl}_{clusters}.nc",
|
||||
retro_cost_energy = "resources/retro_cost_{network}_s{simpl}_{clusters}.csv",
|
||||
retro_cost_energy = "resources/retro_cost_{network}_s{simpl}_{clusters}.csv",
|
||||
floor_area = "resources/floor_area_{network}_s{simpl}_{clusters}.csv"
|
||||
),
|
||||
output=['pypsa-eur-sec/results/test/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_{planning_horizons}.nc']
|
||||
output=['results/version-cb48be3/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{planning_horizons}.nc']
|
||||
|
||||
)
|
||||
import yaml
|
||||
with open('config.yaml', encoding='utf8') as f:
|
||||
@ -1960,6 +1999,9 @@ if __name__ == "__main__":
|
||||
if "I" in opts and "H" in opts:
|
||||
add_waste_heat(n)
|
||||
|
||||
if options['dac']:
|
||||
add_dac(n)
|
||||
|
||||
if "decentral" in opts:
|
||||
decentral(n)
|
||||
|
||||
@ -1978,6 +2020,22 @@ if __name__ == "__main__":
|
||||
limit = get_parameter(snakemake.config["co2_budget"])
|
||||
print("CO2 limit set to",limit)
|
||||
|
||||
for o in opts:
|
||||
|
||||
if "cb" in o:
|
||||
path_cb = snakemake.config['results_dir'] + snakemake.config['run'] + '/csvs/'
|
||||
if not os.path.exists(path_cb):
|
||||
os.makedirs(path_cb)
|
||||
try:
|
||||
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv', index_col=0)
|
||||
except:
|
||||
build_carbon_budget(o)
|
||||
CO2_CAP=pd.read_csv(path_cb + 'carbon_budget_distribution.csv', index_col=0)
|
||||
|
||||
limit=CO2_CAP.loc[investment_year]
|
||||
print("overriding CO2 limit with scenario limit",limit)
|
||||
|
||||
|
||||
for o in opts:
|
||||
if "Co2L" in o:
|
||||
limit = o[o.find("Co2L")+4:]
|
||||
@ -1987,14 +2045,7 @@ if __name__ == "__main__":
|
||||
print("adding CO2 budget limit as per unit of 1990 levels of",limit)
|
||||
add_co2limit(n, Nyears, limit)
|
||||
|
||||
|
||||
for o in opts:
|
||||
for tech in ["solar","onwind","offwind"]:
|
||||
if tech in o:
|
||||
limit = o[o.find(tech)+len(tech):]
|
||||
limit = float(limit.replace("p",".").replace("m","-"))
|
||||
print("changing potential for",tech,"by factor",limit)
|
||||
restrict_technology_potential(n,tech,limit)
|
||||
|
||||
if o[:10] == 'linemaxext':
|
||||
maxext = float(o[10:])*1e3
|
||||
@ -2006,6 +2057,31 @@ if __name__ == "__main__":
|
||||
|
||||
if snakemake.config["sector"]['electricity_distribution_grid']:
|
||||
insert_electricity_distribution_grid(n)
|
||||
for o in opts:
|
||||
if "+" in o:
|
||||
oo = o.split("+")
|
||||
carrier_list=np.hstack((n.generators.carrier.unique(), n.links.carrier.unique(),
|
||||
n.stores.carrier.unique(), n.storage_units.carrier.unique()))
|
||||
suptechs = map(lambda c: c.split("-", 2)[0], carrier_list)
|
||||
if oo[0].startswith(tuple(suptechs)):
|
||||
carrier = oo[0]
|
||||
attr_lookup = {"p": "p_nom_max", "c": "capital_cost"}
|
||||
attr = attr_lookup[oo[1][0]]
|
||||
factor = float(oo[1][1:])
|
||||
#beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan
|
||||
if carrier == "AC": # lines do not have carrier
|
||||
n.lines[attr] *= factor
|
||||
else:
|
||||
comps = {"Generator", "Link", "StorageUnit"} if attr=='p_nom_max' else {"Generator", "Link", "StorageUnit", "Store"}
|
||||
for c in n.iterate_components(comps):
|
||||
if carrier=='solar':
|
||||
sel = c.df.carrier.str.contains(carrier) & ~c.df.carrier.str.contains("solar rooftop")
|
||||
else:
|
||||
sel = c.df.carrier.str.contains(carrier)
|
||||
c.df.loc[sel,attr] *= factor
|
||||
print("changing", attr ,"for",carrier,"by factor",factor)
|
||||
|
||||
|
||||
if snakemake.config["sector"]['gas_distribution_grid']:
|
||||
insert_gas_distribution_costs(n)
|
||||
if snakemake.config["sector"]['electricity_grid_connection']:
|
||||
|
@ -28,10 +28,13 @@ from vresutils.benchmark import memory_logger
|
||||
override_component_attrs = pypsa.descriptors.Dict({k : v.copy() for k,v in pypsa.components.component_attrs.items()})
|
||||
override_component_attrs["Link"].loc["bus2"] = ["string",np.nan,np.nan,"2nd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus3"] = ["string",np.nan,np.nan,"3rd bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["bus4"] = ["string",np.nan,np.nan,"4th bus","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency2"] = ["static or series","per unit",1.,"2nd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per unit",1.,"3rd bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["efficiency4"] = ["static or series","per unit",1.,"4th bus efficiency","Input (optional)"]
|
||||
override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"]
|
||||
override_component_attrs["Link"].loc["p4"] = ["series","MW",0.,"4th bus output","Output"]
|
||||
|
||||
|
||||
|
||||
@ -269,6 +272,7 @@ def solve_network(n, config=None, solver_log=None, opts=None):
|
||||
solver_name=solver_name,
|
||||
solver_logfile=solver_log,
|
||||
solver_options=solver_options,
|
||||
solver_dir=tmpdir,
|
||||
extra_functionality=extra_functionality,
|
||||
formulation=solve_opts['formulation'])
|
||||
#extra_postprocessing=extra_postprocessing
|
||||
|
Loading…
Reference in New Issue
Block a user