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:
parent
6cde3e902b
commit
81d9d2ab4d
67
Snakefile
67
Snakefile
@ -16,12 +16,12 @@ rule cluster_all_elec_networks:
|
||||
|
||||
rule prepare_all_elec_networks:
|
||||
input:
|
||||
expand("networks/elec_s{simpl}_{clusters}_l{ll}_{opts}.nc",
|
||||
expand("networks/elec_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc",
|
||||
**config['scenario'])
|
||||
|
||||
rule solve_all_elec_networks:
|
||||
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'])
|
||||
|
||||
if config['enable'].get('prepare_links_p_nom', False):
|
||||
@ -221,6 +221,19 @@ rule cluster_network:
|
||||
# group: 'build_pypsa_networks'
|
||||
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:
|
||||
# input:
|
||||
# network="networks/elec_{cost}_{resarea}_{opts}.nc",
|
||||
@ -232,11 +245,11 @@ rule cluster_network:
|
||||
# script: "scripts/add_sectors.py"
|
||||
|
||||
rule prepare_network:
|
||||
input: 'networks/{network}_s{simpl}_{clusters}.nc', tech_costs=COSTS
|
||||
output: 'networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc'
|
||||
input: 'networks/{network}_s{simpl}_{clusters}_ec.nc', tech_costs=COSTS
|
||||
output: 'networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc'
|
||||
threads: 1
|
||||
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"
|
||||
|
||||
def memory(w):
|
||||
@ -253,24 +266,24 @@ def memory(w):
|
||||
# return 4890+310 * int(w.clusters)
|
||||
|
||||
rule solve_network:
|
||||
input: "networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc"
|
||||
output: "results/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}_ec_l{ll}_{opts}.nc"
|
||||
shadow: "shallow"
|
||||
log:
|
||||
solver="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_solver.log",
|
||||
python="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_python.log",
|
||||
memory="logs/{network}_s{simpl}_{clusters}_l{ll}_{opts}_memory.log"
|
||||
benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}"
|
||||
solver="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_solver.log",
|
||||
python="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_python.log",
|
||||
memory="logs/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_memory.log"
|
||||
benchmark: "benchmarks/solve_network/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}"
|
||||
threads: 4
|
||||
resources: mem=memory
|
||||
# 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"
|
||||
|
||||
rule trace_solve_network:
|
||||
input: "networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc"
|
||||
output: "results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}_trace.nc"
|
||||
input: "networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
|
||||
output: "results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_trace.nc"
|
||||
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
|
||||
resources: mem=memory
|
||||
script: "scripts/trace_solve_network.py"
|
||||
@ -278,14 +291,14 @@ rule trace_solve_network:
|
||||
rule solve_operations_network:
|
||||
input:
|
||||
unprepared="networks/{network}_s{simpl}_{clusters}.nc",
|
||||
optimized="results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}.nc"
|
||||
output: "results/networks/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op.nc"
|
||||
optimized="results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}.nc"
|
||||
output: "results/networks/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_op.nc"
|
||||
shadow: "shallow"
|
||||
log:
|
||||
solver="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_solver.log",
|
||||
python="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_python.log",
|
||||
memory="logs/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}_op_memory.log"
|
||||
benchmark: "benchmarks/solve_operations_network/{network}_s{simpl}_{clusters}_l{ll}_{opts}"
|
||||
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}_ec_l{ll}_{opts}_op_python.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}_ec_l{ll}_{opts}"
|
||||
threads: 4
|
||||
resources: mem=(lambda w: 5000 + 372 * int(w.clusters))
|
||||
# group: "solve_operations"
|
||||
@ -293,11 +306,11 @@ rule solve_operations_network:
|
||||
|
||||
rule plot_network:
|
||||
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
|
||||
output:
|
||||
only_map="results/plots/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{attr}.{ext}",
|
||||
ext="results/plots/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{attr}_ext.{ext}"
|
||||
only_map="results/plots/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{attr}.{ext}",
|
||||
ext="results/plots/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{attr}_ext.{ext}"
|
||||
script: "scripts/plot_network.py"
|
||||
|
||||
def input_make_summary(w):
|
||||
@ -309,7 +322,7 @@ def input_make_summary(w):
|
||||
else:
|
||||
ll = w.ll
|
||||
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,
|
||||
ll=ll,
|
||||
**{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:
|
||||
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"
|
||||
|
||||
rule plot_summary:
|
||||
input: "results/summaries/{network}_s{simpl}_{clusters}_l{ll}_{opts}_{country}"
|
||||
output: "results/plots/summary_{summary}_{network}_s{simpl}_{clusters}_l{ll}_{opts}_{country}.{ext}"
|
||||
input: "results/summaries/{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{country}"
|
||||
output: "results/plots/summary_{summary}_{network}_s{simpl}_{clusters}_ec_l{ll}_{opts}_{country}.{ext}"
|
||||
script: "scripts/plot_summary.py"
|
||||
|
||||
def input_plot_p_nom_max(wildcards):
|
||||
|
@ -33,6 +33,7 @@ electricity:
|
||||
extendable_carriers:
|
||||
Generator: [OCGT]
|
||||
StorageUnit: [battery, H2]
|
||||
Store: [] # battery, H2
|
||||
|
||||
max_hours:
|
||||
battery: 6
|
||||
|
@ -31,6 +31,7 @@ electricity:
|
||||
extendable_carriers:
|
||||
Generator: [OCGT]
|
||||
StorageUnit: [battery, H2]
|
||||
Store: [] #battery, H2
|
||||
|
||||
max_hours:
|
||||
battery: 6
|
||||
|
@ -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."
|
||||
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,,,
|
||||
-- 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."
|
||||
-- StorageUnit,--,"Any subset of {'battery','H2'}","Places extendable storage units (battery and/or hydrogen) at every node/bus 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'}","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,,,
|
||||
-- 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>`_."
|
||||
|
|
@ -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 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>`_).
|
||||
|
@ -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>`_.
|
||||
|
||||
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::
|
||||
:caption: Overview
|
||||
|
||||
simplification/simplify_network
|
||||
simplification/cluster_network
|
||||
simplification/add_extra_components
|
||||
simplification/prepare_network
|
||||
|
37
doc/simplification/add_extra_components.rst
Normal file
37
doc/simplification/add_extra_components.rst
Normal 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
|
@ -119,7 +119,7 @@ orders ``snakemake`` to run the script ``solve_network`` that produces the solve
|
||||
|
||||
.. warning::
|
||||
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``.
|
||||
|
||||
This triggers a workflow of multiple preceding jobs that depend on each rule's inputs and outputs:
|
||||
|
@ -17,6 +17,7 @@ dependencies:
|
||||
- memory_profiler
|
||||
- yaml
|
||||
- pytables
|
||||
- powerplantmatching>=0.4.2
|
||||
|
||||
# Second order dependencies which should really be deps of atlite
|
||||
- xarray
|
||||
@ -25,6 +26,7 @@ dependencies:
|
||||
#- toolz
|
||||
#- dask
|
||||
- progressbar2
|
||||
- pyyaml>=5.1.0
|
||||
|
||||
# Include ipython so that one does not inadvertently drop out of the conda
|
||||
# environment by calling ipython
|
||||
@ -46,6 +48,5 @@ dependencies:
|
||||
- git+https://github.com/PyPSA/glaes.git#egg=glaes
|
||||
- git+https://github.com/PyPSA/geokit.git#egg=geokit
|
||||
- cdsapi
|
||||
- powerplantmatching
|
||||
- sphinx
|
||||
- sphinx_rtd_theme
|
||||
|
@ -18,6 +18,7 @@ dependencies:
|
||||
- memory_profiler
|
||||
- yaml
|
||||
- pytables
|
||||
- powerplantmatching>=0.4.2
|
||||
|
||||
# Second order dependencies which should really be deps of atlite
|
||||
- xarray
|
||||
@ -26,6 +27,7 @@ dependencies:
|
||||
- toolz
|
||||
- dask
|
||||
- progressbar2
|
||||
- pyyaml>=5.1.0
|
||||
|
||||
# Include ipython so that one does not inadvertently drop out of the conda
|
||||
# environment by calling ipython
|
||||
@ -46,4 +48,3 @@ dependencies:
|
||||
- git+https://github.com/PyPSA/glaes.git#egg=glaes
|
||||
- git+https://github.com/PyPSA/geokit.git#egg=geokit
|
||||
- cdsapi
|
||||
- powerplantmatching>=0.4.2
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
-----------------
|
||||
@ -21,7 +21,6 @@ Relevant Settings
|
||||
co2limit:
|
||||
extendable_carriers:
|
||||
Generator:
|
||||
StorageUnit:
|
||||
estimate_renewable_capacities_from_capacity_stats:
|
||||
|
||||
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 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),
|
||||
- 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``)
|
||||
"""
|
||||
|
||||
@ -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 inverter pair', 'capital_cost'])
|
||||
n.links.loc[dc_b, 'capital_cost'] = costs
|
||||
# ### Generators
|
||||
|
||||
### Generators
|
||||
|
||||
def attach_wind_and_solar(n, costs):
|
||||
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())
|
||||
|
||||
|
||||
# # Generators
|
||||
|
||||
|
||||
def attach_conventional_generators(n, costs, ppl):
|
||||
carriers = snakemake.config['electricity']['conventional_carriers']
|
||||
@ -329,6 +326,7 @@ def attach_conventional_generators(n, costs, ppl):
|
||||
|
||||
|
||||
def attach_hydro(n, costs, ppl):
|
||||
if 'hydro' not in snakemake.config['renewable']: return
|
||||
c = snakemake.config['renewable']['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):
|
||||
elec_opts = snakemake.config['electricity']
|
||||
carriers = pd.Index(elec_opts['extendable_carriers']['Generator'])
|
||||
|
||||
_add_missing_carriers_from_costs(n, costs, carriers)
|
||||
|
||||
for tech in carriers:
|
||||
suptech = tech.split('-')[0]
|
||||
|
||||
if suptech == 'OCGT':
|
||||
if tech.startswith('OCGT'):
|
||||
ocgt = ppl.query("carrier in ['OCGT', 'CCGT']").groupby('bus', as_index=False).first()
|
||||
n.madd('Generator', ocgt.index,
|
||||
suffix=' OCGT',
|
||||
@ -449,7 +444,7 @@ def attach_extendable_generators(n, costs, ppl):
|
||||
marginal_cost=costs.at['OCGT', 'marginal_cost'],
|
||||
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()
|
||||
n.madd('Generator', ccgt.index,
|
||||
suffix=' CCGT',
|
||||
@ -461,7 +456,7 @@ def attach_extendable_generators(n, costs, ppl):
|
||||
marginal_cost=costs.at['CCGT', 'marginal_cost'],
|
||||
efficiency=costs.at['CCGT', 'efficiency'])
|
||||
|
||||
elif suptech == 'nuclear':
|
||||
elif tech.startswith('nuclear'):
|
||||
nuclear = ppl.query("carrier == 'nuclear'").groupby('bus', as_index=False).first()
|
||||
n.madd('Generator', nuclear.index,
|
||||
suffix=' nuclear',
|
||||
@ -479,77 +474,6 @@ def attach_extendable_generators(n, costs, ppl):
|
||||
"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):
|
||||
if tech_map is None:
|
||||
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])
|
||||
.where(lambda s: s>0.1, 0.)) # only capacities above 100kW
|
||||
|
||||
def add_nice_carrier_names(n):
|
||||
nice_names = pd.Series(snakemake.config['plotting']['nice_names'])
|
||||
def add_nice_carrier_names(n, config=None):
|
||||
if config is None: config = snakemake.config
|
||||
nice_names = pd.Series(config['plotting']['nice_names'])
|
||||
n.carriers['nice_names'] = nice_names[n.carriers.index]
|
||||
|
||||
|
||||
@ -608,13 +533,11 @@ if __name__ == "__main__":
|
||||
attach_load(n)
|
||||
|
||||
update_transmission_costs(n, costs)
|
||||
attach_conventional_generators(n, costs, ppl)
|
||||
|
||||
attach_conventional_generators(n, costs, ppl)
|
||||
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_storage(n, costs)
|
||||
|
||||
estimate_renewable_capacities(n)
|
||||
add_nice_carrier_names(n)
|
||||
|
166
scripts/add_extra_components.py
Normal file
166
scripts/add_extra_components.py
Normal 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])
|
@ -31,6 +31,7 @@ electricity:
|
||||
extendable_carriers:
|
||||
Generator: [OCGT]
|
||||
StorageUnit: [battery, H2]
|
||||
Store: [] #battery, H2
|
||||
|
||||
max_hours:
|
||||
battery: 6
|
||||
|
Loading…
Reference in New Issue
Block a user