diff --git a/.gitignore b/.gitignore index 83c1397e6..54b36be88 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__ *dconf gurobi.log .vscode +.idea *.orig /bak @@ -25,6 +26,7 @@ gurobi.log /tmp doc/_build + /scripts/old /scripts/create_scenarios.py /config/create_scenarios.py @@ -56,6 +58,14 @@ dask-worker-space/ publications.jrc.ec.europa.eu/ d1gam3xoknrgr2.cloudfront.net/ +#latex files in report/ +report/*.aux +report/*.bbl +report/*.blg +report/*.log +report/*.out +report/*.toc + *.org *.nc diff --git a/.reuse/dep5 b/.reuse/dep5 index 251189232..0f7829d69 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -34,3 +34,7 @@ License: CC0-1.0 Files: borg-it Copyright: 2017-2024 The PyPSA-Eur Authors License: CC0-1.0 + +Files: report/* +Copyright: Open Energy Transition gGmbH +License: MIT diff --git a/README.md b/README.md index 1ed6f43cf..2eb2d11ef 100644 --- a/README.md +++ b/README.md @@ -13,96 +13,86 @@ SPDX-License-Identifier: CC-BY-4.0 [![REUSE status](https://api.reuse.software/badge/github.com/pypsa/pypsa-eur)](https://api.reuse.software/info/github.com/pypsa/pypsa-eur) [![Stack Exchange questions](https://img.shields.io/stackexchange/stackoverflow/t/pypsa)](https://stackoverflow.com/questions/tagged/pypsa) -# PyPSA-Eur: A Sector-Coupled Open Optimisation Model of the European Energy System - -PyPSA-Eur is an open model dataset of the European energy system at the -transmission network level that covers the full ENTSO-E area. The model is suitable both for operational studies and generation and transmission expansion planning studies. -The continental scope and highly resolved spatial scale enables a proper description of the long-range -smoothing effects for renewable power generation and their varying resource availability. - - - - -The model is described in the [documentation](https://pypsa-eur.readthedocs.io) -and in the paper -[PyPSA-Eur: An Open Optimisation Model of the European Transmission -System](https://arxiv.org/abs/1806.01613), 2018, -[arXiv:1806.01613](https://arxiv.org/abs/1806.01613). -The model building routines are defined through a snakemake workflow. -Please see the [documentation](https://pypsa-eur.readthedocs.io/) -for installation instructions and other useful information about the snakemake workflow. -The model is designed to be imported into the open toolbox -[PyPSA](https://github.com/PyPSA/PyPSA). - -**WARNING**: PyPSA-Eur is under active development and has several -[limitations](https://pypsa-eur.readthedocs.io/en/latest/limitations.html) which -you should understand before using the model. The github repository -[issues](https://github.com/PyPSA/pypsa-eur/issues) collect known topics we are -working on (please feel free to help or make suggestions). The -[documentation](https://pypsa-eur.readthedocs.io/) remains somewhat patchy. You -can find showcases of the model's capabilities in the Joule paper [The potential -role of a hydrogen network in -Europe](https://doi.org/10.1016/j.joule.2023.06.016), another [paper in Joule -with a description of the industry -sector](https://doi.org/10.1016/j.joule.2022.04.016), or in [a 2021 presentation -at EMP-E](https://nworbmot.org/energy/brown-empe.pdf). We do not recommend to -use the full resolution network model for simulations. At high granularity the -assignment of loads and generators to the nearest network node may not be a -correct assumption, depending on the topology of the underlying distribution -grid, and local grid bottlenecks may cause unrealistic load-shedding or -generator curtailment. We recommend to cluster the network to a couple of -hundred nodes to remove these local inconsistencies. See the discussion in -Section 3.4 "Model validation" of the paper. - - -![PyPSA-Eur Grid Model](doc/img/elec.png) - -The dataset consists of: - -- A grid model based on a modified [GridKit](https://github.com/bdw/GridKit) - extraction of the [ENTSO-E Transmission System - Map](https://www.entsoe.eu/data/map/). The grid model contains 7072 lines - (alternating current lines at and above 220kV voltage level and all high - voltage direct current lines) and 3803 substations. -- The open power plant database - [powerplantmatching](https://github.com/PyPSA/powerplantmatching). -- Electrical demand time series from the - [OPSD project](https://open-power-system-data.org/). -- Renewable time series based on ERA5 and SARAH, assembled using the [atlite tool](https://github.com/PyPSA/atlite). -- Geographical potentials for wind and solar generators based on land use (CORINE) and excluding nature reserves (Natura2000) are computed with the [atlite library](https://github.com/PyPSA/atlite). - -A sector-coupled extension adds demand -and supply for the following sectors: transport, space and water -heating, biomass, industry and industrial feedstocks, agriculture, -forestry and fishing. This completes the energy system and includes -all greenhouse gas emitters except waste management and land use. - -This diagram gives an overview of the sectors and the links between -them: - -![sector diagram](doc/img/multisector_figure.png) - -Each of these sectors is built up on the transmission network nodes -from [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur): - -![network diagram](https://github.com/PyPSA/pypsa-eur/blob/master/doc/img/base.png?raw=true) - -For computational reasons the model is usually clustered down -to 50-200 nodes. - -Already-built versions of the model can be found in the accompanying [Zenodo -repository](https://doi.org/10.5281/zenodo.3601881). - -# Contributing and Support -We strongly welcome anyone interested in contributing to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on GitHub. -- In case of code-related **questions**, please post on [stack overflow](https://stackoverflow.com/questions/tagged/pypsa). -- For non-programming related and more general questions please refer to the [mailing list](https://groups.google.com/group/pypsa). -- To **discuss** with other PyPSA users, organise projects, share news, and get in touch with the community you can use the [discord server](https://discord.com/invite/AnuJBk23FU). -- For **bugs and feature requests**, please use the [PyPSA-Eur Github Issues page](https://github.com/PyPSA/pypsa-eur/issues). - -# Licence - -The code in PyPSA-Eur is released as free software under the -[MIT License](https://opensource.org/licenses/MIT), see [`doc/licenses.rst`](doc/licenses.rst). -However, different licenses and terms of use may apply to the various -input data, see [`doc/data_sources.rst`](doc/data_sources.rst). +# EU-Flex Platform +Open Energy Transition Logo + + +## Overview +The EU-Flex Platform is an implementation of ACER's EU-wide flexibility assessment platform based on PyPSA-Eur. This project develops a comprehensive modeling tool for assessing power system flexibility needs across the European Union, as mandated by Regulation (EU) 2024/1747. + +## Project Scope +This repository covers Phase 1 (Implementation) and Phase 2 (Testing) of the EU-Flex Platform development: + +### Phase 1: Implementation (Dec 2024 - Nov 2025) +- Development of core modeling capabilities using PyPSA +- Implementation of flexibility assessment functionalities +- Integration of various data sources and power system components +- Development of APIs and user interfaces +- Setting up security and operational protocols + +### Phase 2: Testing (Dec 2025 - June 2026) +- Comprehensive testing with complete datasets +- Performance optimization and refinement +- Integration testing with real-world scenarios +- System validation and quality assurance +- Methodology alignment and adjustments + +## Key Features (WIP) +- EU-wide power system modeling +- Flexibility needs assessment across multiple timeframes +- Integration of renewable energy sources +- Analysis of demand-side response and storage options +- Cross-border capacity evaluation +- Stochastic analysis capabilities + +## Repository structure + +* `benchmarks`: will store `snakemake` benchmarks (does not exist initially) +* `config`: configurations used in the study +* `cutouts`: will store raw weather data cutouts from `atlite` (does not exist initially) +* `data`: includes input data that is not produced by any `snakemake` rule +* `doc`: includes all files necessary to build the `readthedocs` documentation of PyPSA-Eur +* `envs`: includes all the `mamba` environment specifications to run the workflow +* `logs`: will store log files (does not exist initially) +* `notebooks`: includes all the `notebooks` used for ad-hoc analysis +* `report`: contains all files necessary to build the report; plots and result files are generated automatically +* `rules`: includes all the `snakemake`rules loaded in the `Snakefile` +* `resources`: will store intermediate results of the workflow which can be picked up again by subsequent rules (does not exist initially) +* `results`: will store the solved PyPSA network data, summary files and plots (does not exist initially) +* `scripts`: includes all the Python scripts executed by the `snakemake` rules to build the model + +## Technology Stack +- [OET/PyPSA-Eur](https://github.com/open-energy-transition/pypsa-eur) used via the [OET's soft-fork strategy](https://open-energy-transition.github.io/handbook/docs/Engineering/SoftForkStrategy). The model is described in the [documentation](https://pypsa-eur.readthedocs.io) and in the paper +[PyPSA-Eur: An Open Optimisation Model of the European Transmission System](https://arxiv.org/abs/1806.01613), 2018, [arXiv:1806.01613](https://arxiv.org/abs/1806.01613). +- [Snakemake](https://snakemake.readthedocs.io) for workflow management + +## Installation and Usage + +### 1. Installation + +Clone the repository: + + git clone https://github.com/open-energy-transition/{{repository}} + +You need [mamba](https://mamba.readthedocs.io/en/latest/) to run the analysis. Users may also prefer to use [micromamba](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html) or [conda](https://docs.conda.io/projects/conda/en/latest/index.html). Using `mamba`, you can create an environment from within you can run it: + + mamba env create -f environment.yaml + +Activate the newly created `{{project_short_name}}` environment: + + mamba activate {{project_short_name}} + +### 2. Run the analysis + + snakemake -call + +This will run all analysis steps to reproduce results and build the report. + +To generate a PDF of the dependency graph of all steps `resources/dag.pdf` run: + + snakemake -c1 dag + +* Open Energy Transition (g)GmbH, Königsallee 52, 95448 Bayreuth, Germany + +## Access and Usage +This platform is being developed for ACER (European Union Agency for the Cooperation of Energy Regulators). Access and usage rights are restricted according to ACER's policies and regulations. diff --git a/Snakefile b/Snakefile index 79e58c573..e0db636fd 100644 --- a/Snakefile +++ b/Snakefile @@ -54,6 +54,7 @@ include: "rules/solve_electricity.smk" include: "rules/postprocess.smk" include: "rules/validate.smk" include: "rules/development.smk" +include: "rules/report.smk" if config["foresight"] == "overnight": diff --git a/config/config.default.yaml b/config/config.default.yaml index 64197dbbe..94c12fc99 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -710,6 +710,7 @@ sector: biogas_upgrading_cc: false conventional_generation: OCGT: gas + keep_existing_capacities: false biomass_to_liquid: false biomass_to_liquid_cc: false electrobiofuels: false diff --git a/doc/configtables/sector.csv b/doc/configtables/sector.csv index ba98adee0..61b7d4726 100644 --- a/doc/configtables/sector.csv +++ b/doc/configtables/sector.csv @@ -178,6 +178,7 @@ biomass_spatial,--,"{true, false}",Add option for resolving biomass demand regio biomass_transport,--,"{true, false}",Add option for transporting solid biomass between nodes biogas_upgrading_cc,--,"{true, false}",Add option to capture CO2 from biomass upgrading conventional_generation,,,Add a more detailed description of conventional carriers. Any power generation requires the consumption of fuel from nodes representing that fuel. +keep_existing_capacities,--,"{true, false}",Keep existing conventional carriers from the power model. Defaults to false. biomass_to_liquid,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil biomass_to_liquid_cc,--,"{true, false}",Add option for transforming solid biomass into liquid fuel with the same properties as oil with carbon capture biosng,--,"{true, false}",Add option for transforming solid biomass into synthesis gas with the same properties as natural gas diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 749b96b0b..52e1032a8 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -479,6 +479,11 @@ PyPSA-Eur 0.12.0 (30th August 2024) * Address various deprecations. +* Allow running the sector model for isolated non-EU28 countries, by filling missing sectoral + data with defaults, average EU values or zeros, if not available. + +* Enable retaining exisiting conventional capacities added in the power only model for sector coupeled applications. + PyPSA-Eur 0.11.0 (25th May 2024) ===================================== diff --git a/report/references.bib b/report/references.bib new file mode 100644 index 000000000..c239cf057 --- /dev/null +++ b/report/references.bib @@ -0,0 +1,8 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +@misc{ Nobody06, + author = "Nobody Jr", + title = "My Article", + year = "2006" } \ No newline at end of file diff --git a/report/report.tex b/report/report.tex new file mode 100644 index 000000000..7988756f6 --- /dev/null +++ b/report/report.tex @@ -0,0 +1,205 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\documentclass[5p]{elsarticle} + +\journal{TBA} + +\bibliographystyle{elsarticle-num} +\biboptions{numbers,sort&compress} + +% format hacks +\usepackage{libertine} +\usepackage{libertinust1math} +% \usepackage{geometry} +% \geometry{ +% top=30mm, +% bottom=35mm, +% } + +\usepackage{amsmath} +\usepackage{bbold} +\usepackage{graphicx} +\usepackage{eurosym} +\usepackage{mathtools} +\usepackage{url} +\usepackage{booktabs} +\usepackage{epstopdf} +\usepackage{xfrac} +\usepackage{tabularx} +\usepackage{bm} +\usepackage{subcaption} +\usepackage{blindtext} +\usepackage{longtable} +\usepackage{multirow} +\usepackage{threeparttable} +\usepackage{pdflscape} +\usepackage[export]{adjustbox} +\usepackage[version=4]{mhchem} +\usepackage[colorlinks]{hyperref} +\usepackage[parfill]{parskip} +\usepackage[nameinlink,sort&compress,capitalise,noabbrev]{cleveref} +\usepackage[leftcaption,raggedright]{sidecap} +\usepackage[prependcaption,textsize=footnotesize]{todonotes} + +\usepackage{siunitx} +\sisetup{ + range-units = single, + per-mode = symbol +} +\DeclareSIUnit\year{a} +\DeclareSIUnit{\tco}{t_{\ce{CO2}}} +\DeclareSIUnit{\sieuro}{\mbox{\euro}} + +\usepackage{lipsum} + +\usepackage[resetlabels,labeled]{multibib} +\newcites{S}{Supplementary References} +\bibliographystyleS{elsarticle-num} + +\graphicspath{ + {../results/graphics/}, +} + +% \usepackage[ +% type={CC}, +% modifier={by}, +% version={4.0}, +% ]{doclicense} + +\newcommand{\abs}[1]{\left|#1\right|} +\newcommand{\norm}[1]{\left\lVert#1\right\rVert} +\newcommand{\set}[1]{\left\{#1\right\} } +\DeclareMathOperator*{\argmin}{\arg\!\min} +\newcommand{\R}{\mathbb{R} } +\newcommand{\B}{\mathbb{B} } +\newcommand{\N}{\mathbb{N} } +\newcommand{\co}{\ce{CO2}~} +\def\el{${}_{\textrm{el}}$} +\def\th{${}_{\textrm{th}}$} +\def\deg{${}^\circ$} + +\begin{document} + + \begin{frontmatter} + + \title{ project name } + + \author[affil]{ name surname } % \corref{correspondingauthor} + \ead{ name.surname@openenergytransition.org } + + \address[affil]{ affiliation } + + \begin{abstract} + \input{sections/abstract.tex} + \end{abstract} + + \begin{keyword} + TODO + \end{keyword} + + % \begin{graphicalabstract} + % \end{graphicalabstract} + + \begin{highlights} + \item A + \item B + \item C + \end{highlights} + +\end{frontmatter} + +% \listoftodos[TODOs] + +% \tableofcontents + +\section{Introduction} +\label{sec:intro} + +\input{sections/introduction.tex} + +\section{Methods} +\label{sec:methods} + +\input{sections/methods.tex} + +\section{Results} +\label{sec:results} + +\input{sections/results.tex} + +\section{Discussion} +\label{sec:discussion} + +\input{sections/discussion.tex} + +\section{Conclusion} +\label{sec:conclusion} + +\input{sections/conclusion.tex} + +\section*{Acknowledgements} + +% XY gratefully acknowledge funding from XY under grant no. 00000. + +\section*{License} +% \doclicenseLongText +% \doclicenseIcon + +\section*{Author Contributions} + +% following https://casrai.org/credit/ + +\textbf{Author XY}: +Conceptualization -- +Data curation -- +Formal Analysis -- +Funding acquisition -- +Investigation -- +Methodology -- +Project administration -- +Resources -- +Software -- +Supervision -- +Validation -- +Visualization -- +Writing - original draft -- +Writing - review \& editing + +\section*{Data and Code Availability} + +A dataset of the model results is available at \href{zenodoTBA}{doi:zenodoTBA}. +The code to reproduce the experiments is available at \href{https://github.com/repository}{github.com/repository}. + +% tidy with https://flamingtempura.github.io/bibtex-tidy/ +\addcontentsline{toc}{section}{References} +\renewcommand{\ttdefault}{\sfdefault} +\bibliography{references} + +% supplementary information + +\newpage + +\makeatletter +\renewcommand \thesection{S\@arabic\c@section} +\renewcommand\thetable{S\@arabic\c@table} +\renewcommand \thefigure{S\@arabic\c@figure} +\makeatother +\renewcommand{\citenumfont}[1]{S#1} +\setcounter{equation}{0} +\setcounter{figure}{0} +\setcounter{table}{0} +\setcounter{section}{0} + + +\section{Supplementary Information} +\label{sec:si} + +\input{sections/supplementary.tex} + +\addcontentsline{toc}{section}{Supplementary References} +\renewcommand{\ttdefault}{\sfdefault} +\bibliographyS{references} + +\end{document} \ No newline at end of file diff --git a/report/sections/.gitkeep b/report/sections/.gitkeep new file mode 100644 index 000000000..071569975 --- /dev/null +++ b/report/sections/.gitkeep @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Open Energy Transition gGmbH +# +# SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/report/sections/abstract.tex b/report/sections/abstract.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/abstract.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/sections/conclusion.tex b/report/sections/conclusion.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/conclusion.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/sections/discussion.tex b/report/sections/discussion.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/discussion.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/sections/introduction.tex b/report/sections/introduction.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/introduction.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/sections/methods.tex b/report/sections/methods.tex new file mode 100644 index 000000000..a3c1cd5e9 --- /dev/null +++ b/report/sections/methods.tex @@ -0,0 +1,6 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] +Blablabla said Nobody ~\cite{Nobody06}. \ No newline at end of file diff --git a/report/sections/results.tex b/report/sections/results.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/results.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/sections/supplementary.tex b/report/sections/supplementary.tex new file mode 100644 index 000000000..bf978e970 --- /dev/null +++ b/report/sections/supplementary.tex @@ -0,0 +1,5 @@ +% SPDX-FileCopyrightText: Open Energy Transition gGmbH +% +% SPDX-License-Identifier: MIT + +\lipsum[1] \ No newline at end of file diff --git a/report/static/.gitkeep b/report/static/.gitkeep new file mode 100644 index 000000000..071569975 --- /dev/null +++ b/report/static/.gitkeep @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Open Energy Transition gGmbH +# +# SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/rules/build_sector.smk b/rules/build_sector.smk index 04f5c3e8e..7fd9c0400 100755 --- a/rules/build_sector.smk +++ b/rules/build_sector.smk @@ -1095,6 +1095,7 @@ rule prepare_sector_network: countries=config_provider("countries"), adjustments=config_provider("adjustments", "sector"), emissions_scope=config_provider("energy", "emissions"), + electricity=config_provider("electricity"), biomass=config_provider("biomass"), RDIR=RDIR, heat_pump_sources=config_provider("sector", "heat_pump_sources"), diff --git a/rules/report.smk b/rules/report.smk new file mode 100755 index 000000000..c79d4f0de --- /dev/null +++ b/rules/report.smk @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Open Energy Transition gGmbH +# +# SPDX-License-Identifier: MIT + +rule report: + message: "Compile report." + params: + fn="report" + input: + tex="report/report.tex", + bib="report/references.bib" + output: "report/report.pdf" + shell: + """ + pdflatex -output-directory report {input.tex} + cd report; bibtex {params.fn}; cd .. + pdflatex -output-directory report {input.tex} + pdflatex -output-directory report {input.tex} + """ diff --git a/scripts/add_electricity.py b/scripts/add_electricity.py index 3dfffac23..82c752d69 100755 --- a/scripts/add_electricity.py +++ b/scripts/add_electricity.py @@ -463,6 +463,7 @@ def set_transmission_costs( def attach_wind_and_solar( n: pypsa.Network, costs: pd.DataFrame, + ppl: pd.DataFrame, input_profiles: str, carriers: list | set, extendable_carriers: list | set, @@ -512,12 +513,20 @@ def attach_wind_and_solar( else: capital_cost = costs.at[car, "capital_cost"] + if not ppl.query("carrier == @car").empty: + caps = ppl.query("carrier == @car").groupby("bus").p_nom.sum() + caps = pd.Series(data = caps, index = ds.indexes["bus"]).fillna(0) + else: + caps = pd.Series(index = ds.indexes["bus"]).fillna(0) + n.add( "Generator", ds.indexes["bus"], " " + car, bus=ds.indexes["bus"], carrier=car, + p_nom = caps, + p_nom_min = caps, p_nom_extendable=car in extendable_carriers["Generator"], p_nom_max=ds["p_nom_max"].to_pandas(), marginal_cost=costs.at[supcar, "marginal_cost"], @@ -1054,6 +1063,7 @@ def attach_stores(n, costs, extendable_carriers): attach_wind_and_solar( n, costs, + ppl, snakemake.input, renewable_carriers, extendable_carriers, diff --git a/scripts/build_district_heat_share.py b/scripts/build_district_heat_share.py index e84819189..3056b688f 100644 --- a/scripts/build_district_heat_share.py +++ b/scripts/build_district_heat_share.py @@ -55,9 +55,11 @@ pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0) year = str(snakemake.params.energy_totals_year) - district_heat_share = pd.read_csv(snakemake.input.district_heat_share, index_col=0)[ - year - ] + district_heat_share = pd.read_csv(snakemake.input.district_heat_share, index_col=0) + if not district_heat_share.empty: + district_heat_share = district_heat_share[year] + else: + district_heat_share = pd.Series(index=pop_layout.index, data=0) # make ct-based share nodal district_heat_share = district_heat_share.reindex(pop_layout.ct).fillna(0) @@ -70,7 +72,8 @@ pop_layout["urban_ct_fraction"] = pop_layout.urban / pop_layout.ct.map(ct_urban.get) # fraction of node that is urban - urban_fraction = pop_layout.urban / pop_layout[["rural", "urban"]].sum(axis=1) + urban_fraction = (pop_layout.urban / pop_layout[["rural", "urban"]].sum(axis=1)).fillna(0) + # maximum potential of urban demand covered by district heating central_fraction = snakemake.config["sector"]["district_heating"]["potential"] @@ -78,7 +81,7 @@ # district heating share at each node dist_fraction_node = ( district_heat_share * pop_layout["urban_ct_fraction"] / pop_layout["fraction"] - ) + ).fillna(0) # if district heating share larger than urban fraction -> set urban # fraction to district heating share diff --git a/scripts/build_energy_totals.py b/scripts/build_energy_totals.py index 6afa6b334..d6aeccaa2 100644 --- a/scripts/build_energy_totals.py +++ b/scripts/build_energy_totals.py @@ -1625,7 +1625,40 @@ def build_heating_efficiencies( ) swiss = build_swiss() - idees = build_idees(idees_countries) + if len(idees_countries) > 0: + idees = build_idees(idees_countries) + else: + # e.g. UA and MD + logger.info(f"No IDEES data available for {countries} and years 2000-2015. Filling with zeros.") + years = range(2000, 2016) + idees = pd.DataFrame( + index=pd.MultiIndex.from_tuples([(country, year) for country in countries for year in years]), + columns=[ + "passenger cars", + "passenger car efficiency", + "total passenger cars", + "total other road passenger", + "total light duty road freight", + "total two-wheel", + "total heavy duty road freight", + "electricity passenger cars", + "electricity other road passenger", + "electricity light duty road freight", + "total rail passenger", + "total rail freight", + "electricity rail passenger", + "electricity rail freight", + "total domestic aviation passenger", + "total domestic aviation freight", + "total international aviation passenger", + "total international aviation freight", + "derived heat residential", + "derived heat services", + "thermal uses residential", + "thermal uses services", + ], + data=0 + ) energy = build_energy_totals(countries, eurostat, swiss, idees) diff --git a/scripts/build_industrial_energy_demand_per_country_today.py b/scripts/build_industrial_energy_demand_per_country_today.py index f011e61f6..18817bfdd 100644 --- a/scripts/build_industrial_energy_demand_per_country_today.py +++ b/scripts/build_industrial_energy_demand_per_country_today.py @@ -65,6 +65,7 @@ import country_converter as coco import pandas as pd +import numpy as np from _helpers import set_scenario_config from tqdm import tqdm @@ -207,12 +208,14 @@ def add_non_eu27_industrial_energy_demand(countries, demand, production): return demand eu27_production = production.loc[countries.intersection(eu27)].sum() + if eu27_production.sum() == 0: + logger.info("EU production is zero. Fallback: Filling non EU28 countries with zeros.") eu27_energy = demand.groupby(level=1).sum() eu27_averages = eu27_energy / eu27_production demand_non_eu27 = pd.concat( {k: v * eu27_averages for k, v in production.loc[non_eu27].iterrows()} - ) + ).fillna(0) return pd.concat([demand, demand_non_eu27]) @@ -292,7 +295,14 @@ def add_coke_ovens(demand, fn, year, factor=0.75): year = params.get("reference_year", 2019) countries = pd.Index(snakemake.params.countries) - demand = industrial_energy_demand(countries.intersection(eu27), year) + if len(countries.intersection(eu27)) > 0: + demand = industrial_energy_demand(countries.intersection(eu27), year) + else: + # e.g. only UA or MD + logger.info( + f"No industrial energy demand available for {countries}. Filling with average values of EU." + ) + demand = industrial_energy_demand(eu27, year) # output in MtMaterial/a production = ( diff --git a/scripts/build_industrial_energy_demand_per_node_today.py b/scripts/build_industrial_energy_demand_per_node_today.py index 0675ce079..2bd51c306 100644 --- a/scripts/build_industrial_energy_demand_per_node_today.py +++ b/scripts/build_industrial_energy_demand_per_node_today.py @@ -72,7 +72,15 @@ def build_nodal_industrial_energy_demand(): buses = keys.index[keys.country == country] mapping = sector_mapping.get(sector, "population") - key = keys.loc[buses, mapping] + try: + key = keys.loc[buses, mapping].fillna(0) + except: + logger.info( + f"No industrial demand available for {mapping}. Filling with zeros." + ) + keys[mapping] = 0 + key = keys.loc[buses, mapping].fillna(0) + demand = industrial_demand[country, sector] outer = pd.DataFrame( diff --git a/scripts/build_industrial_production_per_node.py b/scripts/build_industrial_production_per_node.py index cdc6e1a20..a1263b378 100644 --- a/scripts/build_industrial_production_per_node.py +++ b/scripts/build_industrial_production_per_node.py @@ -75,6 +75,15 @@ def build_nodal_industrial_production(): buses = keys.index[keys.country == country] mapping = sector_mapping.get(sector, "population") + try: + key = keys.loc[buses, mapping].fillna(0) + except: + logger.info( + f"No industrial production available for {mapping}. Filling with zeros." + ) + keys[mapping] = 0 + key = keys.loc[buses, mapping].fillna(0) + key = keys.loc[buses, mapping] nodal_production.loc[buses, sector] = ( industrial_production.at[country, sector] * key diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 7332c5947..395954f74 100755 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1099,12 +1099,13 @@ def annuity_factor(v): return costs -def add_generation(n, costs): +def add_generation(n, costs, existing_capacities=0, existing_efficiencies=None): logger.info("Adding electricity generation") nodes = pop_layout.index - conventionals = options["conventional_generation"] + fallback = {"OCGT": "gas"} + conventionals = options.get("conventional_generation", fallback) for generator, carrier in conventionals.items(): carrier_nodes = vars(spatial)[carrier].nodes @@ -1121,9 +1122,28 @@ def add_generation(n, costs): * costs.at[generator, "VOM"], # NB: VOM is per MWel capital_cost=costs.at[generator, "efficiency"] * costs.at[generator, "fixed"], # NB: fixed cost is per MWel - p_nom_extendable=True, + p_nom_extendable=( + True + if generator + in snakemake.params.electricity.get("extendable_carriers", dict()).get( + "Generator", list() + ) + else False + ), + p_nom=( + existing_capacities[generator] / existing_efficiencies[generator] + if not existing_capacities == 0 else 0 + ), # NB: existing capacities are MWel + p_max_pu = 0.7 if carrier == "uranium" else 1, # be conservative for nuclear (maintance or unplanned shut downs) + p_nom_min=( + existing_capacities[generator] if not existing_capacities == 0 else 0 + ), carrier=generator, - efficiency=costs.at[generator, "efficiency"], + efficiency=( + existing_efficiencies[generator] + if existing_efficiencies is not None + else costs.at[generator, "efficiency"] + ), efficiency2=costs.at[carrier, "CO2 intensity"], lifetime=costs.at[generator, "lifetime"], ) @@ -4595,6 +4615,28 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): cyclic_state_of_charge=True, ) +def get_capacities_from_elec(n, carriers, component): + """ + Gets capacities and efficiencies for {carrier} in n.{component} that were + previously assigned in add_electricity. + """ + component_list = ["generators", "storage_units", "links", "stores"] + component_dict = {name: getattr(n, name) for name in component_list} + e_nom_carriers = ["stores"] + nom_col = {x: "e_nom" if x in e_nom_carriers else "p_nom" for x in component_list} + eff_col = "efficiency" + + capacity_dict = {} + efficiency_dict = {} + for carrier in carriers: + capacity_dict[carrier] = component_dict[component].query("carrier in @carrier")[ + nom_col[component] + ] + efficiency_dict[carrier] = component_dict[component].query( + "carrier in @carrier" + )[eff_col] + + return capacity_dict, efficiency_dict # %% if __name__ == "__main__": @@ -4639,6 +4681,15 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): ) pop_weighted_energy_totals.update(pop_weighted_heat_totals) + if options.get("keep_existing_capacities", False): + existing_capacities, existing_efficiencies = get_capacities_from_elec( + n, + carriers=options.get("conventional_generation").keys(), + component="generators", + ) + else: + existing_capacities, existing_efficiencies = 0, None + landfall_lengths = { tech: settings["landfall_length"] for tech, settings in snakemake.params.renewable.items() @@ -4652,7 +4703,7 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): spatial = define_spatial(pop_layout.index, options) - if snakemake.params.foresight in ["myopic", "perfect"]: + if snakemake.params.foresight in ["overnight", "myopic", "perfect"]: add_lifetime_wind_solar(n, costs) conventional = snakemake.params.conventional_carriers @@ -4663,7 +4714,7 @@ def add_enhanced_geothermal(n, egs_potentials, egs_overlap, costs): add_co2_tracking(n, costs, options) - add_generation(n, costs) + add_generation(n, costs, existing_capacities, existing_efficiencies) add_storage_and_grids(n, costs)