battery+H2: option for stores and links instead of storage_units (#97)

* add_electricity: stores and links instead of storageunits

* move code for attaching in add_extra_stores.py

* Update Snakefile

Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de>

* Update Snakefile

Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de>

* Update Snakefile

Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de>

* rename to add_extra_components.py

* incorporate suggestions

* Snakefile: update script name

* extra_components: import _add_missing_carriers_from_costs

* Add '_ec" infix in Snakefile

* Snakefile add infix at missing spot

* remove unintendetely inserted data.bundle, correct import in add_extra_components

* environment: fix pyyaml version

* set powerplantmatching as conda-dependency

* environment fix minimal ppm version

* environment reinsert pyyaml

* environment: carry over changes to environment.docs.yaml

* extra_components: add carriers

* update docstring

* Update scripts/add_extra_components.py

* Update scripts/add_extra_components.py

* tutorial: fix hyperlink [skip travis]

* update release notes

* update documentation

* add_electricity: merge removal of suptech
This commit is contained in:
Fabian Neumann 2019-11-19 19:36:28 +01:00 committed by GitHub
parent 6cde3e902b
commit 81d9d2ab4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 282 additions and 126 deletions

View File

@ -16,12 +16,12 @@ rule cluster_all_elec_networks:
rule prepare_all_elec_networks: rule prepare_all_elec_networks:
input: input:
expand("networks/elec_s{simpl}_{clusters}_l{ll}_{opts}.nc", expand("networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc",
**config['scenario']) **config['scenario'])
rule solve_all_elec_networks: rule solve_all_elec_networks:
input: input:
expand("results/networks/elec_s{simpl}_{clusters}_l{ll}_{opts}.nc", expand("results/networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc",
**config['scenario']) **config['scenario'])
if config['enable'].get('prepare_links_p_nom', False): if config['enable'].get('prepare_links_p_nom', False):
@ -221,6 +221,19 @@ rule cluster_network:
# group: 'build_pypsa_networks' # group: 'build_pypsa_networks'
script: "scripts/cluster_network.py" script: "scripts/cluster_network.py"
rule add_extra_components:
input:
network='networks/{network}_s{simpl}_{clusters}.nc',
tech_costs=COSTS,
output: 'networks/{network}_s{simpl}_{clusters}_ec.nc'
benchmark: "benchmarks/add_extra_components/{network}_s{simpl}_{clusters}_ec"
threads: 1
resources: mem=3000
# group: 'build_pypsa_networks'
script: "scripts/add_extra_components.py"
# rule add_sectors: # rule add_sectors:
# input: # input:
# network="networks/elec_{cost}_{resarea}_{opts}.nc", # network="networks/elec_{cost}_{resarea}_{opts}.nc",
@ -232,11 +245,11 @@ rule cluster_network:
# script: "scripts/add_sectors.py" # script: "scripts/add_sectors.py"
rule prepare_network: rule prepare_network:
input: 'networks/{network}_s{simpl}_{clusters}.nc', tech_costs=COSTS input: 'networks/{network}_s{simpl}_{clusters}_ec.nc', tech_costs=COSTS
output: 'networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc' output: 'networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc'
threads: 1 threads: 1
resources: mem=1000 resources: mem=1000
# benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}" # benchmark: "benchmarks/prepare_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}"
script: "scripts/prepare_network.py" script: "scripts/prepare_network.py"
def memory(w): def memory(w):
@ -253,24 +266,24 @@ def memory(w):
# return 4890+310 * int(w.clusters) # return 4890+310 * int(w.clusters)
rule solve_network: rule solve_network:
input: "networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc" input: "networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
output: "results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc" output: "results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
shadow: "shallow" shadow: "shallow"
log: log:
solver="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_solver.log", solver="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_solver.log",
python="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_python.log", python="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_python.log",
memory="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_memory.log" memory="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_memory.log"
benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}" benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}"
threads: 4 threads: 4
resources: mem=memory resources: mem=memory
# group: "solve" # with group, threads is ignored https://bitbucket.org/snakemake/snakemake/issues/971/group-job-description-does-not-contain # group: "solve" # with group, threads is ignored https://bitbucket.org/snakemake/snakemake/issues/971/group-job-description-does-not-contain
script: "scripts/solve_network.py" script: "scripts/solve_network.py"
rule trace_solve_network: rule trace_solve_network:
input: "networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc" input: "networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
output: "results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}_trace.nc" output: "results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_trace.nc"
shadow: "shallow" shadow: "shallow"
log: python="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_python_trace.log", log: python="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_python_trace.log",
threads: 4 threads: 4
resources: mem=memory resources: mem=memory
script: "scripts/trace_solve_network.py" script: "scripts/trace_solve_network.py"
@ -278,14 +291,14 @@ rule trace_solve_network:
rule solve_operations_network: rule solve_operations_network:
input: input:
unprepared="networks/{network}_s{simpl}_{clusters}.nc", unprepared="networks/{network}_s{simpl}_{clusters}.nc",
optimized="results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc" optimized="results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
output: "results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op.nc" output: "results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_op.nc"
shadow: "shallow" shadow: "shallow"
log: log:
solver="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_solver.log", solver="logs/solve_operations_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_op_solver.log",
python="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_python.log", python="logs/solve_operations_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_op_python.log",
memory="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_memory.log" memory="logs/solve_operations_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_op_memory.log"
benchmark: "benchmarks/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}" benchmark: "benchmarks/solve_operations_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}"
threads: 4 threads: 4
resources: mem=(lambda w: 5000 + 372 * int(w.clusters)) resources: mem=(lambda w: 5000 + 372 * int(w.clusters))
# group: "solve_operations" # group: "solve_operations"
@ -293,11 +306,11 @@ rule solve_operations_network:
rule plot_network: rule plot_network:
input: input:
network="results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc", network="results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc",
tech_costs=COSTS tech_costs=COSTS
output: output:
only_map="results/plots/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{attr}.{ext}", only_map="results/plots/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{attr}.{ext}",
ext="results/plots/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{attr}_ext.{ext}" ext="results/plots/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{attr}_ext.{ext}"
script: "scripts/plot_network.py" script: "scripts/plot_network.py"
def input_make_summary(w): def input_make_summary(w):
@ -309,7 +322,7 @@ def input_make_summary(w):
else: else:
ll = w.ll ll = w.ll
return ([COSTS] + return ([COSTS] +
expand("results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc", expand("results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc",
network=w.network, network=w.network,
ll=ll, ll=ll,
**{k: config["scenario"][k] if getattr(w, k) == "all" else getattr(w, k) **{k: config["scenario"][k] if getattr(w, k) == "all" else getattr(w, k)
@ -317,12 +330,12 @@ def input_make_summary(w):
rule make_summary: rule make_summary:
input: input_make_summary input: input_make_summary
output: directory("results/summaries/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{country}") output: directory("results/summaries/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{country}")
script: "scripts/make_summary.py" script: "scripts/make_summary.py"
rule plot_summary: rule plot_summary:
input: "results/summaries/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{country}" input: "results/summaries/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{country}"
output: "results/plots/summary_{summary}_{network}_s{simpl}_{clusters}_l{ll}_{opts}_{country}.{ext}" output: "results/plots/summary_{summary}_{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{country}.{ext}"
script: "scripts/plot_summary.py" script: "scripts/plot_summary.py"
def input_plot_p_nom_max(wildcards): def input_plot_p_nom_max(wildcards):

View File

@ -33,6 +33,7 @@ electricity:
extendable_carriers: extendable_carriers:
Generator: [OCGT] Generator: [OCGT]
StorageUnit: [battery, H2] StorageUnit: [battery, H2]
Store: [] # battery, H2
max_hours: max_hours:
battery: 6 battery: 6

View File

@ -31,6 +31,7 @@ electricity:
extendable_carriers: extendable_carriers:
Generator: [OCGT] Generator: [OCGT]
StorageUnit: [battery, H2] StorageUnit: [battery, H2]
Store: [] #battery, H2
max_hours: max_hours:
battery: 6 battery: 6

View File

@ -4,8 +4,9 @@ co2limit,:math:`t_{CO_2-eq}/a`,float,"Cap on total annual system carbon dioxide
co2base,:math:`t_{CO_2-eq}/a`,float,"Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard." co2base,:math:`t_{CO_2-eq}/a`,float,"Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard."
agg_p_nom_limits,--,file path,"Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``." agg_p_nom_limits,--,file path,"Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``."
extendable_carriers,,, extendable_carriers,,,
-- Generator,--,"Any subset of {'OCGT','CCGT', 'nuclear'}","Places extendable conventional power plants (OCGT, CCGT and/or nuclear) where such power plants are located today without capacity limits." -- Generator,--,"Any subset of {'OCGT','CCGT'}","Places extendable conventional power plants (OCGT and/or CCGT) where gas power plants are located today without capacity limits."
-- StorageUnit,--,"Any subset of {'battery','H2'}","Places extendable storage units (battery and/or hydrogen) at every node/bus without capacity limits." -- StorageUnit,--,"Any subset of {'battery','H2'}","Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity."
-- Store,--,"Any subset of {'battery','H2'}","Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity."
max_hours,,, max_hours,,,
-- battery,h,float,"Maximum state of charge capacity of the battery in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_." -- battery,h,float,"Maximum state of charge capacity of the battery in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_."
-- H2,h,float,"Maximum state of charge capacity of the hydrogen storage in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_." -- H2,h,float,"Maximum state of charge capacity of the hydrogen storage in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_."

1 Unit Values Description
4 co2base :math:`t_{CO_2-eq}/a` float Reference value of total annual system carbon dioxide emissions if relative emission reduction target is specified in ``{opts}`` wildcard.
5 agg_p_nom_limits -- file path Reference to ``.csv`` file specifying per carrier generator nominal capacity constraints for individual countries if ``'CCL'`` is in ``{opts}`` wildcard. Defaults to ``data/agg_p_nom_minmax.csv``.
6 extendable_carriers
7 -- Generator -- Any subset of {'OCGT','CCGT', 'nuclear'} Any subset of {'OCGT','CCGT'} Places extendable conventional power plants (OCGT, CCGT and/or nuclear) where such power plants are located today without capacity limits. Places extendable conventional power plants (OCGT and/or CCGT) where gas power plants are located today without capacity limits.
8 -- StorageUnit -- Any subset of {'battery','H2'} Places extendable storage units (battery and/or hydrogen) at every node/bus without capacity limits. Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity.
9 -- Store -- Any subset of {'battery','H2'} Adds extendable storage units (battery and/or hydrogen) at every node/bus after clustering without capacity limits and with zero initial capacity.
10 max_hours
11 -- battery h float Maximum state of charge capacity of the battery in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.
12 -- H2 h float Maximum state of charge capacity of the hydrogen storage in terms of hours at full output capacity ``p_nom``. Cf. `PyPSA documentation <https://pypsa.readthedocs.io/en/latest/components.html#storage-unit>`_.

View File

@ -11,10 +11,18 @@ This is the first release of PyPSA-Eur:
* The ``conda`` environment files were updated and extended (`#81 <https://github.com/PyPSA/pypsa-eur/pull/81>`_). * The ``conda`` environment files were updated and extended (`#81 <https://github.com/PyPSA/pypsa-eur/pull/81>`_).
* The power plant database was updated (`#84 <https://github.com/PyPSA/pypsa-eur/pull/84>`_). * The power plant database was updated with extensive filtering options via ``pandas.query`` functionality (`#84 <https://github.com/PyPSA/pypsa-eur/pull/84>`_ and `#94 <https://github.com/PyPSA/pypsa-eur/pull/94>`_).
* Continuous integration testing with `Travis CI <https://travis-ci.org>`_ is now included (`#82 <https://github.com/PyPSA/pypsa-eur/pull/82>`_). * Continuous integration testing with `Travis CI <https://travis-ci.org>`_ is now included for Linux, Mac and Windows (`#82 <https://github.com/PyPSA/pypsa-eur/pull/82>`_).
* Data dependencies were moved to `zenodo <https://zenodo.org/>`_ (`#60 <https://github.com/PyPSA/pypsa-eur/issues/60>`_). * Data dependencies were moved to `zenodo <https://zenodo.org/>`_ and are now versioned (`#60 <https://github.com/PyPSA/pypsa-eur/issues/60>`_).
* Data dependencies are now retrieved from withing the snakemake workflow (`#86 <https://github.com/PyPSA/pypsa-eur/issues/60>`_). * Data dependencies are now retrieved directly from within the snakemake workflow (`#86 <https://github.com/PyPSA/pypsa-eur/pull/86>`_).
* Emission prices can be added to marginal costs of generators through the keyworks ``Ep`` in the ``{opts}`` wildcard (`#100 <https://github.com/PyPSA/pypsa-eur/pull/100>`_).
* An option is introduced to add extendable nuclear power plants to the network (`#98 <https://github.com/PyPSA/pypsa-eur/pull/98>`_).
* Focus weights can now be specified for particular countries for the network clustering, which allows to set a proportion of the total number of clusters for particular countries (`#87 <https://github.com/PyPSA/pypsa-eur/pull/87>`_).
* A new rule :mod:`add_extra_components` allows to add additional components to the network only after clustering. It is thereby possible to model storage units (e.g. battery and hydrogen) in more detail via a combination of ``Store``, ``Link`` and ``Bus`` elements (`#97 <https://github.com/PyPSA/pypsa-eur/pull/97>`_).

View File

@ -13,9 +13,12 @@ The simplification and clustering steps are described in detail in the paper
- Jonas Hörsch and Tom Brown. `The role of spatial scale in joint optimisations of generation and transmission for European highly renewable scenarios <https://arxiv.org/abs/1705.07617>`_), *14th International Conference on the European Energy Market*, 2017. `arXiv:1705.07617 <https://arxiv.org/abs/1705.07617>`_, `doi:10.1109/EEM.2017.7982024 <https://doi.org/10.1109/EEM.2017.7982024>`_. - Jonas Hörsch and Tom Brown. `The role of spatial scale in joint optimisations of generation and transmission for European highly renewable scenarios <https://arxiv.org/abs/1705.07617>`_), *14th International Conference on the European Energy Market*, 2017. `arXiv:1705.07617 <https://arxiv.org/abs/1705.07617>`_, `doi:10.1109/EEM.2017.7982024 <https://doi.org/10.1109/EEM.2017.7982024>`_.
After simplification and clustering of the network, additional components may be appended in the rule :mod:`add_extra_components` and the network is prepared for solving in :mod:`prepare_network`.
.. toctree:: .. toctree::
:caption: Overview :caption: Overview
simplification/simplify_network simplification/simplify_network
simplification/cluster_network simplification/cluster_network
simplification/add_extra_components
simplification/prepare_network simplification/prepare_network

View File

@ -0,0 +1,37 @@
.. _extra_components:
Rule ``add_extra_components``
=============================
.. graphviz::
:align: center
digraph snakemake_dag {
graph [bgcolor=white,
margin=0,
size="8,5"
];
node [fontname=sans,
fontsize=10,
penwidth=2,
shape=box,
style=rounded
];
edge [color=grey,
penwidth=2
];
1 [color="0.56 0.6 0.85",
label=prepare_network];
2 [color="0.47 0.6 0.85",
fillcolor=gray,
label=add_extra_components,
style=filled];
2 -> 1;
3 [color="0.03 0.6 0.85",
label=cluster_network];
3 -> 2;
}
|
.. automodule:: add_extra_components

View File

@ -119,7 +119,7 @@ orders ``snakemake`` to run the script ``solve_network`` that produces the solve
.. warning:: .. warning::
On Windows the previous command may currently cause a ``MissingRuleException`` due to problems with output files in subfolders. On Windows the previous command may currently cause a ``MissingRuleException`` due to problems with output files in subfolders.
This is an `open issue < https://github.com/snakemake/snakemake/issues/46>`_ at `snakemake <https://snakemake.readthedocs.io/>`_. This is an `open issue <https://github.com/snakemake/snakemake/issues/46>`_ at `snakemake <https://snakemake.readthedocs.io/>`_.
Windows users should add the option ``--keep-target-files`` to the command or instead run ``snakemake solve_all_elec_networks``. Windows users should add the option ``--keep-target-files`` to the command or instead run ``snakemake solve_all_elec_networks``.
This triggers a workflow of multiple preceding jobs that depend on each rule's inputs and outputs: This triggers a workflow of multiple preceding jobs that depend on each rule's inputs and outputs:

View File

@ -17,6 +17,7 @@ dependencies:
- memory_profiler - memory_profiler
- yaml - yaml
- pytables - pytables
- powerplantmatching>=0.4.2
# Second order dependencies which should really be deps of atlite # Second order dependencies which should really be deps of atlite
- xarray - xarray
@ -25,6 +26,7 @@ dependencies:
#- toolz #- toolz
#- dask #- dask
- progressbar2 - progressbar2
- pyyaml>=5.1.0
# Include ipython so that one does not inadvertently drop out of the conda # Include ipython so that one does not inadvertently drop out of the conda
# environment by calling ipython # environment by calling ipython
@ -46,6 +48,5 @@ dependencies:
- git+https://github.com/PyPSA/glaes.git#egg=glaes - git+https://github.com/PyPSA/glaes.git#egg=glaes
- git+https://github.com/PyPSA/geokit.git#egg=geokit - git+https://github.com/PyPSA/geokit.git#egg=geokit
- cdsapi - cdsapi
- powerplantmatching
- sphinx - sphinx
- sphinx_rtd_theme - sphinx_rtd_theme

View File

@ -18,6 +18,7 @@ dependencies:
- memory_profiler - memory_profiler
- yaml - yaml
- pytables - pytables
- powerplantmatching>=0.4.2
# Second order dependencies which should really be deps of atlite # Second order dependencies which should really be deps of atlite
- xarray - xarray
@ -26,6 +27,7 @@ dependencies:
- toolz - toolz
- dask - dask
- progressbar2 - progressbar2
- pyyaml>=5.1.0
# Include ipython so that one does not inadvertently drop out of the conda # Include ipython so that one does not inadvertently drop out of the conda
# environment by calling ipython # environment by calling ipython
@ -46,4 +48,3 @@ dependencies:
- git+https://github.com/PyPSA/glaes.git#egg=glaes - git+https://github.com/PyPSA/glaes.git#egg=glaes
- git+https://github.com/PyPSA/geokit.git#egg=geokit - git+https://github.com/PyPSA/geokit.git#egg=geokit
- cdsapi - cdsapi
- powerplantmatching>=0.4.2

View File

@ -1,6 +1,6 @@
# coding: utf-8 # coding: utf-8
""" """
Adds electrical generators and storage units to a base network. Adds electrical generators and existing hydro storage units to a base network.
Relevant Settings Relevant Settings
----------------- -----------------
@ -21,7 +21,6 @@ Relevant Settings
co2limit: co2limit:
extendable_carriers: extendable_carriers:
Generator: Generator:
StorageUnit:
estimate_renewable_capacities_from_capacity_stats: estimate_renewable_capacities_from_capacity_stats:
load: load:
@ -81,10 +80,9 @@ The rule :mod:`add_electricity` ties all the different data inputs from the prec
- today's thermal and hydro power generation capacities (for the technologies listed in the config setting ``electricity: conventional_carriers``), and - today's thermal and hydro power generation capacities (for the technologies listed in the config setting ``electricity: conventional_carriers``), and
- today's load time-series (upsampled in a top-down approach according to population and gross domestic product) - today's load time-series (upsampled in a top-down approach according to population and gross domestic product)
It further adds extendable ``generators`` and ``storage_units`` with **zero** capacity for It further adds extendable ``generators`` with **zero** capacity for
- photovoltaic, onshore and AC- as well as DC-connected offshore wind installations with today's locational, hourly wind and solar capacity factors (but **no** current capacities), - photovoltaic, onshore and AC- as well as DC-connected offshore wind installations with today's locational, hourly wind and solar capacity factors (but **no** current capacities),
- long-term hydrogen and short-term battery storage units (if listed in the config setting ``electricity: extendable_carriers``), and
- additional open- and combined-cycle gas turbines (if ``OCGT`` and/or ``CCGT`` is listed in the config setting ``electricity: extendable_carriers``) - additional open- and combined-cycle gas turbines (if ``OCGT`` and/or ``CCGT`` is listed in the config setting ``electricity: extendable_carriers``)
""" """
@ -265,7 +263,8 @@ def update_transmission_costs(n, costs, length_factor=1.0, simple_hvdc_costs=Fal
costs.at['HVDC submarine', 'capital_cost']) + costs.at['HVDC submarine', 'capital_cost']) +
costs.at['HVDC inverter pair', 'capital_cost']) costs.at['HVDC inverter pair', 'capital_cost'])
n.links.loc[dc_b, 'capital_cost'] = costs n.links.loc[dc_b, 'capital_cost'] = costs
# ### Generators
### Generators
def attach_wind_and_solar(n, costs): def attach_wind_and_solar(n, costs):
for tech in snakemake.config['renewable']: for tech in snakemake.config['renewable']:
@ -307,8 +306,6 @@ def attach_wind_and_solar(n, costs):
p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas()) p_max_pu=ds['profile'].transpose('time', 'bus').to_pandas())
# # Generators
def attach_conventional_generators(n, costs, ppl): def attach_conventional_generators(n, costs, ppl):
carriers = snakemake.config['electricity']['conventional_carriers'] carriers = snakemake.config['electricity']['conventional_carriers']
@ -329,6 +326,7 @@ def attach_conventional_generators(n, costs, ppl):
def attach_hydro(n, costs, ppl): def attach_hydro(n, costs, ppl):
if 'hydro' not in snakemake.config['renewable']: return
c = snakemake.config['renewable']['hydro'] c = snakemake.config['renewable']['hydro']
carriers = c.get('carriers', ['ror', 'PHS', 'hydro']) carriers = c.get('carriers', ['ror', 'PHS', 'hydro'])
@ -431,13 +429,10 @@ def attach_hydro(n, costs, ppl):
def attach_extendable_generators(n, costs, ppl): def attach_extendable_generators(n, costs, ppl):
elec_opts = snakemake.config['electricity'] elec_opts = snakemake.config['electricity']
carriers = pd.Index(elec_opts['extendable_carriers']['Generator']) carriers = pd.Index(elec_opts['extendable_carriers']['Generator'])
_add_missing_carriers_from_costs(n, costs, carriers) _add_missing_carriers_from_costs(n, costs, carriers)
for tech in carriers: for tech in carriers:
suptech = tech.split('-')[0] if tech.startswith('OCGT'):
if suptech == 'OCGT':
ocgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first() ocgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first()
n.madd('Generator', ocgt.index, n.madd('Generator', ocgt.index,
suffix=' OCGT', suffix=' OCGT',
@ -449,7 +444,7 @@ def attach_extendable_generators(n, costs, ppl):
marginal_cost=costs.at['OCGT', 'marginal_cost'], marginal_cost=costs.at['OCGT', 'marginal_cost'],
efficiency=costs.at['OCGT', 'efficiency']) efficiency=costs.at['OCGT', 'efficiency'])
elif suptech == 'CCGT': elif tech.startswith('CCGT'):
ccgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first() ccgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first()
n.madd('Generator', ccgt.index, n.madd('Generator', ccgt.index,
suffix=' CCGT', suffix=' CCGT',
@ -461,7 +456,7 @@ def attach_extendable_generators(n, costs, ppl):
marginal_cost=costs.at['CCGT', 'marginal_cost'], marginal_cost=costs.at['CCGT', 'marginal_cost'],
efficiency=costs.at['CCGT', 'efficiency']) efficiency=costs.at['CCGT', 'efficiency'])
elif suptech == 'nuclear': elif tech.startswith('nuclear'):
nuclear = ppl.query("carrier == 'nuclear'").groupby('bus', as_index=False).first() nuclear = ppl.query("carrier == 'nuclear'").groupby('bus', as_index=False).first()
n.madd('Generator', nuclear.index, n.madd('Generator', nuclear.index,
suffix=' nuclear', suffix=' nuclear',
@ -479,77 +474,6 @@ def attach_extendable_generators(n, costs, ppl):
"Only OCGT, CCGT and nuclear are allowed at the moment.") "Only OCGT, CCGT and nuclear are allowed at the moment.")
def attach_storage(n, costs):
elec_opts = snakemake.config['electricity']
carriers = elec_opts['extendable_carriers']['StorageUnit']
max_hours = elec_opts['max_hours']
_add_missing_carriers_from_costs(n, costs, carriers)
buses_i = n.buses.index[n.buses.substation_lv]
for carrier in carriers:
n.madd("StorageUnit", buses_i, ' ' + carrier,
bus=buses_i,
carrier=carrier,
p_nom_extendable=True,
capital_cost=costs.at[carrier, 'capital_cost'],
marginal_cost=costs.at[carrier, 'marginal_cost'],
efficiency_store=costs.at[carrier, 'efficiency'],
efficiency_dispatch=costs.at[carrier, 'efficiency'],
max_hours=max_hours[carrier],
cyclic_state_of_charge=True)
## Implementing them separately will come later!
##
# if 'H2' in carriers:
# h2_buses = n.madd("Bus", buses + " H2", carrier="H2")
# n.madd("Link", h2_buses + " Electrolysis",
# bus1=h2_buses,
# bus0=buses,
# p_nom_extendable=True,
# efficiency=costs.at["electrolysis", "efficiency"],
# capital_cost=costs.at["electrolysis", "capital_cost"])
# n.madd("Link", h2_buses + " Fuel Cell",
# bus0=h2_buses,
# bus1=buses,
# p_nom_extendable=True,
# efficiency=costs.at["fuel cell", "efficiency"],
# #NB: fixed cost is per MWel
# capital_cost=costs.at["fuel cell", "capital_cost"] * costs.at["fuel cell", "efficiency"])
# n.madd("Store", h2_buses,
# bus=h2_buses,
# e_nom_extendable=True,
# e_cyclic=True,
# capital_cost=costs.at["hydrogen storage", "capital_cost"])
# if 'battery' in carriers:
# b_buses = n.madd("Bus", buses + " battery", carrier="battery")
# network.madd("Store", b_buses,
# bus=b_buses,
# e_cyclic=True,
# e_nom_extendable=True,
# capital_cost=costs.at['battery storage', 'capital_cost'])
# network.madd("Link", b_buses + " charger",
# bus0=buses,
# bus1=b_buses,
# efficiency=costs.at['battery inverter', 'efficiency']**0.5,
# capital_cost=costs.at['battery inverter', 'capital_cost'],
# p_nom_extendable=True)
# network.madd("Link",
# nodes + " battery discharger",
# bus0=nodes + " battery",
# bus1=nodes,
# efficiency=costs.at['battery inverter','efficiency']**0.5,
# marginal_cost=options['marginal_cost_storage'],
# p_nom_extendable=True)
def estimate_renewable_capacities(n, tech_map=None): def estimate_renewable_capacities(n, tech_map=None):
if tech_map is None: if tech_map is None:
tech_map = (snakemake.config['electricity'] tech_map = (snakemake.config['electricity']
@ -574,8 +498,9 @@ def estimate_renewable_capacities(n, tech_map=None):
.transform(lambda s: normed(s) * tech_capacities.at[s.name]) .transform(lambda s: normed(s) * tech_capacities.at[s.name])
.where(lambda s: s>0.1, 0.)) # only capacities above 100kW .where(lambda s: s>0.1, 0.)) # only capacities above 100kW
def add_nice_carrier_names(n): def add_nice_carrier_names(n, config=None):
nice_names = pd.Series(snakemake.config['plotting']['nice_names']) if config is None: config = snakemake.config
nice_names = pd.Series(config['plotting']['nice_names'])
n.carriers['nice_names'] = nice_names[n.carriers.index] n.carriers['nice_names'] = nice_names[n.carriers.index]
@ -608,13 +533,11 @@ if __name__ == "__main__":
attach_load(n) attach_load(n)
update_transmission_costs(n, costs) update_transmission_costs(n, costs)
attach_conventional_generators(n, costs, ppl)
attach_conventional_generators(n, costs, ppl)
attach_wind_and_solar(n, costs) attach_wind_and_solar(n, costs)
if 'hydro' in snakemake.config['renewable']: attach_hydro(n, costs, ppl)
attach_hydro(n, costs, ppl)
attach_extendable_generators(n, costs, ppl) attach_extendable_generators(n, costs, ppl)
attach_storage(n, costs)
estimate_renewable_capacities(n) estimate_renewable_capacities(n)
add_nice_carrier_names(n) add_nice_carrier_names(n)

View File

@ -0,0 +1,166 @@
# coding: utf-8
"""
Adds extra extendable components to the clustered and simplified network.
Relevant Settings
-----------------
.. code:: yaml
costs:
year:
USD2013_to_EUR2013:
dicountrate:
emission_prices:
electricity:
max_hours:
marginal_cost:
capital_cost:
extendable_carriers:
StorageUnit:
Store:
.. seealso::
Documentation of the configuration file ``config.yaml`` at :ref:`costs_cf`,
:ref:`electricity_cf`
Inputs
------
- ``data/costs.csv``: The database of cost assumptions for all included technologies for specific years from various sources; e.g. discount rate, lifetime, investment (CAPEX), fixed operation and maintenance (FOM), variable operation and maintenance (VOM), fuel costs, efficiency, carbon-dioxide intensity.
Outputs
-------
- ``networks/{network}_s{simpl}_{clusters}_ec.nc``:
Description
-----------
The rule :mod:`add_extra_components` attaches additional extendable components to the clustered and simplified network. These can be configured in the ``config.yaml`` at ``electricity: extendable_carriers: ``. It processes ``networks/{network}_s{simpl}_{clusters}.nc`` to build ``networks/{network}_s{simpl}_{clusters}_ec.nc``, which in contrast to the former (depending on the configuration) contain with **zero** initial capacity
- ``StorageUnits`` of carrier 'H2' and/or 'battery'. If this option is chosen, every bus is given an extendable ``StorageUnit`` of the corresponding carrier. The energy and power capacities are linked through a parameter that specifies the energy capacity as maximum hours at full dispatch power and is configured in ``electricity: max_hours:``. This linkage leads to one investment variable per storage unit. The default ``max_hours`` lead to long-term hydrogen and short-term battery storage units.
- ``Stores`` of carrier 'H2' and/or 'battery' in combination with ``Links``. If this option is chosen, the script adds extra buses with corresponding carrier where energy ``Stores`` are attached and which are connected to the corresponding power buses via two links, one each for charging and discharging. This leads to three investment variables for the energy capacity, charging and discharging capacity of the storage unit.
"""
import logging
import pandas as pd
import pypsa
from add_electricity import (load_costs, normed, add_nice_carrier_names,
_add_missing_carriers_from_costs)
idx = pd.IndexSlice
logger = logging.getLogger(__name__)
def attach_storageunits(n, costs):
elec_opts = snakemake.config['electricity']
carriers = elec_opts['extendable_carriers']['StorageUnit']
max_hours = elec_opts['max_hours']
_add_missing_carriers_from_costs(n, costs, carriers)
buses_i = n.buses.index
for carrier in carriers:
n.madd("StorageUnit", buses_i, ' ' + carrier,
bus=buses_i,
carrier=carrier,
p_nom_extendable=True,
capital_cost=costs.at[carrier, 'capital_cost'],
marginal_cost=costs.at[carrier, 'marginal_cost'],
efficiency_store=costs.at[carrier, 'efficiency'],
efficiency_dispatch=costs.at[carrier, 'efficiency'],
max_hours=max_hours[carrier],
cyclic_state_of_charge=True)
def attach_stores(n, costs):
elec_opts = snakemake.config['electricity']
carriers = elec_opts['extendable_carriers']['Store']
_add_missing_carriers_from_costs(n, costs, carriers)
buses_i = n.buses.index
bus_sub_dict = {k: n.buses[k].values for k in ['x', 'y', 'country']}
if 'H2' in carriers:
h2_buses_i = n.madd("Bus", buses_i + " H2", carrier="H2", **bus_sub_dict)
n.madd("Store", h2_buses_i,
bus=h2_buses_i,
carrier='H2',
e_nom_extendable=True,
e_cyclic=True,
capital_cost=costs.at["hydrogen storage", "capital_cost"])
n.madd("Link", h2_buses_i + " Electrolysis",
bus0=buses_i,
bus1=h2_buses_i,
carrier='H2 electrolysis',
p_nom_extendable=True,
efficiency=costs.at["electrolysis", "efficiency"],
capital_cost=costs.at["electrolysis", "capital_cost"])
n.madd("Link", h2_buses_i + " Fuel Cell",
bus0=h2_buses_i,
bus1=buses_i,
carrier='H2 fuel cell',
p_nom_extendable=True,
efficiency=costs.at["fuel cell", "efficiency"],
#NB: fixed cost is per MWel
capital_cost=costs.at["fuel cell", "capital_cost"] * costs.at["fuel cell", "efficiency"])
if 'battery' in carriers:
b_buses_i = n.madd("Bus", buses_i + " battery", carrier="battery", **bus_sub_dict)
n.madd("Store", b_buses_i,
bus=b_buses_i,
carrier='battery',
e_cyclic=True,
e_nom_extendable=True,
capital_cost=costs.at['battery storage', 'capital_cost'])
n.madd("Link", b_buses_i + " charger",
bus0=buses_i,
bus1=b_buses_i,
carrier='battery charger',
efficiency=costs.at['battery inverter', 'efficiency']**0.5,
capital_cost=costs.at['battery inverter', 'capital_cost'],
p_nom_extendable=True)
n.madd("Link", b_buses_i + " discharger",
bus0=b_buses_i,
bus1=buses_i,
carrier='battery discharger',
efficiency=costs.at['battery inverter','efficiency']**0.5,
capital_cost=costs.at['battery inverter', 'capital_cost'],
p_nom_extendable=True)
if __name__ == "__main__":
# Detect running outside of snakemake and mock snakemake for testing
if 'snakemake' not in globals():
from vresutils.snakemake import MockSnakemake, Dict
snakemake = MockSnakemake(output=['networks/elec_s_5_ec.nc'])
snakemake.input = snakemake.expand(
Dict(network='networks/elec_s_5.nc',
tech_costs='data/costs.csv'))
logging.basicConfig(level=snakemake.config['logging_level'])
n = pypsa.Network(snakemake.input.network)
Nyears = n.snapshot_weightings.sum()/8760.
costs = load_costs(Nyears, tech_costs=snakemake.input.tech_costs,
config=snakemake.config['costs'],
elec_config=snakemake.config['electricity'])
attach_storageunits(n, costs)
attach_stores(n, costs)
add_nice_carrier_names(n, config=snakemake.config)
n.export_to_netcdf(snakemake.output[0])

View File

@ -31,6 +31,7 @@ electricity:
extendable_carriers: extendable_carriers:
Generator: [OCGT] Generator: [OCGT]
StorageUnit: [battery, H2] StorageUnit: [battery, H2]
Store: [] #battery, H2
max_hours: max_hours:
battery: 6 battery: 6