pypsa-eur/scripts/build_powerplants.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

228 lines
7.9 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
2024-01-08 08:30:21 +00:00
# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors
#
2021-09-14 14:37:41 +00:00
# SPDX-License-Identifier: MIT
# coding: utf-8
"""
Retrieves conventional powerplant capacities and locations from
2024-08-02 09:42:30 +00:00
`powerplantmatching <https://github.com/PyPSA/powerplantmatching>`_, assigns
these to buses and creates a ``.csv`` file. It is possible to amend the
powerplant database with custom entries provided in
``data/custom_powerplants.csv``.
2024-01-08 08:30:21 +00:00
Lastly, for every substation, powerplants with zero-initial capacity can be added for certain fuel types automatically.
2019-08-11 09:40:47 +00:00
Relevant Settings
-----------------
2019-08-11 20:34:18 +00:00
.. code:: yaml
2019-10-31 17:01:33 +00:00
electricity:
2019-11-01 12:27:42 +00:00
powerplants_filter:
custom_powerplants:
2024-01-08 08:30:21 +00:00
everywhere_powerplants:
2019-08-11 11:17:36 +00:00
.. seealso::
Documentation of the configuration file ``config/config.yaml`` at
:ref:`electricity`
2019-08-11 09:40:47 +00:00
Inputs
------
2019-08-11 20:34:18 +00:00
- ``networks/base.nc``: confer :ref:`base`.
2024-08-02 09:42:30 +00:00
- ``data/custom_powerplants.csv``: custom powerplants in the same format as `powerplantmatching <https://github.com/PyPSA/powerplantmatching>`_ provides
2019-08-11 20:34:18 +00:00
2019-08-11 09:40:47 +00:00
Outputs
-------
- ``resource/powerplants_s_{clusters}.csv``: A list of conventional power plants (i.e. neither wind nor solar) with fields for name, fuel type, technology, country, capacity in MW, duration, commissioning year, retrofit year, latitude, longitude, and dam information as documented in the `powerplantmatching README <https://github.com/PyPSA/powerplantmatching/blob/master/README.md>`_; additionally it includes information on the closest substation/bus in ``networks/base_s_{clusters}.nc``.
2019-08-12 17:01:53 +00:00
2023-03-09 12:28:42 +00:00
.. image:: img/powerplantmatching.png
2019-08-12 17:01:53 +00:00
:scale: 30 %
2024-08-02 09:42:30 +00:00
**Source:** `powerplantmatching on GitHub <https://github.com/PyPSA/powerplantmatching>`_
2019-08-11 20:34:18 +00:00
2019-08-11 09:40:47 +00:00
Description
-----------
The configuration options ``electricity: powerplants_filter`` and ``electricity: custom_powerplants`` can be used to control whether data should be retrieved from the original powerplants database or from custom amendmends. These specify `pandas.query <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.query.html>`_ commands.
2024-01-08 08:30:21 +00:00
In addition the configuration option ``electricity: everywhere_powerplants`` can be used to place powerplants with zero-initial capacity of certain fuel types at all substations.
1. Adding all powerplants from custom:
.. code:: yaml
powerplants_filter: false
custom_powerplants: true
2. Replacing powerplants in e.g. Germany by custom data:
.. code:: yaml
powerplants_filter: Country not in ['Germany']
custom_powerplants: true
or
.. code:: yaml
powerplants_filter: Country not in ['Germany']
custom_powerplants: Country in ['Germany']
3. Adding additional built year constraints:
.. code:: yaml
powerplants_filter: Country not in ['Germany'] and YearCommissioned <= 2015
custom_powerplants: YearCommissioned <= 2015
2024-01-08 08:30:21 +00:00
4. Adding powerplants at all substations for 4 conventional carrier types:
.. code:: yaml
everywhere_powerplants: ['Natural Gas', 'Coal', 'nuclear', 'OCGT']
"""
2024-01-08 08:30:21 +00:00
import itertools
2019-10-31 13:48:10 +00:00
import logging
Add logging to logfiles to all snakemake workflow scripts. (#102) * Add logging to logfiles to all snakemake workflow scripts. * Fix missing quotation marks in Snakefile. * Apply suggestions from code review Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de> * Apply suggestions from code review Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de> * doc: fix _ec_ filenames in docs * Allow logging message format to be specified in config.yaml. * Add logging for Snakemake rule 'retrieve_databundle '. * Add limited logging to STDERR only for retrieve_*.py scripts. * Import progressbar module only on demand. * Fix logging to file and enable concurrent printing to STDERR for most scripts. * Add new 'logging_format' option to Travis CI test config.yaml. * Add missing parenthesis (bug fix) and cross-os compatible paths. * Fix typos in messages. * Use correct log files for logging (bug fix). * doc: fix line references * config: logging_format in all configs * doc: add doc for logging_format * environment: update to powerplantmatching 0.4.3 * doc: update line references for tutorial.rst * Change logging configuration scheme for config.yaml. * Add helper function for doing basic logging configuration. * Add logpath for prepare_links_p_nom rule. * Outsource basic logging configuration for all scripts to _helper submodule. * Update documentation for changed config.yaml structure. Instead of 'logging_level' and 'logging_format', now 'logging' with subcategories is used. * _helpers: Change configure_logging signature.
2019-11-28 07:22:52 +00:00
2024-01-08 08:30:21 +00:00
import numpy as np
import pandas as pd
import powerplantmatching as pm
import pypsa
2023-08-15 13:02:41 +00:00
from _helpers import configure_logging, set_scenario_config
from powerplantmatching.export import map_country_bus
logger = logging.getLogger(__name__)
def add_custom_powerplants(ppl, custom_powerplants, custom_ppl_query=False):
2019-10-31 13:48:10 +00:00
if not custom_ppl_query:
2019-08-19 16:04:51 +00:00
return ppl
add_ppls = pd.read_csv(custom_powerplants, dtype={"bus": "str"})
2019-10-31 13:48:10 +00:00
if isinstance(custom_ppl_query, str):
add_ppls.query(custom_ppl_query, inplace=True)
2022-01-29 15:17:46 +00:00
return pd.concat(
[ppl, add_ppls], sort=False, ignore_index=True, verify_integrity=True
)
2019-10-31 13:48:10 +00:00
2024-01-08 08:30:21 +00:00
def add_everywhere_powerplants(ppl, substations, everywhere_powerplants):
# Create a dataframe with "everywhere_powerplants" of stated carriers at the location of all substations
2024-01-08 08:45:14 +00:00
everywhere_ppl = (
2024-01-08 08:30:21 +00:00
pd.DataFrame(
itertools.product(substations.index.values, everywhere_powerplants),
columns=["substation_index", "Fueltype"],
).merge(
substations[["x", "y", "country"]],
left_on="substation_index",
right_index=True,
)
).drop(columns="substation_index")
# PPL uses different columns names compared to substations dataframe -> rename
everywhere_ppl = everywhere_ppl.rename(
columns={"x": "lon", "y": "lat", "country": "Country"}
)
2024-01-08 08:49:44 +00:00
# Add default values for the powerplants
2024-01-08 08:30:21 +00:00
everywhere_ppl["Name"] = (
2024-01-08 08:49:44 +00:00
"Automatically added everywhere-powerplant " + everywhere_ppl.Fueltype
)
2024-01-08 08:30:21 +00:00
everywhere_ppl["Set"] = "PP"
2024-01-08 08:49:44 +00:00
everywhere_ppl["Technology"] = everywhere_ppl["Fueltype"]
2024-01-08 08:30:21 +00:00
everywhere_ppl["Capacity"] = 0.0
2024-01-08 12:56:02 +00:00
# Assign plausible values for the commissioning and decommissioning years
# required for multi-year models
everywhere_ppl["DateIn"] = ppl["DateIn"].min()
everywhere_ppl["DateOut"] = ppl["DateOut"].max()
2024-01-08 08:30:21 +00:00
# NaN values for efficiency will be replaced by the generic efficiency by attach_conventional_generators(...) in add_electricity.py later
everywhere_ppl["Efficiency"] = np.nan
return pd.concat(
[ppl, everywhere_ppl], sort=False, ignore_index=True, verify_integrity=True
)
def replace_natural_gas_technology(df):
2024-05-20 13:49:40 +00:00
mapping = {
"Steam Turbine": "CCGT",
"Combustion Engine": "OCGT",
"Not Found": "CCGT",
}
tech = df.Technology.replace(mapping).fillna("CCGT")
return df.Technology.mask(df.Fueltype == "Natural Gas", tech)
def replace_natural_gas_fueltype(df):
return df.Fueltype.mask(
(df.Technology == "OCGT") | (df.Technology == "CCGT"), "Natural Gas"
)
if __name__ == "__main__":
if "snakemake" not in globals():
Introduce mocksnakemake which acutally parses Snakefile (#107) * rewrite mocksnakemake for parsing real Snakefile * continue add function to scripts * going through all scripts, setting new mocksnakemake * fix plotting scripts * fix build_country_flh * fix build_country_flh II * adjust config files * fix make_summary for tutorial network * create dir also for output * incorporate suggestions * consistent import of mocksnakemake * consistent import of mocksnakemake II * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/plot_network.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/plot_network.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/retrieve_databundle.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * use pathlib for mocksnakemake * rename mocksnakemake into mock_snakemake * revert change in data * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * remove setting logfile in mock_snakemake, use Path in configure_logging * fix fallback path and base_dir fix return type of make_io_accessable * reformulate mock_snakemake * incorporate suggestion, fix typos * mock_snakemake: apply absolute paths again, add assertion error *.py: make hard coded io path accessable for mock_snakemake * retrieve_natura_raster: use snakemake.output for fn_out * include suggestion * Apply suggestions from code review Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * linting, add return ad end of file * Update scripts/plot_p_nom_max.py Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * Update scripts/plot_p_nom_max.py fixes #112 Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * plot_p_nom_max: small correction * config.tutorial.yaml fix snapshots end * use techs instead of technology * revert try out from previous commit, complete replacing * change clusters -> clusts in plot_p_nom_max due to wildcard constraints of clusters * change clusters -> clusts in plot_p_nom_max due to wildcard constraints of clusters II
2019-12-09 20:29:15 +00:00
from _helpers import mock_snakemake
Introduce mocksnakemake which acutally parses Snakefile (#107) * rewrite mocksnakemake for parsing real Snakefile * continue add function to scripts * going through all scripts, setting new mocksnakemake * fix plotting scripts * fix build_country_flh * fix build_country_flh II * adjust config files * fix make_summary for tutorial network * create dir also for output * incorporate suggestions * consistent import of mocksnakemake * consistent import of mocksnakemake II * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/plot_network.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/plot_network.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * Update scripts/retrieve_databundle.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * use pathlib for mocksnakemake * rename mocksnakemake into mock_snakemake * revert change in data * Update scripts/_helpers.py Co-Authored-By: euronion <42553970+euronion@users.noreply.github.com> * remove setting logfile in mock_snakemake, use Path in configure_logging * fix fallback path and base_dir fix return type of make_io_accessable * reformulate mock_snakemake * incorporate suggestion, fix typos * mock_snakemake: apply absolute paths again, add assertion error *.py: make hard coded io path accessable for mock_snakemake * retrieve_natura_raster: use snakemake.output for fn_out * include suggestion * Apply suggestions from code review Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * linting, add return ad end of file * Update scripts/plot_p_nom_max.py Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * Update scripts/plot_p_nom_max.py fixes #112 Co-Authored-By: Jonas Hörsch <jonas.hoersch@posteo.de> * plot_p_nom_max: small correction * config.tutorial.yaml fix snapshots end * use techs instead of technology * revert try out from previous commit, complete replacing * change clusters -> clusts in plot_p_nom_max due to wildcard constraints of clusters * change clusters -> clusts in plot_p_nom_max due to wildcard constraints of clusters II
2019-12-09 20:29:15 +00:00
snakemake = mock_snakemake("build_powerplants")
Add logging to logfiles to all snakemake workflow scripts. (#102) * Add logging to logfiles to all snakemake workflow scripts. * Fix missing quotation marks in Snakefile. * Apply suggestions from code review Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de> * Apply suggestions from code review Co-Authored-By: Fabian Neumann <fabian.neumann@outlook.de> * doc: fix _ec_ filenames in docs * Allow logging message format to be specified in config.yaml. * Add logging for Snakemake rule 'retrieve_databundle '. * Add limited logging to STDERR only for retrieve_*.py scripts. * Import progressbar module only on demand. * Fix logging to file and enable concurrent printing to STDERR for most scripts. * Add new 'logging_format' option to Travis CI test config.yaml. * Add missing parenthesis (bug fix) and cross-os compatible paths. * Fix typos in messages. * Use correct log files for logging (bug fix). * doc: fix line references * config: logging_format in all configs * doc: add doc for logging_format * environment: update to powerplantmatching 0.4.3 * doc: update line references for tutorial.rst * Change logging configuration scheme for config.yaml. * Add helper function for doing basic logging configuration. * Add logpath for prepare_links_p_nom rule. * Outsource basic logging configuration for all scripts to _helper submodule. * Update documentation for changed config.yaml structure. Instead of 'logging_level' and 'logging_format', now 'logging' with subcategories is used. * _helpers: Change configure_logging signature.
2019-11-28 07:22:52 +00:00
configure_logging(snakemake)
2023-08-15 13:02:41 +00:00
set_scenario_config(snakemake)
n = pypsa.Network(snakemake.input.network)
countries = snakemake.params.countries
ppl = (
pm.powerplants(from_url=True)
2022-04-04 16:07:48 +00:00
.powerplant.fill_missing_decommissioning_years()
.powerplant.convert_country_to_alpha2()
.query('Fueltype not in ["Solar", "Wind"] and Country in @countries')
.assign(Technology=replace_natural_gas_technology)
.assign(Fueltype=replace_natural_gas_fueltype)
)
2022-04-08 10:23:24 +00:00
# Correct bioenergy for countries where possible
opsd = pm.data.OPSD_VRE().powerplant.convert_country_to_alpha2()
opsd = opsd.query('Country in @countries and Fueltype == "Bioenergy"')
opsd["Name"] = "Biomass"
2022-04-08 10:23:24 +00:00
available_countries = opsd.Country.unique()
ppl = ppl.query('not (Country in @available_countries and Fueltype == "Bioenergy")')
ppl = pd.concat([ppl, opsd])
ppl_query = snakemake.params.powerplants_filter
2019-10-31 13:48:10 +00:00
if isinstance(ppl_query, str):
ppl.query(ppl_query, inplace=True)
# add carriers from own powerplant files:
custom_ppl_query = snakemake.params.custom_powerplants
ppl = add_custom_powerplants(
ppl, snakemake.input.custom_powerplants, custom_ppl_query
)
2023-10-08 09:20:36 +00:00
if countries_wo_ppl := set(countries) - set(ppl.Country.unique()):
logging.warning(f"No powerplants known in: {', '.join(countries_wo_ppl)}")
2024-01-08 08:30:21 +00:00
# Add "everywhere powerplants" to all bus locations
ppl = add_everywhere_powerplants(
ppl, n.buses, snakemake.params.everywhere_powerplants
2024-01-08 08:30:21 +00:00
)
2023-06-29 17:24:38 +00:00
ppl = ppl.dropna(subset=["lat", "lon"])
ppl = map_country_bus(ppl, n.buses)
bus_null_b = ppl["bus"].isnull()
if bus_null_b.any():
logging.warning(
f"Couldn't find close bus for {bus_null_b.sum()} powerplants. "
"Removing them from the powerplants list."
)
ppl = ppl[~bus_null_b]
# TODO: This has to fixed in PPM, some powerplants are still duplicated
cumcount = ppl.groupby(["bus", "Fueltype"]).cumcount() + 1
ppl.Name = ppl.Name.where(cumcount == 1, ppl.Name + " " + cumcount.astype(str))
ppl.reset_index(drop=True).to_csv(snakemake.output[0])