├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── validate.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .reuse └── dep5 ├── .syncignore-receive ├── .syncignore-send ├── CITATION.cff ├── Changelog.md ├── LICENSE.md ├── README.md ├── ariadne-data ├── costs │ ├── mean │ │ ├── costs_2020.csv │ │ ├── costs_2025.csv │ │ ├── costs_2030.csv │ │ ├── costs_2035.csv │ │ ├── costs_2040.csv │ │ ├── costs_2045.csv │ │ └── costs_2050.csv │ ├── optimist │ │ ├── costs_2020.csv │ │ ├── costs_2025.csv │ │ ├── costs_2030.csv │ │ ├── costs_2035.csv │ │ ├── costs_2040.csv │ │ └── costs_2045.csv │ └── pessimist │ │ ├── costs_2020.csv │ │ ├── costs_2025.csv │ │ ├── costs_2030.csv │ │ ├── costs_2035.csv │ │ ├── costs_2040.csv │ │ └── costs_2045.csv ├── costs_2019-modifications.csv ├── costs_2020-modifications.csv ├── costs_2025-modifications.csv ├── costs_2030-modifications.csv ├── costs_2035-modifications.csv ├── costs_2040-modifications.csv ├── costs_2045-modifications.csv ├── costs_2050-modifications.csv ├── offshore_connection_points.csv └── wasserstoff_kernnetz │ └── locations_wasserstoff_kernnetz.csv ├── config ├── config.personal.yaml ├── config.public.yaml ├── config.yaml ├── scenarios.manual.yaml └── scenarios.public.yaml ├── environment.yaml ├── matplotlibrc └── workflow ├── Snakefile ├── envs └── .gitkeep ├── notebooks └── .gitkeep ├── rules └── .gitkeep ├── scripts ├── additional_functionality.py ├── build_egon_data.py ├── build_existing_chp_de.py ├── build_mobility_demand.py ├── build_scenarios.py ├── build_wasserstoff_kernnetz.py ├── cluster_wasserstoff_kernnetz.py ├── export_ariadne_variables.py ├── modify_cost_data.py ├── modify_district_heat_share.py ├── modify_existing_heating.py ├── modify_industry_demand.py ├── modify_prenetwork.py ├── plot_ariadne_report.py ├── plot_ariadne_scenario_comparison.py ├── plot_ariadne_variables.py ├── plot_hydrogen_network_incl_kernnetz.py └── retrieve_ariadne_database.py └── submodules └── .gitkeep /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Apply pre-commit (from #156) 2 | ca1f43c23f1c0936cb9f9e1062854fdf0060c409 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pdf filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before asking for a review for this PR make sure to complete the following checklist: 2 | 3 | - [ ] Workflow with target rule `ariadne_all` completes without errors 4 | - [ ] The logic of `export_ariadne_variables` has been adapted to the changes 5 | - [ ] One or several figures that validate the changes in the PR have been posted as a comment 6 | - [ ] A brief description of the changes has been added to `Changelog.md` 7 | - [ ] The latest `main` has been merged into the PR 8 | - [ ] The config has a new prefix of the format `YYYYMMDDdescriptive_title` 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # dependabot 2 | # Ref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 3 | # ------------------------------------------------------------------------------ 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: daily 10 | groups: 11 | # open a single pull-request for all GitHub actions updates 12 | github-actions: 13 | patterns: 14 | - '*' 15 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validator Bot 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - main 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | run-validation: 15 | name: Run validation 16 | if: github.event.pull_request.head.repo.full_name == github.repository 17 | runs-on: self-hosted 18 | steps: 19 | - uses: lkstrp/pypsa-validator@v0.2.5 20 | with: 21 | step: run-self-hosted-validation 22 | env_file: environment.yaml 23 | snakemake_config: config/config.yaml 24 | pre_command: "build_scenarios -f" 25 | main_command: "ariadne_all" 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | create-report: 30 | name: Create report 31 | if: github.event.pull_request.head.repo.full_name == github.repository 32 | needs: run-validation 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: lkstrp/pypsa-validator@v0.2.5 36 | with: 37 | step: create-comment 38 | snakemake_config: config/config.yaml 39 | # The path starting from prefix in config 40 | # For plot results///.png pass 41 | # /.png 42 | plots: > 43 | " 44 | KN2045_Bal_v4/ariadne/primary_energy.png 45 | KN2045_Bal_v4/ariadne/secondary_energy.png 46 | KN2045_Bal_v4/ariadne/final_energy.png 47 | ariadne_comparison/Trade-Secondary-Energy-Hydrogen-Volume.png 48 | ariadne_comparison/Trade-Secondary-Energy-Liquids-Hydrogen-Volume.png 49 | ariadne_comparison/Capacity-Electricity-Solar.png 50 | " 51 | validator_key: ${{ secrets.VALIDATOR_KEY }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # misc 2 | .vscode 3 | dconf 4 | .snakemake/ 5 | *.ipynb_checkpoints 6 | *.nc 7 | *.log 8 | *.pdf 9 | *.png 10 | 11 | # generated files 12 | results/ 13 | resources/ 14 | data/ 15 | config/scenarios.automated.yaml 16 | 17 | ## Core latex/pdflatex auxiliary files: 18 | *.aux 19 | *.lof 20 | *.log 21 | *.lot 22 | *.fls 23 | *.out 24 | *.toc 25 | *.fmt 26 | *.fot 27 | *.cb 28 | *.cb2 29 | 30 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 31 | *.bbl 32 | *.bcf 33 | *.blg 34 | *-blx.aux 35 | *-blx.bib 36 | *.brf 37 | *.run.xml 38 | 39 | ## Build tool auxiliary files: 40 | *.fdb_latexmk 41 | *.synctex 42 | *.synctex(busy) 43 | *.synctex.gz 44 | *.synctex.gz(busy) 45 | *.pdfsync 46 | 47 | # Python 48 | *.pyc 49 | build/ 50 | dist/ 51 | __pycache__ 52 | *.egg-info 53 | .cache/ 54 | 55 | ## Intermediate documents: 56 | *.dvi 57 | *.xdv 58 | *-converted-to.* 59 | # these rules might exclude image files for figures etc. 60 | # *.ps 61 | # *.eps 62 | # *.pdf 63 | 64 | ## Build tool directories for auxiliary files 65 | # latexrun 66 | latex.out/ 67 | 68 | ## Auxiliary and intermediate files from other packages: 69 | # algorithms 70 | *.alg 71 | *.loa 72 | 73 | # achemso 74 | acs-*.bib 75 | 76 | # amsthm 77 | *.thm 78 | 79 | # beamer 80 | *.nav 81 | *.pre 82 | *.snm 83 | *.vrb 84 | 85 | # changes 86 | *.soc 87 | 88 | # cprotect 89 | *.cpt 90 | 91 | # elsarticle (documentclass of Elsevier journals) 92 | *.spl 93 | 94 | # endnotes 95 | *.ent 96 | 97 | # fixme 98 | *.lox 99 | 100 | # feynmf/feynmp 101 | *.mf 102 | *.mp 103 | *.t[1-9] 104 | *.t[1-9][0-9] 105 | *.tfm 106 | 107 | #(r)(e)ledmac/(r)(e)ledpar 108 | *.end 109 | *.?end 110 | *.[1-9] 111 | *.[1-9][0-9] 112 | *.[1-9][0-9][0-9] 113 | *.[1-9]R 114 | *.[1-9][0-9]R 115 | *.[1-9][0-9][0-9]R 116 | *.eledsec[1-9] 117 | *.eledsec[1-9]R 118 | *.eledsec[1-9][0-9] 119 | *.eledsec[1-9][0-9]R 120 | *.eledsec[1-9][0-9][0-9] 121 | *.eledsec[1-9][0-9][0-9]R 122 | 123 | # glossaries 124 | *.acn 125 | *.acr 126 | *.glg 127 | *.glo 128 | *.gls 129 | *.glsdefs 130 | 131 | # gnuplottex 132 | *-gnuplottex-* 133 | 134 | # gregoriotex 135 | *.gaux 136 | *.gtex 137 | 138 | # htlatex 139 | *.4ct 140 | *.4tc 141 | *.idv 142 | *.lg 143 | *.trc 144 | *.xref 145 | 146 | # hyperref 147 | *.brf 148 | 149 | # knitr 150 | *-concordance.tex 151 | *.tikz 152 | *-tikzDictionary 153 | 154 | # listings 155 | *.lol 156 | 157 | # makeidx 158 | *.idx 159 | *.ilg 160 | *.ind 161 | *.ist 162 | 163 | # minitoc 164 | *.maf 165 | *.mlf 166 | *.mlt 167 | *.mtc[0-9]* 168 | *.slf[0-9]* 169 | *.slt[0-9]* 170 | *.stc[0-9]* 171 | 172 | # minted 173 | _minted* 174 | *.pyg 175 | 176 | # morewrites 177 | *.mw 178 | 179 | # nomencl 180 | *.nlg 181 | *.nlo 182 | *.nls 183 | 184 | # pax 185 | *.pax 186 | 187 | # pdfpcnotes 188 | *.pdfpc 189 | 190 | # sagetex 191 | *.sagetex.sage 192 | *.sagetex.py 193 | *.sagetex.scmd 194 | 195 | # scrwfile 196 | *.wrt 197 | 198 | # sympy 199 | *.sout 200 | *.sympy 201 | sympy-plots-for-*.tex/ 202 | 203 | # pdfcomment 204 | *.upa 205 | *.upb 206 | 207 | # pythontex 208 | *.pytxcode 209 | pythontex-files-*/ 210 | 211 | # thmtools 212 | *.loe 213 | 214 | # TikZ & PGF 215 | *.dpth 216 | *.md5 217 | *.auxlock 218 | 219 | *.tdo 220 | *.lod 221 | 222 | # xmpincl 223 | *.xmpi 224 | 225 | # xindy 226 | *.xdy 227 | 228 | # xypic precompiled matrices 229 | *.xyc 230 | 231 | # endfloat 232 | *.ttt 233 | *.fff 234 | 235 | # Latexian 236 | TSWLatexianTemp* 237 | 238 | ## Editors: 239 | # WinEdt 240 | *.bak 241 | *.sav 242 | 243 | # Texpad 244 | .texpadtmp 245 | 246 | # LyX 247 | *.lyx~ 248 | 249 | # Kile 250 | *.backup 251 | 252 | # KBibTeX 253 | *~[0-9]* 254 | 255 | # auto folder when using emacs and auctex 256 | ./auto/* 257 | *.el 258 | 259 | # expex forward references with \gathertags 260 | *-tags.tex 261 | 262 | # standalone packages 263 | *.sta 264 | 265 | logs 266 | 267 | benchmarks 268 | 269 | cidportal.jrc.ec.europa.eu 270 | 271 | zenodo.org 272 | globalenergymonitor.org/wp-content/uploads/2023/07/Europe-Gas-Tracker-2023-03-v3.xlsx 273 | fnb-gas.de 274 | raw.githubusercontent.com 275 | cutouts 276 | 277 | 278 | # custom local files 279 | local 280 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "workflow/submodules/pypsa-eur"] 2 | path = workflow/submodules/pypsa-eur 3 | url = git@github.com:PyPSA/pypsa-eur.git 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: "^LICENSES" 2 | 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v5.0.0 6 | hooks: 7 | - id: check-merge-conflict 8 | - id: end-of-file-fixer 9 | - id: fix-encoding-pragma 10 | - id: mixed-line-ending 11 | - id: trailing-whitespace 12 | - id: check-added-large-files 13 | args: ["--maxkb=2000"] 14 | 15 | # Sort package imports alphabetically 16 | - repo: https://github.com/PyCQA/isort 17 | rev: 5.13.2 18 | hooks: 19 | - id: isort 20 | args: ["--profile", "black", "--filter-files"] 21 | 22 | # Convert relative imports to absolute imports 23 | - repo: https://github.com/MarcoGorelli/absolufy-imports 24 | rev: v0.3.1 25 | hooks: 26 | - id: absolufy-imports 27 | 28 | # Find common spelling mistakes in comments and docstrings 29 | - repo: https://github.com/codespell-project/codespell 30 | rev: v2.3.0 31 | hooks: 32 | - id: codespell 33 | args: ['--ignore-regex="(\b[A-Z]+\b)"', '--ignore-words-list=fom,appartment,bage,ore,setis,tabacco,berfore,vor,pris'] # Ignore capital case words, e.g. country codes 34 | types_or: [python, rst, markdown] 35 | files: ^(scripts|doc)/ 36 | 37 | # Make docstrings PEP 257 compliant 38 | # Broken for pre-commit<=4.0.0 39 | # See https://github.com/PyCQA/docformatter/issues/293 40 | # - repo: https://github.com/PyCQA/docformatter 41 | # rev: v1.7.5 42 | # hooks: 43 | # - id: docformatter 44 | # args: ["--in-place", "--make-summary-multi-line", "--pre-summary-newline"] 45 | 46 | - repo: https://github.com/keewis/blackdoc 47 | rev: v0.3.9 48 | hooks: 49 | - id: blackdoc 50 | 51 | # Formatting with "black" coding style 52 | - repo: https://github.com/psf/black-pre-commit-mirror 53 | rev: 24.10.0 54 | hooks: 55 | # Format Python files 56 | - id: black 57 | # Format Jupyter Python notebooks 58 | - id: black-jupyter 59 | 60 | # Remove output from Jupyter notebooks 61 | - repo: https://github.com/aflc/pre-commit-jupyter 62 | rev: v1.2.1 63 | hooks: 64 | - id: jupyter-notebook-cleanup 65 | args: ["--remove-kernel-metadata"] 66 | 67 | # Do YAML formatting (before the linter checks it for misses) 68 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 69 | rev: v2.14.0 70 | hooks: 71 | - id: pretty-format-yaml 72 | args: [--autofix, --indent, "2", --preserve-quotes] 73 | 74 | # Format Snakemake rule / workflow files 75 | - repo: https://github.com/snakemake/snakefmt 76 | rev: v0.10.2 77 | hooks: 78 | - id: snakefmt 79 | 80 | # For cleaning jupyter notebooks 81 | - repo: https://github.com/aflc/pre-commit-jupyter 82 | rev: v1.2.1 83 | hooks: 84 | - id: jupyter-notebook-cleanup 85 | exclude: examples/solve-on-remote.ipynb 86 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyPSA/pypsa-ariadne/97b7c25e657bb65341354673334300d678eac973/.reuse/dep5 -------------------------------------------------------------------------------- /.syncignore-receive: -------------------------------------------------------------------------------- 1 | # build files to ignore when receiving from cluster 2 | .snakemake 3 | .git 4 | .pytest_cache 5 | .ipynb_checkpoints 6 | .vscode 7 | .DS_Store 8 | __pycache__ 9 | *.pyc 10 | *.pyo 11 | 12 | # project specific 13 | data 14 | resources 15 | *.nc 16 | ariadne-data 17 | -------------------------------------------------------------------------------- /.syncignore-send: -------------------------------------------------------------------------------- 1 | .snakemake 2 | .git 3 | .pytest_cache 4 | __pycache__ 5 | *.pyc 6 | *.pyo 7 | .vscode 8 | .DS_Store 9 | .ipynb_checkpoints 10 | .vscode 11 | resources 12 | results 13 | benchmarks 14 | logs 15 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "pypsa-ariadne" 3 | title: "Kopernikus-Projekt Ariadne - Gesamtsystemmodell PyPSA-Eur" 4 | repository: https://github.com/account/pypsa-ariadne 5 | version: 0.0.0 6 | doi: "N/A" 7 | date-released: "N/A" 8 | license: MIT 9 | authors: 10 | - family-names: Your surname 11 | given-names: Christoph Tries 12 | orcid: https://orcid.org/0009-0000-2425-0993 13 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | - add option for Offhsore NEP to delay projects by x years (CurPol and KN2045minus_WorstCase are delayed by one year) 3 | - Force onwind south by increasing minimum capacity and decreasing capacity per sqkm 4 | - Adjusting aviation demand (from Aladin) and emission accounting (only domestic aviation for national target) 5 | - Increase HVC_environment_sequestration_fraction from 0.1 to 0.6 6 | - Disallow HVC to air in DE 7 | - Restricting the maximum capacity of CurrentPolicies and minus scenarios to the 'uba Projektionsbericht' 8 | - Restricting Fischer Tropsch capacity addition with config[solving][limit_DE_FT_cap] 9 | - Except for Current Policies force a minimum of 5 GW of electrolysis capacity in Germany 10 | - limit the import/export limit to/from Germany 11 | - adjusting capacity factor of solar to match historic data 12 | - Rely on DEA investment costs for electrolysis 13 | - updated the Kernnetz to use latest data and operate it more flexible 14 | - added Italy with 3 additional nodes 15 | - adapted spatial distribution of district heating demand in Germany according to data from eGo^N project 16 | - add retrofit of gas turbines to H2, and H2 turbines 17 | - unravel gas bus and turn off gas network 18 | - fix the hydrogen import boundary condition 19 | - add primary oil bus and account for refinery emissions 20 | - added Changelog file 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Christoph Tries Your surname 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🟢 Development continues at [PyPSA-DE](https://github.com/PyPSA/pypsa-de) 2 | # 🛑 This repostiory is deprecated 3 | 4 | # Kopernikus-Projekt Ariadne - Gesamtsystemmodell PyPSA-DE 5 | 6 | Dieses Repository enthält das Gesamtsystemmodell PyPSA-DE für das Kopernikus-Projekt Ariadne, basierend auf der Toolbox PyPSA und dem Datensatz PyPSA-Eur. Das Modell bildet Deutschland mit hoher geographischer Auflösung, mit voller Sektorenkopplung und mit Integration in das europäische Energiesystem ab. 7 | 8 | This repository contains the entire scientific project, including data sources and code. The philosophy behind this repository is that no intermediary results are included, but all results are computed from raw data and code. 9 | 10 | ## Clone the repository - including necessary submodules! 11 | 12 | To start you need to clone the [PyPSA-Ariadne repository](https://github.com/PyPSA/pypsa-ariadne/). Since the repository relies on Git Submodules to integrate the PyPSA-Eur dataset as a basis on which to expand, you need to include the `--recurse-submodules` flag in your `git clone` command: 13 | 14 | git clone --recurse-submodules git@github.com:PyPSA/pypsa-ariadne.git 15 | 16 | Alternatively, after having cloned the repository without activating submodules, you can run the two following commands: 17 | 18 | git submodule update --init --recursive 19 | 20 | This command first initializes your local configuration file, second fetches all the data from the project(s) declared as submodule(s) (in this case, PyPSA-Eur) as well as all potential nested submodules, and third checks out the appropriate PyPSA-Eur commit which is defined in the PyPSA-Ariadne repository. 21 | 22 | You can fetch and merge any new commits from the remote of the submodules with the following command: 23 | 24 | git submodule update --remote 25 | 26 | More information on Git Submodules can be found [here](https://git-scm.com/book/en/v2/Git-Tools-Submodules). 27 | 28 | ## Getting ready 29 | 30 | You need conda or [mamba](https://mamba.readthedocs.io/en/latest/) to run the analysis. Using mamba, you can create an environment from within you can run it: 31 | 32 | mamba env create -f environment.yaml 33 | 34 | ## For external users: Use config.public.yaml 35 | 36 | The default workflow configured for this repository assumes access to the internal Ariadne2 database. Users that do not have the required login details can run the analysis based on the data published during the [first phase of the Ariadne project](https://data.ece.iiasa.ac.at/ariadne/). 37 | 38 | This is possible by providing an additional config to the snakemake workflow. For every `snakemake COMMAND` specified in the instructions below, public users should use: 39 | 40 | ``` 41 | snakemake --configfile=config/config.public.yaml COMMAND 42 | ``` 43 | 44 | The additional config file specifies the required database, model, and scenario names for Ariadne1. If public users wish to edit the default scenario specifications, they should change `scenarios.public.yaml` instead of `scenarios.manual.yaml`. More details on using scenarios are given below. 45 | 46 | ## For internal users: Provide login details 47 | 48 | The snakemake rule `retrieve_ariadne_database` logs into the interal Ariadne IIASA Database via the [`pyam`](https://pyam-iamc.readthedocs.io/en/stable/tutorials/iiasa.html) package. The credentials for logging into this database have to be stored locally on your machine with `ixmp4`. To do this activate the project environment and run 49 | 50 | ``` 51 | ixmp4 login 52 | ``` 53 | 54 | You will be prompted to enter your ``. 55 | 56 | Caveat: These credentials are stored on your machine in plain text. 57 | 58 | To switch between internal and public use, the command `ixmp4 logout` may be necessary. 59 | 60 | ## Run the analysis 61 | 62 | Before running any analysis with scenarios, the rule `build_scenarios` must be executed. This will create the file `config/scenarios.automated.yaml` which includes input data and CO2 targets from the IIASA Ariadne database as well as the specifications from the manual scenario file. [This file is specified in the default config.yaml via they key `run:scenarios:manual_file` (by default located at `config/scenarios.manual.yaml`)]. 63 | 64 | snakemake -call build_scenarios -f 65 | 66 | Note that the hierarchy of scenario files is the following: `scenarios.automated.yaml` > (any `explicitly specified --configfiles`) > `config.yaml `> `config.default.yaml `Changes in the file `scenarios.manual.yaml `are only taken into account if the rule `build_scenarios` is executed. 67 | 68 | For the first run, open config.yaml and set 69 | 70 | enable: 71 | retrieve: true # set to false once initial data is retrieved 72 | retrieve_cutout: true # set to false once initial data is retrieved 73 | 74 | and then run from main repository 75 | 76 | snakemake -call 77 | 78 | This will run all analysis steps to reproduce results. 79 | 80 | To generate a PDF of the dependency graph of all steps `build/dag.pdf` run: 81 | 82 | snakemake -c1 --use-conda -f dag 83 | 84 | ## Repo structure 85 | 86 | * `config`: configuration files 87 | * `ariadne-data`: Germany specific data from the Ariadne project 88 | * `workflow`: contains the Snakemake workflow, including the submodule PyPSA-Eur and specific scripts for Germany 89 | * `cutouts`: very large weather data cutouts supplied by atlite library (does not exist initially) 90 | * `data`: place for raw data (does not exist initially) 91 | * `resources`: place for intermediate/processing data for the workflow (does not exist initially) 92 | * `results`: will contain all results (does not exist initially) 93 | 94 | ## Differences to PyPSA-EUR 95 | 96 | - Specific cost assumption for Germany: 97 | - Gas, Oil, Coal prices 98 | - electrolysis and heat-pump costs 99 | - Infrastructure costs according to the Netzentwicklungsplan 23 (NEP23) 100 | - option for pessimstic, mean and optimistic cost development 101 | - Transport and Industry demands as well as heating stock imported from the sectoral models in the Ariadne consortium 102 | - More detailed data on CHPs in Germany 103 | - Option for building the German Wasserstoffkernnetz 104 | - The model has been validated against 2020 electricity data for Germany 105 | - National CO2-Targets according to the Klimaschutzgesetz 106 | - Additional constraints that limit maximum capacity of specific technologies 107 | - Import constraints 108 | - Renewable build out according to the Wind-an-Land, Wind-auf-See and Solarstrategie laws 109 | - A comprehensive reporting module that exports Capacity Expansion, Primary/Secondary/Final Energy, CO2 Emissions per Sector, Trade, Investments, ... 110 | - Plotting functionality to compare different scenarios 111 | 112 | ## License 113 | 114 | The code in this repo is MIT licensed, see `./LICENSE.md`. 115 | -------------------------------------------------------------------------------- /ariadne-data/costs_2019-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,16.0,EUR/MWh_th,Ariadne, 3 | oil,fuel,33.2457,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.7391,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1685,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf 6 | decentral ground-sourced heat pump,investment,2774,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2020-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,11.2,EUR/MWh_th,Ariadne, 3 | oil,fuel,22.1982,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,5.7048,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1685,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf 6 | decentral ground-sourced heat pump,investment,2774,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2025-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,40,EUR/MWh_th,Ariadne, 3 | oil,fuel,32.9876,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,10.6694,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1604,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2682,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2030-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,22.3,EUR/MWh_th,Ariadne, 3 | oil,fuel,38.821,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.2056,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1523,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2589,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2035-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,22.4,EUR/MWh_th,Ariadne, 3 | oil,fuel,38.5629,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.2601,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1483,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2497,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2040-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,22.6,EUR/MWh_th,Ariadne, 3 | oil,fuel,38.3564,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.3036,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1442,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2404,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2045-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,22.8,EUR/MWh_th,Ariadne, 3 | oil,fuel,38.0983,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.3472,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1402,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2312,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/costs_2050-modifications.csv: -------------------------------------------------------------------------------- 1 | technology,parameter,value,unit,source,further description 2 | gas,fuel,22.9,EUR/MWh_th,Ariadne, 3 | oil,fuel,37.8918,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1bbl = 1.6998MWh" 4 | coal,fuel,6.4016,EUR2020/MWh,Ariadne,"$2020 = 0.8775 EUR2020, 1t = 8.06 MWh" 5 | decentral air-sourced heat pump,investment,1362,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 6 | decentral ground-sourced heat pump,investment,2219,EUR2020/kW_th,https://ariadneprojekt.de/media/2024/01/Ariadne-Analyse_HeizkostenEmissionenGebaeude_Januar2024.pdf https://www.enpal.de/waermepumpe/kosten/ https://www.bdew.de/media/documents/BDEW-HKV_Altbau.pdf and cost reduction from DEA 7 | electricity distribution grid,investment,1500,EUR2020/kW,oriented towards JRC-EU-TIMES 8 | HVAC overhead,investment,472,EUR2020/MW/km,NEP2021,"Assuming High Temperature Low Sag Cables with higher Amperage" 9 | HVAC overhead,investment,772,EUR2020/MW/km,NEP2023,"Assuming High Temperature Low Sag Cables with higher Amperage" 10 | offwind-ac-connection-submarine,investment,3786,EUR2020/MW/km,NEP2021,"220kv, 1A, 2 circuits, 3 phases, inflation" 11 | offwind-ac-connection-submarine,investment,2488,EUR2020/MW/km,NEP2023, 12 | offwind-ac-connection-underground,investment,3786,EUR2020/MW/km,NEP2021, 13 | offwind-ac-connection-underground,investment,2488,EUR2020/MW/km,NEP2023, 14 | offwind-ac-station,investment,697,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 15 | offwind-ac-station,investment,722,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 16 | HVDC inverter pair,investment,597015,EUR2020/MW,NEP2021+NEP2023 17 | HVDC overhead,investment,995,EUR2020/MW/km,NEP2021+NEP2023 18 | HVDC underground,investment,3234,EUR2020/MW/km,NEP2021, 19 | HVDC underground,investment,2978,EUR2020/MW/km,NEP2023, 20 | HVDC submarine,investment,3234,EUR2020/MW/km,NEP2021,"set equal to HVDC underground" 21 | HVDC submarine,investment,2978,EUR2020/MW/km,NEP2023,"set equal to HVDC underground" 22 | offwind-dc-connection-submarine,investment,1990,EUR2020/MW/km,NEP2021,"DC 525kv seeseitig" 23 | offwind-dc-connection-submarine,investment,2708,EUR2020/MW/km,NEP2023,"DC 525kv seeseitig" 24 | offwind-dc-connection-underground,investment,3234,EUR2020/MW/km,NEP2021,"DC 525kv landseitig" 25 | offwind-dc-connection-underground,investment,3430,EUR2020/MW/km,NEP2023,"DC 525kv landseitig" 26 | offwind-dc-station,investment,746,EUR2020/kW,NEP2021,"cost of two stations, landseitig and seeseitig" 27 | offwind-dc-station,investment,903,EUR2020/kW,NEP2023,"cost of two stations, landseitig and seeseitig" 28 | -------------------------------------------------------------------------------- /ariadne-data/offshore_connection_points.csv: -------------------------------------------------------------------------------- 1 | Projekt,Nummer,Bezeichnung des Projekts,Netzverknüpfungspunkt (ÜNB),Trassenlänge in km,Übertragungsleistung in MW,Inbetriebnahmejahr,Kommentar,lat,lon 2 | NOR-0-1,,AC-ONAS NOR-0-1 (Riffgat),Emden / Borßum (TenneT),,113,2014.0,NEP2023 Istnetz,53.350195,7.224917 3 | NOR-0-2,,AC-ONAS NOR-0-2 (Nordergründe),Inhausen (TenneT),,111,2017.0,NEP2023 Istnetz,53.620526,8.059785 4 | NOR-2-1,,AC-ONAS (alpha ventus),Hagermarsch (TenneT),,62,2009.0,NEP2023 Istnetz,53.637944,7.298534 5 | NOR-2-2,,DC-ONAS NOR-2-2 (DolWin1),Dörpen / West (TenneT),,800,2015.0,NEP2023 Istnetz,52.982333,7.257574 6 | NOR-2-3,,DC-ONAS NOR-2-3 (DolWin3),Dörpen / West (TenneT),,900,2018.0,NEP2023 Istnetz,52.982333,7.257574 7 | NOR-3-1,,DC-ONAS NOR-3-1 (DolWin2),Dörpen / West (TenneT),,916,2016.0,NEP2023 Istnetz,52.982333,7.257574 8 | NOR-4-1,,DC-ONAS NOR-4-1 (HelWin1),Büttel (TenneT),,576,2015.0,NEP2023 Istnetz,53.917758,9.234576 9 | NOR-4-2,,DC-ONAS NOR-4-2 (HelWin2),Büttel (TenneT),,690,2015.0,NEP2023 Istnetz,53.917758,9.234576 10 | NOR-5-1,,DC-ONAS NOR-5-1 (SylWin1),Büttel (TenneT),,864,2015.0,NEP2023 Istnetz,53.917758,9.234576 11 | NOR-6-1,,DC-ONAS NOR-6-1 (BorWin1),Diele (TenneT),,400,2010.0,NEP2023 Istnetz,53.126873,7.315915 12 | NOR-6-2,,DC-ONAS NOR-6-2 (BorWin2),Diele (TenneT),,800,2015.0,NEP2023 Istnetz,53.126873,7.315915 13 | NOR-8-1,,DC-ONAS NOR-8-1 (BorWin3),Emden / Ost (TenneT),,900,2019.0,NEP2023 Istnetz,53.357128,7.24358 14 | NOR-1-1,,DC-ONAS NOR-1-1 (DolWin5),Emden / Ost (TenneT),132.0,900,2025.0,NEP2023 Startnetz,53.357128,7.24358 15 | NOR-3-2,,DC-ONAS NOR-3-2 (DolWin4),Hanekenfähr (Amprion),196.0,900,2028.0,NEP2023 Startnetz,52.475798,7.307034 16 | NOR-3-3,,DC-ONAS NOR-3-3 (DolWin6),Emden / Ost (TenneT),90.0,900,2023.0,NEP2023 Startnetz,53.357128,7.24358 17 | NOR-6-3,,DC-ONAS NOR-6-3 (BorWin4),Hanekenfähr (Amprion),264.0,900,2028.0,NEP2023 Startnetz,52.475798,7.307034 18 | NOR-7-1,,DC-ONAS NOR-7-1 (BorWin5),Garrel / Ost (TenneT),225.0,900,2025.0,NEP2023 Startnetz,52.945885,8.075682 19 | NOR-7-2,,DC-ONAS NOR-7-2 (BorWin6),Büttel (TenneT),235.0,980,2027.0,NEP2023 Startnetz,53.917758,9.234576 20 | NOR-9-1,M243,HGÜ-Verbindung NOR-9-1 (BalWin1),Wehrendorf (Amprion),363.0,2000,2030.0,Szenario B/C 2045,52.347253,8.307983 21 | NOR-9-2,M236,HGÜ-Verbindung NOR-9-2 (BalWin3),Wilhelmshaven 2 (TenneT),250.0,2000,2031.0,Szenario B/C 2045,53.56214,8.12915 22 | NOR-9-3,M234,HGÜ-Verbindung NOR-9-3 (BalWin4),Unterweser (TenneT),265.0,2000,2029.0,Szenario B/C 2045,53.428708,8.472726 23 | NOR-10-1,M39,HGÜ-Verbindung NOR-10-1 (BalWin2),Westerkappeln (Amprion),371.0,2000,2030.0,Szenario B/C 2045,52.275966,7.887789 24 | NOR-11-1,M233,HGÜ-Verbindung NOR-11-1 (LanWin3),Suchraum Heide (50Hertz),215.0,2000,2030.0,Szenario B/C 2045,54.16277,9.05231 25 | NOR-12-1,M231,HGÜ-Verbindung NOR-12-1 (LanWin1),Unterweser (TenneT),265.0,2000,2030.0,Szenario B/C 2045,53.428708,8.472726 26 | NOR-12-2,M249,HGÜ-Verbindung NOR-12-2 (LanWin2),Suchraum Heide (50Hertz),270.0,2000,2030.0,Szenario B/C 2045,54.16277,9.05231 27 | NOR-11-2,M248,HGÜ-Verbindung NOR-11-2 (LanWin4),Wilhelmshaven 2 (TenneT),225.0,2000,2031.0,Szenario B/C 2045,53.56214,8.12915 28 | NOR-13-1,M242,HGÜ-Verbindung NOR-13-1 (LanWin5),Suchraum Rastede (TenneT),290.0,2000,2031.0,Szenario B/C 2045,53.26056,8.18557 29 | NOR-21-1,M254,HGÜ-Verbindung NOR-21-1 (BorWin7),Niederrhein (Amprion),454.0,2000,2032.0,Szenario B/C 2045,51.64892,6.69122 30 | NOR-14-1,M263,HGÜ-Verbindung NOR-14-1,Blockland / neu (TenneT),390.0,2000,2032.0,Szenario B/C 2045,53.107731,8.818825 31 | NOR-13-2,M262,HGÜ-Verbindung NOR-13-2 (LanWin6),Suchraum Pöschendorf (50Hertz),310.0,2000,2033.0,Szenario B/C 2045,54.036954,9.486766 32 | NOR-15-1,M256,HGÜ-Verbindung NOR-15-1,Kusenhorst (Amprion),550.0,2000,2033.0,Szenario B/C 2045,51.69599,7.06535 33 | NOR-16-2,M264,HGÜ-Verbindung NOR-16-2,Suchraum Pöschendorf (TenneT),365.0,2000,2034.0,Szenario B/C 2045,54.036954,9.486766 34 | NOR-17-1,M246,HGÜ-Verbindung NOR-17-1,Rommerskirchen (Amprion),653.0,2000,2034.0,Szenario B/C 2045,51.008848,6.704552 35 | NOR-16-1,M265,HGÜ-Verbindung NOR-16-1,Suchraum BBS (50Hertz),460.0,2000,2035.0,Szenario B/C 2045,53.5247,10.7854 36 | NOR-18-1,M266,HGÜ-Verbindung NOR-18-1,Wiemersdorf / Hardebek (TenneT),400.0,2000,2035.0,Szenario B/C 2045,53.96076,9.93693 37 | NOR-19-1,M247,HGÜ-Verbindung NOR-19-1,Oberzier (Amprion),807.0,2000,2036.0,Szenario B/C 2045,50.871206,6.458087 38 | NOR-19-3,M257,HGÜ-Verbindung NOR-19-3,Kriftel (Amprion),918.0,2000,2036.0,Szenario B/C 2045,50.097589,8.470316 39 | NOR-19-2,M258,HGÜ-Verbindung NOR-19-2,Suchraum Ried (Amprion),953.0,2000,2037.0,Szenario B/C 2045,49.815643,8.589033 40 | NOR-17-2,M267,HGÜ-Verbindung NOR-17-2,Suchraum Nüttermoor (TenneT),375.0,2000,2037.0,Szenario B/C 2045,53.2582,7.42933 41 | NOR-x-6,M268,HGÜ-Verbindung NOR-x-6,Suchraum BBS (50Hertz),450.0,2000,2038.0,Szenario B/C 2045,53.5247,10.7854 42 | NOR-20-1,M250,HGÜ-Verbindung NOR-20-1,Suchraum Rastede (TenneT),375.0,2000,2039.0,Szenario B/C 2045,53.26056,8.18557 43 | NOR-x-7,M259,HGÜ-Verbindung NOR-x-7,Lippe (Amprion),558.0,2000,2040.0,Szenario B/C 2045,51.64425,7.403782 44 | NOR-x-8,M269,HGÜ-Verbindung NOR-x-8,Suchraum Brunsbüttel (50Hertz),315.0,2000,2041.0,Szenario B/C 2045,53.896482,9.203835 45 | NOR-x-9,M270,HGÜ-Verbindung NOR-x-9,Samtgemeinde Sottrum (TenneT),420.0,2000,2042.0,Szenario B/C 2045,53.114298,9.245839 46 | NOR-x-10,M260,HGÜ-Verbindung NOR-x-10,Rommerskirchen (Amprion),658.0,2000,2043.0,Szenario B/C 2045,51.008848,6.704552 47 | NOR-x-11,M271,HGÜ-Verbindung NOR-x-11,Suchraum Nüttermoor (TenneT),325.0,2000,2044.0,Szenario B/C 2045,53.2582,7.42933 48 | NOR-x-12,M261,HGÜ-Verbindung NOR-x-12,Sechtem (Amprion),684.0,2000,2045.0,Szenario B/C 2045,50.796324,6.972961 49 | OST-1-1,,AC-ONAS OST-1-1 (Ostwind 1),Lubmin (50Hertz),,250,2018.0,NEP2023 Istnetz,54.139899,13.681212 50 | OST-1-2,,AC-ONAS OST-1-2 (Ostwind 1),Lubmin (50Hertz),,250,2019.0,NEP2023 Istnetz,54.139899,13.681212 51 | OST-1-3,,AC-ONAS OST-1-3 (Ostwind 1),Lubmin (50Hertz),,250,2019.0,NEP2023 Istnetz,54.139899,13.681212 52 | OST-3-1,,AC-ONAS OST-3-1 (Kriegers Flak),Bentwisch (50Hertz),,51,2011.0,NEP2023 Istnetz,54.100443,12.216497 53 | OST-3-2,,AC-ONAS OST-3-2 (Kriegers Flak),Bentwisch (50Hertz),,339,2015.0,NEP2023 Istnetz,54.100443,12.216497 54 | OST-1-4,,AC-ONAS OST-1-4 (Ostwind 3),Suchraum Brünzow (50Hertz),102.0,300,2026.0,NEP2023 Startnetz,54.10065,13.60677 55 | OST-2-1,,AC-ONAS OST-2-1 (Ostwind 2),Lubmin (50Hertz),98.0,250,2023.0,NEP2023 Startnetz,54.139899,13.681212 56 | OST-2-2,,AC-ONAS OST-2-2 (Ostwind 2),Lubmin (50Hertz),89.0,250,2023.0,NEP2023 Startnetz,54.139899,13.681212 57 | OST-2-3,,AC-ONAS OST-2-3 (Ostwind 2),Lubmin (50Hertz),89.0,250,2024.0,NEP2023 Startnetz,54.139899,13.681212 58 | OST-2-4,M74,HGÜ-Verbindung OST-2-4 (Ostwind 4),Suchraum Brünzow (50Hertz),109.0,2000,2030.0,Szenario B/C 2045,54.10065,13.60677 59 | OST-x-1,M274,AC-Verbindung OST-x-1,Suchraum Gnewitz (50Hertz),45.0,300,2039.0,Szenario B/C 2045,54.078032,12.512037 60 | OST-x-2,M275,AC-Verbindung OST-x-2,Suchraum Gnewitz (50Hertz),45.0,300,2039.0,Szenario B/C 2045,54.078032,12.512037 61 | OST-x-3,M276,AC-Verbindung OST-x-3,Suchraum Brünzow (50Hertz),80.0,300,2040.0,Szenario B/C 2045,54.10065,13.60677 62 | OST-x-4,M277,AC-Verbindung OST-x-4,Suchraum Brünzow (50Hertz),80.0,300,2040.0,Szenario B/C 2045,54.10065,13.60677 63 | OST-T-1,M85,AC-Verbindung OST-T-1 (Testfeld),Suchraum Broderstorf (50Hertz),50.0,300,2045.0,Szenario B/C 2045,54.08179,12.26445 64 | -------------------------------------------------------------------------------- /ariadne-data/wasserstoff_kernnetz/locations_wasserstoff_kernnetz.csv: -------------------------------------------------------------------------------- 1 | ,location,state,point 2 | 0,Brunsbüttel,Schleswig-Holstein,POINT (9.1395423 53.8972549) 3 | 1,Ahlten,Niedersachsen,POINT (9.9119656 52.3650694) 4 | 2,Balve-Eisborn,Nordrhein-Westfalen,POINT (7.8838676 51.3870558) 5 | 3,Reutles,Bayern,POINT (11.0246344 49.5343175) 6 | 4,Hochheim,Hessen,POINT (8.3514488 50.0145517) 7 | 5,Legden,Nordrhein-Westfalen,POINT (7.099754098013676 52.03269789265483) 8 | 6,Kulkwitz,Sachsen,POINT (12.2324261 51.283959) 9 | 7,Marl,Nordrhein-Westfalen,POINT (7.0829054 51.6485843) 10 | 8,Mühlberg,Sachsen,POINT (13.1725218 51.5046897) 11 | 9,Klein Offenseth,Schleswig-Holstein,POINT (9.683364 53.7847143) 12 | 10,Hemmingstedt,Schleswig-Holstein,POINT (9.08263 54.1506435) 13 | 11,Huntorf,Niedersachsen,POINT (8.3897014 53.1956997) 14 | 12,Sandkrug,Niedersachsen,POINT (8.257391972093515 53.0538793739347) 15 | 13,Wiederitzsch,Sachsen,POINT (12.374251 51.3941234) 16 | 14,Finsing,Bayern,POINT (11.8253553 48.2167439) 17 | 15,Ismaning Nord,Bayern,POINT (11.6608879 48.2110287) 18 | 16,Seyweiler,Saarland,POINT (7.2691502 49.161041) 19 | 17,Carling,Saarland,POINT (6.713267207127634 49.16738919353264) 20 | 18,Fürstenhausen,Saarland,POINT (6.8654184 49.2431689) 21 | 19,Leidingen,Saarland,POINT (6.6109318 49.3086764) 22 | 20,Perl,Saarland,POINT (6.3874514 49.4735915) 23 | 21,March,Baden-Württemberg,POINT (7.7790038 48.0578573) 24 | 22,Forchheim,Bayern,POINT (11.0595749 49.7187319) 25 | 23,Lengthal,Bayern,POINT (12.805992 48.1818627) 26 | 24,Münchsmünster,Bayern,POINT (11.6864502 48.7630342) 27 | 25,Schmidhausen,Bayern,POINT (11.8304698 48.4235449) 28 | 26,Schnaitsee,Bayern,POINT (12.3686999 48.0702334) 29 | 27,Irsching (Menning),Bayern,POINT (11.5730893 48.7574014) 30 | 28,Mailing,Bayern,POINT (11.478937018588255 48.774092800000005) 31 | 29,Zöllnitz,Sachsen-Anhalt,POINT (11.7003344 52.0089065) 32 | 30,Lubmin,Mecklenburg-Vorpommern,POINT (13.612798 54.1318409) 33 | 31,Uckermark,Brandenburg,POINT (13.915135050326025 53.24526305) 34 | 32,Bobbau,Sachsen-Anhalt,POINT (12.269345975889912 51.69045938775995) 35 | 33,Radeland,Brandenburg,POINT (13.5537371 52.0630691) 36 | 34,Wolfsbehringen,Thüringen,POINT (10.4890767 51.0066759) 37 | 35,Stadtroda,Thüringen,POINT (11.7263639 50.8582666) 38 | 36,Rückersdorf,Thüringen,POINT (12.21941992347776 50.822251899358236) 39 | 37,Dürrengleina,Thüringen,POINT (11.5378974 50.8571407) 40 | 38,Reckrod,Hessen,POINT (9.795001 50.7751551) 41 | 39,Zethau,Sachsen,POINT (13.3837749 50.7773525) 42 | 40,Wilhelmshaven,Niedersachsen,POINT (8.106301 53.5278793) 43 | 41,Sande,Niedersachsen,POINT (8.0144916 53.5027182) 44 | 42,Oude Statenzijl,Niedersachsen,POINT (7.205108658430258 53.20183834422634) 45 | 43,Folmhusen,Niedersachsen,POINT (7.4794643 53.1730626) 46 | 44,Ganderkesee,Niedersachsen,POINT (8.5451469 53.0344984) 47 | 45,Dötlingen,Niedersachsen,POINT (8.3820642 52.9356656) 48 | 46,Visbeck,Niedersachsen,POINT (8.310468203836264 52.834518912466216) 49 | 47,Achim,Niedersachsen,POINT (9.039649 53.0096048) 50 | 48,Heidenau,Niedersachsen,POINT (9.6531686 53.3139784) 51 | 49,Fockbek,Schleswig-Holstein,POINT (9.6067007 54.3065487) 52 | 50,Eckel,Niedersachsen,POINT (9.9233342 53.3512004) 53 | 51,Luttum,Niedersachsen,POINT (9.2992445 52.8957679) 54 | 52,Kolshorn,Niedersachsen,POINT (9.940178157648713 52.41249285000001) 55 | 53,Quarnstedt,Schleswig-Holstein,POINT (9.7866367 53.954381) 56 | 54,Schepsdorf,Niedersachsen,POINT (7.2881111 52.5092543) 57 | 55,Frenswegen,Niedersachsen,POINT (7.0377193 52.4547637) 58 | 56,Rehden,Niedersachsen,POINT (8.476178919627396 52.60675277527164) 59 | 57,Voigtei,Niedersachsen,POINT (8.9226462 52.6081821) 60 | 58,Kohlshorn,Niedersachsen,POINT (9.940178157648713 52.41249285000001) 61 | 59,Reiningen,Niedersachsen,POINT (8.374879149975513 52.50849502371421) 62 | 60,Lehringen,Niedersachsen,POINT (9.4010201 52.8825054) 63 | 61,Weser,Niedersachsen,POINT (9.2181918 52.6410849) 64 | 62,Vinnhorst,Niedersachsen,POINT (9.7032801 52.4222536) 65 | 63,Misburg,Niedersachsen,POINT (9.8612306 52.3735637) 66 | 64,Gersten,Niedersachsen,POINT (7.4953102 52.5709728) 67 | 65,Emsbüren,Niedersachsen,POINT (7.2959699 52.3927616) 68 | 66,Vreden,Nordrhein-Westfalen,POINT (6.8236481 52.035862) 69 | 67,Gescher,Nordrhein-Westfalen,POINT (7.0038803 51.956874) 70 | 68,Wettringen,Nordrhein-Westfalen,POINT (7.3218939 52.2103897) 71 | 69,Albachten,Nordrhein-Westfalen,POINT (7.5273089 51.9218529) 72 | 70,Ascheberg,Nordrhein-Westfalen,POINT (7.6196825 51.7886689) 73 | 71,Werne,Nordrhein-Westfalen,POINT (7.6355052 51.66268) 74 | 72,Paffrath,Nordrhein-Westfalen,POINT (7.1000318 50.9994265) 75 | 73,Niederkassel,Nordrhein-Westfalen,POINT (7.0351942 50.8137102) 76 | 74,Birlinghoven,Nordrhein-Westfalen,POINT (7.2241731 50.7479113) 77 | 75,Rüsselsheim,Hessen,POINT (8.4138251 49.991701) 78 | 76,Westhofen,Nordrhein-Westfalen,POINT (7.5334109 51.4249817) 79 | 77,Medelsheim,Saarland,POINT (7.2672779 49.1444166) 80 | 78,Mittelbrunn,Rheinland-Pfalz,POINT (7.549097 49.3708476) 81 | 79,Gernsheim,Hessen,POINT (8.4861853 49.7505186) 82 | 80,Rimpar,Bayern,POINT (9.95903408726619 49.8530509) 83 | 81,Rothenstadt,Bayern,POINT (12.1396079 49.6338326) 84 | 82,Bad Bentheim,Niedersachsen,POINT (7.1605921 52.3024786) 85 | 83,Ledgen,Nordrhein-Westfalen,POINT (7.099754098013676 52.03269789265483) 86 | 84,St. Hubert,Nordrhein-Westfalen,POINT (6.4524179 51.382662) 87 | 85,Glehn,Nordrhein-Westfalen,POINT (6.5766765 51.1650012) 88 | 86,Voigtslach,Nordrhein-Westfalen,POINT (6.934751 51.0749889) 89 | 87,Ketzin,Brandenburg,POINT (12.8443972 52.4769264) 90 | 88,Buchholz,Brandenburg,POINT (12.929212986885771 52.15737808332214) 91 | 89,Bobbau,Sachsen,POINT (12.269345975889912 51.69045938775995) 92 | 90,Schkeuditz,Sachsen,POINT (12.2216292 51.3963509) 93 | 91,Lüptitz,Sachsen,POINT (12.7716385 51.3933612) 94 | 92,Rüdersdorf,Brandenburg,POINT (13.784527 52.471293) 95 | 93,Vogelsdorf,Brandenburg,POINT (13.7601248 52.5076938) 96 | 94,Wefensleben,Sachsen-Anhalt,POINT (11.15557835653467 52.176005656180244) 97 | 95,Wedringen,Sachsen-Anhalt,POINT (11.4654553 52.2727122) 98 | 96,Bernburg Dröbel,Sachsen-Anhalt,POINT (11.7768217 51.7989678) 99 | 97,Bernburg,Sachsen-Anhalt,POINT (11.7391606 51.7930788) 100 | 98,Bad Lauchstädt,Sachsen-Anhalt,POINT (11.869106908389433 51.38797498313352) 101 | 99,Milzau,Sachsen-Anhalt,POINT (11.8989296 51.3757611) 102 | 100,Leuna,Sachsen-Anhalt,POINT (12.0195081 51.3233638) 103 | 101,Cavertitz,Sachsen,POINT (13.1301296 51.3869974) 104 | 102,Kleinziethen,Brandenburg,POINT (13.4452219 52.3802493) 105 | 103,Heidelberg,Baden-Württemberg,POINT (8.694724 49.4093582) 106 | 104,Heilbronn,Baden-Württemberg,POINT (9.218655 49.142291) 107 | 105,Löchgau,Baden-Württemberg,POINT (9.1053914 49.0024253) 108 | 106,Bad Krozingen,Baden-Württemberg,POINT (7.7033313 47.9118288) 109 | 107,Hittistetten,Baden-Württemberg,POINT (10.09644829589717 48.32667870548472) 110 | 108,Elten,Nordrhein-Westfalen,POINT (6.1620438 51.8731461) 111 | 109,Uedener Bruch,Nordrhein-Westfalen,POINT (6.2940229 51.6682254) 112 | 110,Gescher Süd,Nordrhein-Westfalen,POINT (7.027887397926482 51.94277415) 113 | 111,Amelsbüren,Nordrhein-Westfalen,POINT (7.6059771 51.8833893) 114 | 112,Möllen,Nordrhein-Westfalen,POINT (6.6942745 51.5809193) 115 | 113,Vlieghuis,Nordrhein-Westfalen,POINT (6.8382504272201095 52.66036497820981) 116 | 114,Kalle,Nordrhein-Westfalen,POINT (6.921180663621839 52.573992586428425) 117 | 115,Wallach,Nordrhein-Westfalen,POINT (6.5711307 51.5956853) 118 | 116,Bissingen,Bayern,POINT (10.6158383 48.7177493) 119 | 117,Kötz,Bayern,POINT (10.289760282067878 48.40444315) 120 | 118,Niederhohndorf,Thüringen,POINT (12.466430165766688 50.7532612203904) 121 | 119,Waidhaus,Bayern,POINT (12.4955813 49.6428282) 122 | 120,Arzberg,Bayern,POINT (12.1859087 50.0567356) 123 | 121,Rostock,Mecklenburg-Vorpommern,POINT (12.1400211 54.0886707) 124 | 122,SEN-1,AWZ D Nordsee,POINT (6.5 55) 125 | 123,Helgoland,AWZ D Nordsee,POINT (7.882663327316698 54.183393795580166) 126 | 124,AWZ,AWZ D Ostsee,POINT (14.220711180456643 54.429208831326804) 127 | 125,Edesbüttel,Niedersachsen,POINT (10.6252392 52.4053764) 128 | 126,Lampertheim,Hessen,POINT (8.4093474 49.6239449) 129 | 127,Ludwigshafen,Baden-Württemberg,POINT (8.444314472678961 49.477207809634784) 130 | 128,Emden Ost,Niedersachsen,POINT (7.208170859556342 53.34411255) 131 | 129,Bunde,Niedersachsen,POINT (7.2695448 53.184017) 132 | 130,Rastede,Niedersachsen,POINT (8.201904 53.2467081) 133 | 131,Elsfleth,Niedersachsen,POINT (8.4571037 53.2357029) 134 | 132,Heist,Schleswig-Holstein,POINT (9.6664509 53.6528424) 135 | 133,Ellund,Schleswig-Holstein,POINT (9.3224522 54.7865236) 136 | 134,Peine,Niedersachsen,POINT (10.2349489 52.3217849) 137 | 135,Barßel,Niedersachsen,POINT (7.7434171 53.1699986) 138 | 136,Wardenburg,Niedersachsen,POINT (8.1945038 53.0654681) 139 | 137,Egenstedt,Niedersachsen,POINT (9.9934217 52.1010937) 140 | 138,Hanekenfähr,Niedersachsen,POINT (7.3039222 52.4697713) 141 | 139,Wilhelmshaven Nord,Niedersachsen,POINT (8.102611826217313 53.57990075) 142 | 140,Emden West,Niedersachsen,POINT (7.17328769741499 53.35350955) 143 | 141,Bottrop,Nordrhein-Westfalen,POINT (6.929204 51.521581) 144 | 142,Eynatten,Nordrhein-Westfalen,POINT (6.083339457526605 50.69260916361823) 145 | 143,Wiesbaden,Hessen,POINT (8.2416556 50.0820384) 146 | 144,Schillig,Niedersachsen,POINT (8.0235529 53.7033668) 147 | 145,Dykhausen,Niedersachsen,POINT (7.9665652 53.5031607) 148 | 146,Heek,Nordrhein-Westfalen,POINT (7.1011759 52.1242301) 149 | 147,Dorsten,Nordrhein-Westfalen,POINT (6.9647431 51.6604071) 150 | 148,Hamborn,Nordrhein-Westfalen,POINT (6.772532547024161 51.5041215) 151 | 149,Krefeld,Nordrhein-Westfalen,POINT (6.5623343 51.3331205) 152 | 150,Neumühl,Nordrhein-Westfalen,POINT (6.7973866 51.4902985) 153 | 151,Dannenreich,Brandenburg,POINT (13.7489708 52.3146936) 154 | 152,Havellandkanal,Brandenburg,POINT (13.2461296 52.8455492) 155 | 153,Eisenhüttenstadt,Brandenburg,POINT (14.6294413 52.1448863) 156 | 154,Gosda,Brandenburg,POINT (14.6360307 51.6364028) 157 | 155,Salzgitter,Sachsen-Anhalt,POINT (10.386847343138689 52.13861418123843) 158 | 156,Halle,Sachsen-Anhalt,POINT (11.9705452 51.4825041) 159 | 157,Nempitz,Sachsen-Anhalt,POINT (12.1478216 51.291215) 160 | 158,Kienbaum,Brandenburg,POINT (13.9571532 52.4554736) 161 | 159,Glasewitz,Mecklenburg-Vorpommern,POINT (12.2823709 53.8194539) 162 | 160,Pritzwalk,Brandenburg,POINT (12.1761903 53.1492896) 163 | 161,Biesenbrow,Brandenburg,POINT (14.0155881 53.1191146) 164 | 162,Schwedt,Brandenburg,POINT (14.2840858 53.0586366) 165 | 163,Brusendorf,Brandenburg,POINT (13.509532 52.3093902) 166 | 164,Böhlen,Sachsen,POINT (12.3859676 51.2027284) 167 | 165,Bad Dürrenberg,Sachsen-Anhalt,POINT (12.0665175 51.2998551) 168 | 166,Coswig,Sachsen,POINT (13.5783983 51.1267305) 169 | 167,Uhrsleben,Sachsen-Anhalt,POINT (11.264415698864283 52.1991685) 170 | 168,Lampertheim,Baden-Württemberg,POINT (8.5109914 49.5260921) 171 | 169,Altbach,Baden-Württemberg,POINT (9.3807848 48.7244372) 172 | 170,Fessenheim,Baden-Württemberg,POINT (7.5352027843079 47.91300212650956) 173 | 171,Oberaußem,Nordrhein-Westfalen,POINT (6.6794102 50.9735806) 174 | 172,Kalscheuren,Nordrhein-Westfalen,POINT (6.9074954 50.8753279) 175 | 173,Dormagen,Nordrhein-Westfalen,POINT (6.8407931 51.0941656) 176 | 174,Recklinghausen,Nordrhein-Westfalen,POINT (7.1978546 51.6143815) 177 | 175,Rinkerode,Nordrhein-Westfalen,POINT (7.6847518 51.8457359) 178 | 176,Emsbüren,Nordrhein-Westfalen,POINT (7.5543751 51.4789205) 179 | 177,Salzgitter Hallendorf,Niedersachsen,POINT (10.3775522 52.1533277) 180 | 178,Arnsberg-Bruchhausen,Nordrhein-Westfalen,POINT (8.0252464 51.4263456) 181 | 179,Nürnberg,Bayern,POINT (11.077298 49.453872) 182 | 180,Mainz-Kastel,Hessen,POINT (8.2844378 50.0083449) 183 | 181,Lössnig,Sachsen,POINT (12.3959513 51.2992474) 184 | 182,Gelsenkirchen,Nordrhein-Westfalen,POINT (7.0960124 51.5110321) 185 | 183,Röderau,Sachsen,POINT (13.4644419 51.3879818) 186 | 184,Ascheberg (Holstein),Schleswig-Holstein,POINT (10.3423409 54.1483357) 187 | 185,Oldenburg,Niedersachsen,POINT (8.2146017 53.1389753) 188 | 186,Bramsche,Niedersachsen,POINT (7.9749336 52.4109547) 189 | 187,Sixdorf,Sachsen-Anhalt,POINT (11.8334655 51.7331641) 190 | 188,Dillingen,Saarland,POINT (6.7243197 49.3552721) 191 | 189,Saarbrücken,Saarland,POINT (6.996379 49.234362) 192 | 190,Besch,Saarland,POINT (6.3740372 49.5042645) 193 | 191,Freiburg,Baden-Württemberg,POINT (7.8494005 47.9960901) 194 | 192,Burgkirchen,Bayern,POINT (12.7307847 48.1676884) 195 | 193,Moosburg,Bayern,POINT (11.9333 48.4667) 196 | 194,Kösching,Bayern,POINT (11.4971139 48.8104641) 197 | 195,Bad Lauchstädt,Thüringen,POINT (11.869106908389433 51.38797498313352) 198 | 196,Deutschneudorf,Sachsen,POINT (13.4614048 50.6049366) 199 | 197,Jemgum,Niedersachsen,POINT (7.3834995 53.2667955) 200 | 198,Nüttermoor,Niedersachsen,POINT (7.4360347 53.2625382) 201 | 199,Bremen,Niedersachsen,POINT (8.795818388451732 53.077669699449594) 202 | 200,Lemförde,Niedersachsen,POINT (8.3738085 52.4647071) 203 | 201,Elbe-Süd,Niedersachsen,POINT (9.608042769377906 53.57422954537108) 204 | 202,Leversen,Niedersachsen,POINT (9.8959115 53.4071494) 205 | 203,Lingen,Niedersachsen,POINT (7.316584 52.5224659) 206 | 204,Georgsmarienhütte,Niedersachsen,POINT (8.05 52.2) 207 | 205,Rheden,Niedersachsen,POINT (8.476178919627396 52.60675277527164) 208 | 206,Ummeln,Nordrhein-Westfalen,POINT (8.4599606 51.9694347) 209 | 207,Wesseling,Nordrhein-Westfalen,POINT (6.9810852 50.8247166) 210 | 208,Herdecke,Nordrhein-Westfalen,POINT (7.4330062 51.4001119) 211 | 209,Piesteritz,Sachsen-Anhalt,POINT (12.5969035 51.8678903) 212 | 210,Großkugel,Sachsen-Anhalt,POINT (12.151584743366769 51.4166927585755) 213 | 211,Blumberg,Brandenburg,POINT (13.6015561 52.607472) 214 | 212,Glöthe,Sachsen-Anhalt,POINT (11.6734539 51.9100444) 215 | 213,Preußlitz,Sachsen-Anhalt,POINT (11.8119764 51.7331789) 216 | 214,Großkugel,Sachsen,POINT (12.151584743366769 51.4166927585755) 217 | 215,Osdorfer Straße,Brandenburg,POINT (13.2711292 52.4003307) 218 | 216,Lindau,Baden-Württemberg,POINT (9.690886766574819 47.55387858107057) 219 | 217,Wardt,Nordrhein-Westfalen,POINT (6.4371478 51.688944) 220 | 218,Ochtrup,Nordrhein-Westfalen,POINT (7.188806 52.2102308) 221 | 219,Xanten,Nordrhein-Westfalen,POINT (6.4543203 51.661519) 222 | 220,Bergheim,Nordrhein-Westfalen,POINT (6.6410004 50.9540457) 223 | 221,Wertingen,Bayern,POINT (10.6808075 48.5601209) 224 | 222,Hittistetten (Senden),Bayern,POINT (10.0955295 48.3278287) 225 | 223,Rückersdorf,Sachsen,POINT (12.21941992347776 50.822251899358236) 226 | 224,Wrangelsburg,Mecklenburg-Vorpommern,POINT (13.5944793 54.016375) 227 | 225,Ludwighafen,Rheinland-Pfalz,POINT (8.4381568 49.4704113) 228 | 226,Karlsruhe,Rheinland-Pfalz,POINT (8.29684904101257 49.056995900000004) 229 | 227,Westerstede,Niedersachsen,POINT (7.9273367 53.2575197) 230 | 228,Bremerhaven,Niedersachsen,POINT (8.5979065 53.6040332) 231 | 229,Niebüll,Schleswig-Holstein,POINT (8.8255846 54.7869088) 232 | 230,Hallendorf,Niedersachsen,POINT (10.3775522 52.1533277) 233 | 231,Wilhelmshaven Süd,Niedersachsen,POINT (8.1153355 53.5187847) 234 | 232,Wettringen,Niedersachsen,POINT (9.075962 52.8398531) 235 | 233,Gladbeck,Nordrhein-Westfalen,POINT (6.9877343 51.5718665) 236 | 234,Ludwigshafen,Hessen,POINT (8.444314472678961 49.477207809634784) 237 | 235,Frankfurt,Hessen,POINT (8.6820917 50.1106444) 238 | 236,Epe,Nordrhein-Westfalen,POINT (7.0283370514193555 52.17018885) 239 | 237,Hamm,Nordrhein-Westfalen,POINT (7.815197 51.6804093) 240 | 238,Falkenhöh,Brandenburg,POINT (13.141735 52.5695382) 241 | 239,Spreetal,Brandenburg,POINT (13.9011346 52.3971347) 242 | 240,Wefensleben,Niedersachsen,POINT (11.15557835653467 52.176005656180244) 243 | 241,Cörmigk,Sachsen-Anhalt,POINT (11.8413647 51.7265394) 244 | 242,Brandenburg a.d. Havel,Brandenburg,POINT (12.5497933 52.4108261) 245 | 243,Mescherin,Brandenburg,POINT (14.425521097275155 53.25520185) 246 | 244,Borna,Sachsen,POINT (12.4999055 51.1241649) 247 | 245,Dresden,Sachsen,POINT (13.7381437 51.0493286) 248 | 246,Bissingen,Baden-Württemberg,POINT (10.6158383 48.7177493) 249 | 247,Averbruch,Nordrhein-Westfalen,POINT (6.74367564842036 51.54893875) 250 | 248,Weiden,Nordrhein-Westfalen,POINT (7.2411269 51.0625299) 251 | 249,Merkenich,Nordrhein-Westfalen,POINT (6.9579743 51.0258343) 252 | 250,Leverkusen,Nordrhein-Westfalen,POINT (6.9881194 51.0324743) 253 | 251,Uentrop,Nordrhein-Westfalen,POINT (7.940585 51.6926159) 254 | 252,Eisenach,Thüringen,POINT (10.3193565 50.9747134) 255 | 253,Rosengarten (Sottorf),Niedersachsen,POINT (9.8948391 53.4209436) 256 | 254,Bordesholm,Schleswig-Holstein,POINT (10.0221102 54.1762117) 257 | 255,Flintbek/Boksee,Schleswig-Holstein,POINT (10.0837662 54.2453385) 258 | 256,KW Schilling,Niedersachsen,POINT (9.7305278 53.2531242) 259 | 257,Waldshut-Tiengen,Baden-Württemberg,POINT (8.2408579 47.6281754) 260 | 258,Blumberg,Berlin,POINT (13.5659662 52.526965) 261 | 259,Berlin- Lichterfelde,Berlin,POINT (13.3138645 52.437293) 262 | 260,Berlin-Wilmersdorf,Berlin,POINT (13.3203298 52.4871152) 263 | 261,Profen,Sachsen-Anhalt,POINT (12.2164154 51.1273522) 264 | 262,AQD Offshore SEN 1,AWZ D Nordsee,POINT (6.5 55) 265 | 263,AQD Offshore 1,AWZ D Nordsee,POINT (7.882663327316698 54.183393795580166) 266 | 264,Rysum,Niedersachsen,POINT (7.0329435 53.3793437) 267 | 265,Weisweiler,Nordrhein-Westfalen,POINT (6.3163491 50.8280995) 268 | 266,Heide,Nordrhein-Westfalen,POINT (6.9340589 51.8265212) 269 | 267,Friedersdorf,Brandenburg,POINT (13.7908176 52.2928401) 270 | 268,Schönermark,Brandenburg,POINT (13.1227562 53.0067992) 271 | 269,Werben,Brandenburg,POINT (14.1865661 51.813468) 272 | 270,Räpitz,Sachsen-Anhalt,POINT (11.7003344 52.0089065) 273 | 271,Kleindalzig,Sachsen-Anhalt,POINT (11.7003344 52.0089065) 274 | 272,Ranzenbüttel,Niedersachsen,POINT (8.4999473 53.1930356) 275 | 273,Schlootdamm/Steinfeld,Niedersachsen,POINT (8.2902173 52.6063263) 276 | 274,Apollensdorf,Sachsen-Anhalt,POINT (12.55671626284358 51.88540635) 277 | 275,Plaußig,Sachsen,POINT (12.4549089 51.3922413) 278 | 276,Hennickendorf,Brandenburg,POINT (13.8476964 52.5067503) 279 | 277,Leuna Süd,Sachsen-Anhalt,POINT (12.0066871 51.3065098) 280 | 278,Hüthum,Nordrhein-Westfalen,POINT (6.2050212 51.8487348) 281 | 279,Abzweig Stade,Niedersachsen,POINT (9.4316422 53.6268051) 282 | 280,Moorburg,Hamburg,POINT (9.9374768 53.4878001) 283 | 281,Kiel/Gaarden,Schleswig-Holstein,POINT (10.136528 54.305393) 284 | 282,Stade AOS,Niedersachsen,POINT (9.475438 53.599794) 285 | 283,Grenzach,Baden-Württemberg,POINT (7.6545614 47.5553231) 286 | 284,Berlin-Mitte,Berlin,POINT (13.393597526084616 52.518579349999996) 287 | 285,Berlin-Charlottenburg,Berlin,POINT (13.3096834 52.515747) 288 | 286,Zorbau,Sachsen-Anhalt,POINT (12.029826123875736 51.1913242) 289 | 287,Gröditz,Sachsen,POINT (13.4473168 51.4121605) 290 | 288,Unterföhring,Bayern,POINT (11.6449251 48.1950385) 291 | 289,AQD Anlandung,Niedersachsen,POINT (8.0235529 53.7033668) 292 | 290,Emden,Niedersachsen,POINT (7.2058304 53.3670541) 293 | 291,Dinslaken,Nordrhein-Westfalen,POINT (6.7345106 51.5623618) 294 | 292,Fürstenberg (PL),Brandenburg,POINT (14.6614876 52.1449997) 295 | 293,Greifenhagen,Brandenburg,POINT (14.4348806 53.2545323) 296 | 294,Praest,Nordrhein-Westfalen,POINT (6.3400098 51.8221788) 297 | 295,Götzdorf,Niedersachsen,POINT (9.4694842 53.6363197) 298 | -------------------------------------------------------------------------------- /config/config.personal.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: : 2017-2023 The PyPSA-Eur Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # IMPORTANT: Specify your personal configuration here. After having done so, run the following command to avoid git committing the file with your personal configurations: 6 | # git update-index --skip-worktree FILENAME 7 | # and if you want to track the changes again use this command: 8 | # git update-index --no-skip-worktree FILENAME 9 | # See also: https://stackoverflow.com/questions/13630849/git-difference-between-assume-unchanged-and-skip-worktree# 10 | private: 11 | keys: 12 | entsoe_api: 13 | 14 | remote: 15 | ssh: "" 16 | path: "" 17 | -------------------------------------------------------------------------------- /config/config.public.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | run: 6 | prefix: 20240627public_db 7 | name: 8 | - 8Gt_Bal_v3 9 | # - 8Gt_Elec_v3 10 | # - 8Gt_H2_v3 11 | scenarios: 12 | enable: true 13 | manual_file: config/scenarios.public.yaml 14 | file: config/scenarios.automated.yaml 15 | shared_resources: 16 | policy: base #stops recalculating 17 | exclude: 18 | - existing_heating.csv # specify files which should not be shared between scenarios 19 | - costs 20 | - retrieve_cost # This is necessary to save retrieve_cost_data_{year}.log in the correct folder 21 | - industry_sector_ratios 22 | - build_industry_sector_ratios # This is necessary to save build_industry_sector_ratios_data.log in the correct folder 23 | - modify_existing_heating 24 | 25 | iiasa_database: 26 | db_name: ariadne 27 | leitmodelle: 28 | general: REMIND-EU v1.1 29 | buildings: REMod v1.0 30 | transport: DLR 31 | transport_stock: DLR-VECTOR21 32 | industry: FORECAST v1.0 33 | scenarios: 34 | - 8Gt_Bal_v3 35 | - 8Gt_Elec_v3 36 | - 8Gt_H2_v3 37 | reference_scenario: 8Gt_Bal_v3 38 | region: Deutschland 39 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: : 2017-2023 The PyPSA-Eur Authors 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#run 6 | run: 7 | prefix: 20241203-force-onwind-south 8 | name: 9 | # - CurrentPolicies 10 | - KN2045_Bal_v4 11 | # - KN2045_Elec_v4 12 | # - KN2045_H2_v4 13 | # - KN2045plus_EasyRide 14 | # - KN2045plus_LowDemand 15 | # - KN2045minus_WorstCase 16 | # - KN2045minus_SupplyFocus 17 | # - KN2045_Bal_LowDemand 18 | # - KN2045_Bal_HighDemand 19 | scenarios: 20 | enable: true 21 | manual_file: config/scenarios.manual.yaml 22 | file: config/scenarios.automated.yaml 23 | shared_resources: 24 | policy: base #stops recalculating 25 | exclude: 26 | - existing_heating.csv # specify files which should not be shared between scenarios 27 | - costs 28 | - retrieve_cost # This is necessary to save retrieve_cost_data_{year}.log in the correct folder 29 | - industry_sector_ratios 30 | - build_industry_sector_ratios # This is necessary to save build_industry_sector_ratios_data.log in the correct folder 31 | - modify_existing_heating 32 | disable_progressbar: true 33 | debug_co2_limit: false 34 | debug_h2deriv_limit: false 35 | debug_unravel_oilbus: false 36 | debug_unravel_gasbus: false 37 | 38 | iiasa_database: 39 | db_name: ariadne2_intern 40 | leitmodelle: 41 | general: REMIND-EU v1.1 42 | buildings: REMod v1.0 43 | transport: Aladin v1 44 | industry: FORECAST v1.0 45 | scenarios: 46 | - CurrentPolicies 47 | - KN2045_Elec_v4 48 | - KN2045_H2_v4 49 | - KN2045_Bal_v4 50 | - KN2045plus_EasyRide 51 | - KN2045plus_LowDemand 52 | - KN2045minus_WorstCase 53 | - KN2045minus_SupplyFocus 54 | reference_scenario: KN2045_Bal_v4 55 | region: Deutschland 56 | 57 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#foresight 58 | foresight: myopic 59 | 60 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#scenario 61 | # Wildcard docs in https://pypsa-eur.readthedocs.io/en/latest/wildcards.html 62 | scenario: 63 | ll: 64 | - vopt 65 | clusters: 66 | - 49 #current options: 27, 49 67 | opts: 68 | - '' 69 | sector_opts: 70 | - none 71 | planning_horizons: 72 | - 2020 73 | - 2025 74 | - 2030 75 | - 2035 76 | - 2040 77 | - 2045 78 | 79 | existing_capacities: 80 | grouping_years_power: [1920, 1950, 1955, 1960, 1965, 1970, 1975, 1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020] 81 | grouping_years_heat: [1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2019] # heat grouping years >= baseyear will be ignored 82 | 83 | 84 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#countries 85 | # Germany plus 12 "Stromnachbarn" 86 | countries: ['AT', 'BE', 'CH', 'CZ', 'DE', 'DK', 'FR', 'GB', 'LU', 'NL', 'NO', 'PL', 'SE', 'ES', 'IT'] 87 | 88 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#snapshots 89 | snapshots: 90 | start: "2019-01-01" 91 | end: "2020-01-01" 92 | inclusive: 'left' 93 | 94 | atlite: 95 | default_cutout: europe-2019-sarah3-era5 96 | cutouts: 97 | europe-2019-sarah3-era5: 98 | module: [sarah, era5] # in priority order 99 | x: [-12., 42.] 100 | y: [33., 72] 101 | dx: 0.3 102 | dy: 0.3 103 | time: ['2019', '2019'] 104 | 105 | 106 | renewable: 107 | onwind: 108 | capacity_per_sqkm: 1.4 109 | cutout: europe-2019-sarah3-era5 110 | correction_factor: 0.95 111 | resource: 112 | smooth: false #this is false until correction to onshore wind speeds from GWA implemented 113 | #based on Vestas_V112_3MW, but changing hub_height from 80m with time 114 | turbine: 115 | 2020: 116 | V: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 25, 25] 117 | POW: [0., 0., 0.005, 0.15, 0.3, 0.525, 0.905, 1.375, 1.95, 2.58, 2.96, 3.05, 3.06, 3.06, 0.] 118 | hub_height: 80. 119 | P: 3.06 120 | 2030: 121 | V: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 25, 25] 122 | POW: [0., 0., 0.005, 0.15, 0.3, 0.525, 0.905, 1.375, 1.95, 2.58, 2.96, 3.05, 3.06, 3.06, 0.] 123 | hub_height: 90. 124 | P: 3.06 125 | 2040: 126 | V: [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 25, 25] 127 | POW: [0., 0., 0.005, 0.15, 0.3, 0.525, 0.905, 1.375, 1.95, 2.58, 2.96, 3.05, 3.06, 3.06, 0.] 128 | hub_height: 100. 129 | P: 3.06 130 | offwind-ac: 131 | capacity_per_sqkm: 6 132 | landfall_length: 30 133 | cutout: europe-2019-sarah3-era5 134 | correction_factor: 0.95 135 | resource: 136 | smooth: true 137 | #based on NREL_ReferenceTurbine_2020ATB_5.5MW, but changing hub_height from 80m with time 138 | turbine: 139 | 2020: 140 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 141 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 142 | hub_height: 120. 143 | P: 5.5 144 | 2030: 145 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 146 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 147 | hub_height: 130. 148 | P: 5.5 149 | 2040: 150 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 151 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 152 | hub_height: 140. 153 | P: 5.5 154 | offwind-dc: 155 | capacity_per_sqkm: 6 156 | landfall_length: 30 157 | cutout: europe-2019-sarah3-era5 158 | correction_factor: 0.95 159 | resource: 160 | smooth: true 161 | #based on NREL_ReferenceTurbine_2020ATB_5.5MW, but changing hub_height from 80m with time 162 | turbine: 163 | 2020: 164 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 165 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 166 | hub_height: 120. 167 | P: 5.5 168 | 2030: 169 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 170 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 171 | hub_height: 130. 172 | P: 5.5 173 | 2040: 174 | V: [3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.2, 14.5, 14.8, 15.0, 15.2, 15.5, 15.8, 16.0, 16.2, 16.5, 16.8, 17.0, 17.2, 17.5, 17.8, 18.0, 18.2, 18.5, 18.8, 19.0, 19.2, 19.5, 19.8, 20.0, 20.2, 20.5, 20.8, 21.0, 21.2, 21.5, 21.8, 22.0, 22.2, 22.5, 22.8, 23.0, 23.2, 23.5, 23.8, 24.0, 24.2, 24.5, 24.8, 25.0] 175 | POW: [0.0, 0.127, 0.178, 0.237, 0.305, 0.381, 0.468, 0.564, 0.671, 0.789, 0.919, 1.061, 1.216, 1.385, 1.567, 1.765, 1.977, 2.205, 2.45, 2.711, 2.99, 3.287, 3.602, 3.937, 4.291, 4.665, 5.06, 5.477, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 0.0] 176 | hub_height: 140. 177 | P: 5.5 178 | offwind-float: # disabled at the moment 179 | landfall_length: 30 180 | capacity_per_sqkm: 6 181 | cutout: europe-2019-sarah3-era5 182 | correction_factor: 0.95 183 | solar: 184 | cutout: europe-2019-sarah3-era5 185 | correction_factor: 0.9 # scaling to Abbildung 36 of https://www.ise.fraunhofer.de/de/veroeffentlichungen/studien/aktuelle-fakten-zur-photovoltaik-in-deutschland.html 186 | solar-hsat: 187 | cutout: europe-2019-sarah3-era5 188 | correction_factor: 0.9 # scaling to Abbildung 36 of https://www.ise.fraunhofer.de/de/veroeffentlichungen/studien/aktuelle-fakten-zur-photovoltaik-in-deutschland.html 189 | hydro: 190 | cutout: europe-2019-sarah3-era5 191 | 192 | lines: 193 | dynamic_line_rating: 194 | cutout: europe-2019-sarah3-era5 195 | 196 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#enable 197 | enable: 198 | retrieve: false # set to false once initial data is retrieved 199 | retrieve_cutout: false # set to false once initial data is retrieved 200 | clustering: 201 | # simplify_network: 202 | # to_substations: true 203 | # Code snippet for editing focus_weights 204 | # fw = pd.Series(snakemake.config["clustering"]["focus_weights"]) 205 | # fw = fw.div(fw.min()).round() 206 | # fw["ES"] = 1 207 | # print(fw.div(fw.sum()).subtract(5e-5).round(4).to_dict().__repr__().replace(",","\n")) 208 | focus_weights: 209 | # 27 nodes: 8 for Germany, 3 for Italy, 2 each for Denmark, UK and Spain, 1 per each of other 10 "Stromnachbarn" 210 | 'DE': 0.2966 211 | 'AT': 0.0370 212 | 'BE': 0.0370 213 | 'CH': 0.0370 214 | 'CZ': 0.0370 215 | 'DK': 0.0741 216 | 'FR': 0.0370 217 | 'GB': 0.0741 218 | 'LU': 0.0370 219 | 'NL': 0.0370 220 | 'NO': 0.0370 221 | 'PL': 0.0370 222 | 'SE': 0.0370 223 | 'ES': 0.0741 224 | 'IT': 0.1111 225 | # high spatial resolution: change clusters to 49 226 | # 49 nodes: 30 for Germany, 3 for Italy, 2 each for Denmark, UK and Spain, 1 per each of other 10 "Stromnachbarn" 227 | # 'DE': 0.6124 228 | # 'AT': 0.0204 229 | # 'BE': 0.0204 230 | # 'CH': 0.0204 231 | # 'CZ': 0.0204 232 | # 'DK': 0.0408 233 | # 'FR': 0.0204 234 | # 'GB': 0.0408 235 | # 'LU': 0.0204 236 | # 'NL': 0.0204 237 | # 'NO': 0.0204 238 | # 'PL': 0.0204 239 | # 'SE': 0.0204 240 | # 'ES': 0.0408 241 | # 'IT': 0.0612 242 | temporal: 243 | resolution_sector: 365H 244 | 245 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#co2-budget 246 | co2_budget: 247 | 2020: 0.720 # average emissions of 2019 to 2021 relative to 1990, excl LULUCF, EEA data, European Environment Agency. (2023a). Annual European Union greenhouse gas inventory 1990–2021 and inventory report 2023 - CRF Table. https://unfccc.int/documents/627830 248 | 2025: 0.648 # With additional measures (WAM) projection, CO2 excl LULUCF, European Environment Agency. (2023e). Member States’ greenhouse gas (GHG) emission projections 2023. https://www.eea.europa.eu/en/datahub/datahubitem-view/4b8d94a4-aed7-4e67-a54c-0623a50f48e8 249 | 2030: 0.450 # 55% reduction by 2030 (Ff55) 250 | 2035: 0.250 251 | 2040: 0.100 # 90% by 2040 252 | 2045: 0.050 253 | 2050: 0.000 # climate-neutral by 2050 254 | 255 | wasserstoff_kernnetz: 256 | enable: true 257 | reload_locations: false 258 | divide_pipes: true 259 | pipes_segment_length: 50 260 | border_crossing: true 261 | aggregate_build_years: "mean" 262 | recalculate_length: true 263 | aggregate_parallel_pipes: true 264 | ipcei_pci_only: false 265 | cutoff_year: 2028 266 | force_all_ipcei_pci: true 267 | 268 | new_decentral_fossil_boiler_ban: 269 | DE: 2029 270 | 271 | coal_generation_ban: 272 | DE: 2038 273 | 274 | nuclear_generation_ban: 275 | DE: 2022 276 | 277 | first_technology_occurrence: 278 | Link: 279 | H2 pipeline: 2025 280 | H2 Electrolysis: 2025 281 | H2 pipeline retrofitted: 2025 282 | 283 | costs: 284 | horizon: "mean" # "optimist", "pessimist" or "mean" 285 | 286 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#sector 287 | sector: 288 | v2g: false 289 | solar_thermal: false 290 | district_heating: 291 | potential: 0.3 292 | progress: 293 | 2020: 0.0 294 | 2025: 0.15 295 | 2030: 0.3 296 | 2035: 0.45 297 | 2040: 0.6 298 | 2045: 0.8 299 | 2050: 1.0 300 | central_heat_vent: true 301 | co2_spatial: true 302 | biomass_spatial: true 303 | #TBD what to include in config 304 | #relax so no infeasibility in 2050 with no land transport demand 305 | min_part_load_fischer_tropsch: 0. 306 | regional_methanol_demand: true #set to true if regional CO2 constraints needed 307 | regional_oil_demand: true #set to true if regional CO2 constraints needed 308 | regional_coal_demand: true #set to true if regional CO2 constraints needed 309 | gas_network: false 310 | regional_gas_demand: true 311 | H2_retrofit: true 312 | biogas_upgrading_cc: true 313 | biomass_to_liquid: true 314 | biomass_to_liquid_cc: true 315 | cluster_heat_buses: true 316 | # calculated based on ariadne "Stock|Space Heating" 317 | # and then 2% of buildings renovated per year to reduce their demand by 80% 318 | reduce_space_heat_exogenously_factor: 319 | 2020: 0.0 320 | 2025: 0.07 321 | 2030: 0.14 322 | 2035: 0.21 323 | 2040: 0.29 324 | 2045: 0.36 325 | 2050: 0.43 326 | land_transport_fuel_cell_share: 327 | 2020: 0.05 328 | 2025: 0.05 329 | 2030: 0.05 330 | 2035: 0.05 331 | 2040: 0.05 332 | 2045: 0.05 333 | 2050: 0.05 334 | land_transport_electric_share: 335 | 2020: 0.05 336 | 2025: 0.15 337 | 2030: 0.3 338 | 2035: 0.45 339 | 2040: 0.7 340 | 2045: 0.85 341 | 2050: 0.95 342 | land_transport_ice_share: 343 | 2020: 0.9 344 | 2025: 0.8 345 | 2030: 0.65 346 | 2035: 0.5 347 | 2040: 0.25 348 | 2045: 0.1 349 | 2050: 0.0 350 | 351 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#industry 352 | industry: 353 | ammonia: false 354 | St_primary_fraction: 355 | 2020: 0.6 356 | 2025: 0.55 357 | 2030: 0.5 358 | 2035: 0.45 359 | 2040: 0.4 360 | 2045: 0.35 361 | 2050: 0.3 362 | DRI_fraction: 363 | 2020: 0 364 | 2025: 0 365 | 2030: 0.05 366 | 2035: 0.3 367 | 2040: 0.6 368 | 2045: 1 369 | #HVC primary/recycling based on values used in Neumann et al https://doi.org/10.1016/j.joule.2023.06.016, linearly interpolated between 2020 and 2050 370 | #2020 recycling rates based on Agora https://static.agora-energiewende.de/fileadmin/Projekte/2021/2021_02_EU_CEAP/A-EW_254_Mobilising-circular-economy_study_WEB.pdf 371 | #fractions refer to the total primary HVC production in 2020 372 | #assumes 6.7 Mtplastics produced from recycling in 2020 373 | HVC_primary_fraction: 374 | 2020: 1.0 375 | 2025: 0.9 376 | 2030: 0.8 377 | 2035: 0.7 378 | 2040: 0.6 379 | 2045: 0.5 380 | 2050: 0.4 381 | HVC_mechanical_recycling_fraction: 382 | 2020: 0.12 383 | 2025: 0.15 384 | 2030: 0.18 385 | 2035: 0.21 386 | 2040: 0.24 387 | 2045: 0.27 388 | 2050: 0.30 389 | HVC_chemical_recycling_fraction: 390 | 2020: 0.0 391 | 2025: 0.0 392 | 2030: 0.04 393 | 2035: 0.08 394 | 2040: 0.12 395 | 2045: 0.16 396 | 2050: 0.20 397 | # 15 Mt HVC production (from IDEES) -> 6 Mt Plastikabfälle, 398 | # To substitute for other waste, assume all Plastikabfall is used energetically whereas in reality ~40% is recycled 399 | # see https://github.com/PyPSA/pypsa-ariadne/pull/292 400 | HVC_environment_sequestration_fraction: 401 | 2020: 0.6 402 | 2025: 0.6 403 | 2030: 0.6 404 | 2035: 0.6 405 | 2040: 0.6 406 | 2045: 0.6 407 | 2050: 0.6 408 | waste_to_energy: true 409 | waste_to_energy_cc: true 410 | 411 | # docs in https://pypsa-eur.readthedocs.io/en/latest/configuration.html#solving 412 | solving: 413 | runtime: 12h 414 | mem_mb: 70000 #30000 is OK for 22 nodes, 365H; 140000 for 22 nodes 3H; 400000 for 44 nodes 3H 415 | options: 416 | assign_all_duals: true 417 | load_shedding: false 418 | skip_iterations: true # settings for post-discretization: false 419 | min_iterations: 1 # settings for post-discretization: 1 420 | max_iterations: 1 # settings for post-discretization: 1 421 | post_discretization: 422 | enable: false 423 | line_unit_size: 1698 424 | line_threshold: 0.3 425 | link_unit_size: 426 | DC: 1000 427 | gas pipeline: 1500 428 | gas pipeline new: 1500 429 | H2 pipeline: 4700 430 | H2 pipeline retrofitted: 4700 431 | link_threshold: 432 | DC: 0.3 433 | gas pipeline: 0.3 434 | gas pipeline new: 0.3 435 | H2 pipeline: 0.05 436 | H2 pipeline retrofitted: 0.05 437 | fractional_last_unit_size: true 438 | constraints: 439 | limits_capacity_max: 440 | Generator: 441 | onwind: 442 | DE: 443 | 2020: 54.5 444 | 2025: 69 445 | 2030: 157 # EEG2023 Ziel für 2035 446 | 2035: 250 447 | 2040: 250 448 | 2045: 250 449 | offwind: 450 | DE: 451 | 2020: 7.8 452 | 2025: 11.3 453 | 2030: 29.3 # uba Projektionsbericht and NEP without delayed BalWin 3 454 | 2035: 70 455 | 2040: 70 456 | 2045: 70 457 | solar: 458 | DE: 459 | 2020: 53.7 460 | 2025: 110 # EEG2023; assumes for 2026: 128 GW, assuming a fair share reached by end of 2025 461 | 2030: 309 # EEG2023 Ziel für 2035 462 | 2035: 1000 463 | 2040: 1000 464 | 2045: 1000 465 | Store: 466 | co2 sequestered: 467 | DE: 468 | 2020: 0 469 | 2025: 0 470 | 2030: 10000 471 | 2035: 20000 472 | 2040: 50000 473 | 2045: 80000 474 | Link: 475 | methanolisation: 476 | DE: 477 | 2030: 5.7 478 | 2035: 5.7 479 | 2040: 5.7 480 | 2045: 5.7 481 | Fischer-Tropsch: 482 | DE: 483 | 2030: 2.5 484 | 2035: 2.5 485 | 2040: 2.5 486 | 2045: 2.5 487 | HVC to air: 488 | DE: 489 | 2020: 0 # all HVC in Germany is either burned or recycled 490 | 2025: 0 491 | 2030: 0 492 | 2035: 0 493 | 2040: 0 494 | 2045: 0 495 | limits_capacity_min: 496 | Generator: 497 | onwind: 498 | DE: 499 | 2030: 99 # Wind-an-Land Law 2028 500 | 2035: 115 # Wind-an-Land Law 2030 501 | 2040: 157 # target 2035 502 | 2045: 160 # target 2040 503 | offwind: 504 | DE: 505 | 2030: 22.5 # 75% Wind-auf-See Law 506 | 2035: 35 507 | 2040: 42 508 | 2045: 50 509 | solar: 510 | DE: 511 | # EEG2023; Ziel for 2024: 88 GW and for 2026: 128 GW, 512 | # assuming at least 1/3 of difference reached in 2025 513 | 2025: 101 514 | 2030: 101 515 | 2035: 101 516 | 2040: 101 517 | 2045: 101 518 | # For reference, this are the values specified in the laws 519 | # limits_capacity_min: 520 | # Generator: 521 | # onwind: 522 | # DE: 523 | # 2030: 115 # Wind-an-Land Law 524 | # 2035: 157 # Wind-an-Land Law 525 | # 2040: 160 # Wind-an-Land Law 526 | # 2045: 160 527 | # offwind: 528 | # DE: 529 | # 2030: 30 # Wind-auf-See Law 530 | # 2035: 40 # 40 Wind-auf-See Law 531 | # # assuming at least 1/3 of difference reached in 2040 532 | # 2040: 50 533 | # 2045: 70 #70 Wind-auf-See Law 534 | # solar: 535 | # DE: 536 | # # EEG2023; Ziel for 2024: 88 GW and for 2026: 128 GW, 537 | # # assuming at least 1/3 of difference reached in 2025 538 | # 2025: 101 539 | # 2030: 215 # PV strategy 540 | # 2035: 309 541 | # 2040: 400 # PV strategy 542 | # 2045: 400 543 | # # What about the EEG2023 "Strommengenpfad"? 544 | # boundary condition of maximum volumes 545 | limits_volume_max: 546 | # constrain electricity import in TWh 547 | electricity_import: 548 | DE: 549 | 2020: -20 550 | 2025: 0 551 | 2030: 0 552 | 2035: 40 553 | 2040: 80 554 | 2045: 125 555 | electrolysis: 556 | # boundary condition lower? 557 | DE: 558 | 2020: 0 559 | 2025: 5 560 | 2030: 45 561 | 2035: 130 562 | 2040: 215 563 | 2045: 300 564 | h2_derivate_import: 565 | # boundary condition lower? 566 | DE: 567 | 2020: 0 568 | 2025: 0 569 | 2030: 10 570 | 2035: 105 571 | 2040: 200 572 | 2045: 300 573 | h2_import: 574 | # boundary condition lower? 575 | DE: 576 | 2020: 0 577 | 2025: 5 578 | 2030: 15 579 | 2035: 115 580 | 2040: 220 581 | 2045: 325 582 | limits_volume_min: 583 | electrolysis: 584 | DE: 585 | 2020: 0 586 | 2025: 0 587 | 2030: 0 588 | 2035: 0 589 | 2040: 0 590 | 2045: 0 591 | limits_power_max: # in GW 592 | DE: 593 | 2020: 15 594 | 2025: 15 595 | 2030: 20 596 | 2035: 25 597 | 2040: 30 598 | 2045: 35 599 | # solver: 600 | # options: gurobi-numeric-focus 601 | # solver_options: 602 | # gurobi-default: 603 | # NumericFocus: 1 604 | # FeasibilityTol: 1.e-4 605 | # BarHomogeneous: 1 606 | 607 | plotting: 608 | tech_colors: 609 | load: "#111100" 610 | H2 pipeline (Kernnetz): '#6b3161' 611 | renewable oil: '#c9c9c9' 612 | urban central H2 retrofit CHP: '#ff0000' 613 | H2 retrofit OCGT: '#ff0000' 614 | H2 retrofit CCGT: '#ff0000' 615 | H2 OCGT: '#ff0000' 616 | H2 CCGT: '#ff0000' 617 | urban central H2 CHP: '#ff0000' 618 | renewable gas: '#e05b09' 619 | countries: 620 | - all 621 | - DE 622 | carriers: 623 | - electricity 624 | - heat 625 | - H2 626 | - urban central heat 627 | - urban decentral heat 628 | - rural heat 629 | carrier_groups: 630 | electricity: [AC, low_voltage] 631 | 632 | # overwrite in config.default.yaml 633 | #powerplants_filter: (DateOut >= 2019 or DateOut != DateOut) 634 | electricity: 635 | renewable_carriers: [solar, solar-hsat, onwind, offwind-ac, offwind-dc, hydro] # removed offwind-float 636 | powerplants_filter: (DateOut >= 2019 or DateOut != DateOut) and not (Country == "DE" and Set == "CHP") 637 | custom_powerplants: true 638 | custom_file: resources/german_chp.csv 639 | estimate_renewable_capacities: 640 | year: 2019 641 | H2_plants_DE: 642 | enable: true 643 | start: 2030 # should be < force 644 | force: 2035 645 | cost_factor: 0.15 # repurposing cost of OCGT gas to H2 in % investment cost in EUR/MW source: Christidis et al (2023) - H2-Ready-Gaskraftwerke, Table 3 https://reiner-lemoine-institut.de/wp-content/uploads/2023/11/RLI-Studie-H2-ready_DE.pdf 646 | efficiency_loss: 0.05 647 | 648 | pypsa_eur: 649 | Bus: 650 | - AC 651 | Link: 652 | - DC 653 | Generator: 654 | - onwind 655 | - offwind-ac 656 | - offwind-dc 657 | - solar-hsat 658 | - solar 659 | - ror 660 | StorageUnit: 661 | - PHS 662 | - hydro 663 | Store: [] 664 | 665 | 666 | co2_price_add_on_fossils: 667 | # 2020: 25 668 | # 2025: 60 669 | 670 | must_run: 671 | 2020: 672 | DE: 673 | lignite: 0.4 674 | # biogas: 0.6 675 | 676 | transmission_projects: 677 | new_link_capacity: keep #keep or zero 678 | 679 | onshore_nep_force: 680 | cutin_year: 2020 681 | cutout_year: 2030 682 | 683 | #beware - may need to increase max offshore 684 | #to avoid infeasibilities 685 | offshore_nep_force: 686 | cutin_year: 2025 687 | cutout_year: 2030 688 | delay_years: 0 689 | 690 | scale_capacity: 691 | 2020: 692 | DE: 693 | CCGT: 10000 694 | "urban central gas CHP": 22000 695 | -------------------------------------------------------------------------------- /config/scenarios.public.yaml: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2017-2023 The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | 7 | 8Gt_Bal_v3: 8 | iiasa_database: 9 | reference_scenario: 8Gt_Bal_v3 10 | fallback_reference_scenario: 8Gt_Bal_v3 11 | co2_budget_DE_source: KSG 12 | 13 | costs: 14 | horizon: "mean" 15 | NEP: 2023 16 | transmission: "overhead" # either overhead line ("overhead") or underground cable ("underground") 17 | solving: 18 | constraints: 19 | # boundary condition of maximum volumes 20 | limits_volume_max: 21 | # constrain electricity import in TWh 22 | electricity_import: 23 | DE: 24 | 2020: -20 25 | 2025: 0 26 | 2030: 0 27 | 2035: 40 28 | 2040: 80 29 | 2045: 125 30 | electrolysis: 31 | DE: 32 | 2020: 0 33 | 2025: 5 34 | 2030: 45 35 | 2035: 130 36 | 2040: 215 37 | 2045: 300 38 | h2_derivate_import: 39 | DE: 40 | 2020: 0 41 | 2025: 0 42 | 2030: 10 43 | 2035: 105 44 | 2040: 200 45 | 2045: 300 46 | h2_import: 47 | DE: 48 | 2020: 0 49 | 2025: 5 50 | 2030: 15 51 | 2035: 115 52 | 2040: 220 53 | 2045: 325 54 | limits_volume_min: 55 | electrolysis: 56 | DE: 57 | 2020: 0 58 | 2025: 0 59 | 2030: 0 60 | 2035: 0 61 | 2040: 0 62 | 2045: 0 63 | limits_capacity_min: 64 | Link: 65 | H2 Electrolysis: 66 | DE: 67 | 2030: 5 68 | 69 | industry: 70 | steam_biomass_fraction: 0.4 71 | steam_hydrogen_fraction: 0.3 72 | steam_electricity_fraction: 0.3 73 | 74 | 8Gt_Elec_v3: 75 | iiasa_database: 76 | reference_scenario: 8Gt_Elec_v3 77 | fallback_reference_scenario: 8Gt_Elec_v3 78 | co2_budget_DE_source: KSG 79 | 80 | costs: 81 | horizon: "mean" 82 | NEP: 2023 83 | transmission: "overhead" # either overhead line ("overhead") or underground cable ("underground") 84 | solving: 85 | constraints: 86 | limits_volume_max: 87 | # constrain electricity import in TWh 88 | electricity_import: 89 | DE: 90 | 2020: -20 91 | 2025: 0 92 | 2030: 0 93 | 2035: 50 94 | 2040: 100 95 | 2045: 150 96 | electrolysis: 97 | DE: 98 | 2020: 0 99 | 2025: 5 100 | 2030: 45 101 | 2035: 95 102 | 2040: 145 103 | 2045: 200 104 | h2_derivate_import: 105 | DE: 106 | 2020: 0 107 | 2025: 0 108 | 2030: 10 109 | 2035: 70 110 | 2040: 130 111 | 2045: 200 112 | h2_import: 113 | DE: 114 | 2020: 0 115 | 2025: 5 116 | 2030: 10 117 | 2035: 90 118 | 2040: 170 119 | 2045: 250 120 | limits_volume_min: 121 | electrolysis: 122 | DE: 123 | 2020: 0 124 | 2025: 0 125 | 2030: 0 126 | 2035: 0 127 | 2040: 0 128 | 2045: 0 129 | limits_capacity_min: 130 | Link: 131 | H2 Electrolysis: 132 | DE: 133 | 2030: 5 134 | 135 | industry: 136 | steam_biomass_fraction: 0.4 137 | steam_hydrogen_fraction: 0.1 138 | steam_electricity_fraction: 0.5 139 | 140 | 8Gt_H2_v3: 141 | iiasa_database: 142 | reference_scenario: 8Gt_H2_v3 143 | fallback_reference_scenario: 8Gt_H2_v3 144 | co2_budget_DE_source: KSG 145 | 146 | costs: 147 | horizon: "mean" 148 | NEP: 2023 149 | transmission: "overhead" # either overhead line ("overhead") or underground cable ("underground") 150 | solving: 151 | constraints: 152 | limits_volume_max: 153 | # constrain electricity import in TWh 154 | electricity_import: 155 | DE: 156 | 2020: -20 157 | 2025: 0 158 | 2030: 0 159 | 2035: 30 160 | 2040: 70 161 | 2045: 100 # scenario guidelines 162 | 163 | # constrain hydrogen import in TWh 164 | h2_import: 165 | DE: 166 | 2020: 0 167 | 2025: 5 168 | 2030: 45 # scenario guidelines 169 | 2035: 155 170 | 2040: 265 171 | 2045: 400 # scenario guidelines 172 | # import of h2 derivatives in TWh 173 | h2_derivate_import: 174 | DE: 175 | 2020: 0 176 | 2025: 0 177 | 2030: 10 # scenario guidelines 178 | 2035: 140 179 | 2040: 270 180 | 2045: 400 # scenario guidelines 181 | electrolysis: 182 | DE: 183 | 2020: 0 184 | 2025: 5 185 | 2030: 45 # scenario guidelines 186 | 2035: 160 187 | 2040: 275 188 | 2045: 400 # scenario guidelines 189 | 190 | limits_volume_min: 191 | electrolysis: 192 | DE: 193 | 2025: 0 194 | 2030: 0 195 | 2035: 0 196 | 2040: 0 197 | 2045: 200 198 | limits_capacity_min: 199 | Link: 200 | H2 Electrolysis: 201 | DE: 202 | 2030: 5 203 | 204 | industry: 205 | steam_biomass_fraction: 0.4 206 | steam_hydrogen_fraction: 0.5 207 | steam_electricity_fraction: 0.1 208 | -------------------------------------------------------------------------------- /environment.yaml: -------------------------------------------------------------------------------- 1 | name: pypsa-ariadne 2 | channels: 3 | - conda-forge 4 | - bioconda 5 | - gurobi 6 | dependencies: 7 | - python>=3.8 8 | - pip 9 | 10 | - atlite>=0.2.9 11 | - linopy<0.4.0 12 | - dask 13 | 14 | # Dependencies of the workflow itself (see pypsa-eur) 15 | - xlrd 16 | - openpyxl!=3.1.1 17 | - pycountry 18 | - seaborn 19 | - snakemake-minimal<8.25 # See https://github.com/snakemake/snakemake/issues/3202 20 | - memory_profiler 21 | - yaml 22 | - pytables 23 | - lxml 24 | - powerplantmatching>=0.5.15,<0.6 25 | - numpy 26 | - pandas>=2.1 27 | - geopandas>=1.0 28 | - xarray>=2023.11.0 29 | - rioxarray 30 | - netcdf4 31 | - networkx 32 | - scipy 33 | - glpk 34 | - shapely>=2.0 35 | - pyscipopt 36 | - matplotlib 37 | - proj 38 | - fiona 39 | - country_converter 40 | - geopy 41 | - tqdm 42 | - pytz 43 | - tabula-py 44 | - pyxlsb 45 | - graphviz 46 | - pre-commit 47 | 48 | # Ariadne specific 49 | - pyam>=2.0 50 | - ruamel.yaml 51 | - gurobi 52 | 53 | # Keep in conda environment when calling ipython 54 | - ipython 55 | 56 | # GIS dependencies: 57 | - cartopy 58 | - descartes 59 | - rasterio<1.4 60 | 61 | - pip: 62 | 63 | - pypsa>=0.31.0 64 | - tsam>=2.3.1 65 | - snakemake-storage-plugin-http 66 | - snakemake-executor-plugin-slurm 67 | - snakemake-executor-plugin-cluster-generic 68 | - highspy 69 | # - git+https://github.com/PyPSA/pypsa@master # Until new release 70 | -------------------------------------------------------------------------------- /matplotlibrc: -------------------------------------------------------------------------------- 1 | font.family: sans-serif 2 | font.sans-serif: Ubuntu 3 | image.cmap: viridis 4 | axes.grid : false 5 | legend.frameon : false 6 | savefig.bbox : tight 7 | mathtext.default : regular 8 | -------------------------------------------------------------------------------- /workflow/envs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyPSA/pypsa-ariadne/97b7c25e657bb65341354673334300d678eac973/workflow/envs/.gitkeep -------------------------------------------------------------------------------- /workflow/notebooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyPSA/pypsa-ariadne/97b7c25e657bb65341354673334300d678eac973/workflow/notebooks/.gitkeep -------------------------------------------------------------------------------- /workflow/rules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyPSA/pypsa-ariadne/97b7c25e657bb65341354673334300d678eac973/workflow/rules/.gitkeep -------------------------------------------------------------------------------- /workflow/scripts/additional_functionality.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | 5 | import pandas as pd 6 | from prepare_sector_network import determine_emission_sectors 7 | from xarray import DataArray 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def add_capacity_limits(n, investment_year, limits_capacity, sense="maximum"): 13 | 14 | for c in n.iterate_components(limits_capacity): 15 | logger.info(f"Adding {sense} constraints for {c.list_name}") 16 | 17 | attr = "e" if c.name == "Store" else "p" 18 | units = "MWh or tCO2" if c.name == "Store" else "MW" 19 | 20 | for carrier in limits_capacity[c.name]: 21 | 22 | for ct in limits_capacity[c.name][carrier]: 23 | if investment_year not in limits_capacity[c.name][carrier][ct].keys(): 24 | continue 25 | 26 | limit = 1e3 * limits_capacity[c.name][carrier][ct][investment_year] 27 | 28 | logger.info( 29 | f"Adding constraint on {c.name} {carrier} capacity in {ct} to be {sense} {limit} {units}" 30 | ) 31 | 32 | valid_components = ( 33 | (c.df.index.str[:2] == ct) 34 | & (c.df.carrier.str[: len(carrier)] == carrier) 35 | & ~c.df.carrier.str.contains("thermal") 36 | ) # exclude solar thermal 37 | 38 | existing_index = c.df.index[ 39 | valid_components & ~c.df[attr + "_nom_extendable"] 40 | ] 41 | extendable_index = c.df.index[ 42 | valid_components & c.df[attr + "_nom_extendable"] 43 | ] 44 | 45 | existing_capacity = c.df.loc[existing_index, attr + "_nom"].sum() 46 | 47 | logger.info( 48 | f"Existing {c.name} {carrier} capacity in {ct}: {existing_capacity} {units}" 49 | ) 50 | 51 | nom = n.model[c.name + "-" + attr + "_nom"].loc[extendable_index] 52 | 53 | lhs = nom.sum() 54 | 55 | cname = f"capacity_{sense}-{ct}-{c.name}-{carrier.replace(' ','-')}" 56 | 57 | if cname in n.global_constraints.index: 58 | logger.warning( 59 | f"Global constraint {cname} already exists. Dropping and adding it again." 60 | ) 61 | n.global_constraints.drop(cname, inplace=True) 62 | 63 | rhs = limit - existing_capacity 64 | 65 | if sense == "maximum": 66 | if rhs <= 0: 67 | logger.warning( 68 | f"Existing capacity in {ct} for carrier {carrier} already exceeds the limit of {limit} MW. Limiting capacity expansion for this investment period to 0." 69 | ) 70 | rhs = 0 71 | 72 | n.model.add_constraints( 73 | lhs <= rhs, 74 | name=f"GlobalConstraint-{cname}", 75 | ) 76 | n.add( 77 | "GlobalConstraint", 78 | cname, 79 | constant=rhs, 80 | sense="<=", 81 | type="", 82 | carrier_attribute="", 83 | ) 84 | 85 | elif sense == "minimum": 86 | n.model.add_constraints( 87 | lhs >= rhs, 88 | name=f"GlobalConstraint-{cname}", 89 | ) 90 | n.add( 91 | "GlobalConstraint", 92 | cname, 93 | constant=rhs, 94 | sense=">=", 95 | type="", 96 | carrier_attribute="", 97 | ) 98 | else: 99 | logger.error("sense {sense} not recognised") 100 | sys.exit() 101 | 102 | 103 | def add_power_limits(n, investment_year, limits_power_max): 104 | """ 105 | " Restricts the maximum inflow/outflow of electricity from/to a country. 106 | """ 107 | for ct in limits_power_max: 108 | if investment_year not in limits_power_max[ct].keys(): 109 | continue 110 | 111 | limit = 1e3 * limits_power_max[ct][investment_year] / 10 112 | 113 | logger.info( 114 | f"Adding constraint on electricity import/export from/to {ct} to be < {limit} MW" 115 | ) 116 | incoming_line = n.lines.index[ 117 | (n.lines.carrier == "AC") 118 | & (n.lines.bus0.str[:2] != ct) 119 | & (n.lines.bus1.str[:2] == ct) 120 | ] 121 | outgoing_line = n.lines.index[ 122 | (n.lines.carrier == "AC") 123 | & (n.lines.bus0.str[:2] == ct) 124 | & (n.lines.bus1.str[:2] != ct) 125 | ] 126 | 127 | incoming_link = n.links.index[ 128 | (n.links.carrier == "DC") 129 | & (n.links.bus0.str[:2] != ct) 130 | & (n.links.bus1.str[:2] == ct) 131 | ] 132 | outgoing_link = n.links.index[ 133 | (n.links.carrier == "DC") 134 | & (n.links.bus0.str[:2] == ct) 135 | & (n.links.bus1.str[:2] != ct) 136 | ] 137 | 138 | # iterate over snapshots - otherwise exporting of postnetwork fails since 139 | # the constraints are time dependent 140 | for t in n.snapshots: 141 | incoming_line_p = n.model["Line-s"].loc[t, incoming_line] 142 | outgoing_line_p = n.model["Line-s"].loc[t, outgoing_line] 143 | incoming_link_p = n.model["Link-p"].loc[t, incoming_link] 144 | outgoing_link_p = n.model["Link-p"].loc[t, outgoing_link] 145 | 146 | lhs = ( 147 | incoming_link_p.sum() 148 | - outgoing_link_p.sum() 149 | + incoming_line_p.sum() 150 | - outgoing_line_p.sum() 151 | ) / 10 152 | # divide by 10 to avoid numerical issues 153 | 154 | cname_upper = f"Power-import-limit-{ct}-{t}" 155 | cname_lower = f"Power-export-limit-{ct}-{t}" 156 | 157 | n.model.add_constraints(lhs <= limit, name=cname_upper) 158 | n.model.add_constraints(lhs >= -limit, name=cname_lower) 159 | 160 | # not adding to network as the shadow prices are not needed 161 | 162 | 163 | def h2_import_limits(n, investment_year, limits_volume_max): 164 | 165 | for ct in limits_volume_max["h2_import"]: 166 | limit = limits_volume_max["h2_import"][ct][investment_year] * 1e6 167 | 168 | logger.info(f"limiting H2 imports in {ct} to {limit/1e6} TWh/a") 169 | pipeline_carrier = [ 170 | "H2 pipeline", 171 | "H2 pipeline (Kernnetz)", 172 | "H2 pipeline retrofitted", 173 | ] 174 | incoming = n.links.index[ 175 | (n.links.carrier.isin(pipeline_carrier)) 176 | & (n.links.bus0.str[:2] != ct) 177 | & (n.links.bus1.str[:2] == ct) 178 | ] 179 | outgoing = n.links.index[ 180 | (n.links.carrier.isin(pipeline_carrier)) 181 | & (n.links.bus0.str[:2] == ct) 182 | & (n.links.bus1.str[:2] != ct) 183 | ] 184 | 185 | incoming_p = ( 186 | n.model["Link-p"].loc[:, incoming] * n.snapshot_weightings.generators 187 | ).sum() 188 | outgoing_p = ( 189 | n.model["Link-p"].loc[:, outgoing] * n.snapshot_weightings.generators 190 | ).sum() 191 | 192 | lhs = incoming_p - outgoing_p 193 | 194 | cname = f"H2_import_limit-{ct}" 195 | 196 | n.model.add_constraints(lhs <= limit, name=f"GlobalConstraint-{cname}") 197 | 198 | if cname in n.global_constraints.index: 199 | logger.warning( 200 | f"Global constraint {cname} already exists. Dropping and adding it again." 201 | ) 202 | n.global_constraints.drop(cname, inplace=True) 203 | 204 | n.add( 205 | "GlobalConstraint", 206 | cname, 207 | constant=limit, 208 | sense="<=", 209 | type="", 210 | carrier_attribute="", 211 | ) 212 | 213 | 214 | def h2_production_limits(n, investment_year, limits_volume_min, limits_volume_max): 215 | 216 | for ct in limits_volume_max["electrolysis"]: 217 | if ct not in limits_volume_min["electrolysis"]: 218 | logger.warning( 219 | f"no lower limit for H2 electrolysis in {ct} assuming 0 TWh/a" 220 | ) 221 | limit_lower = 0 222 | else: 223 | limit_lower = limits_volume_min["electrolysis"][ct][investment_year] * 1e6 224 | 225 | limit_upper = limits_volume_max["electrolysis"][ct][investment_year] * 1e6 226 | 227 | logger.info( 228 | f"limiting H2 electrolysis in DE between {limit_lower/1e6} and {limit_upper/1e6} TWh/a" 229 | ) 230 | 231 | production = n.links[ 232 | (n.links.carrier == "H2 Electrolysis") & (n.links.bus0.str.contains(ct)) 233 | ].index 234 | efficiency = n.links.loc[production, "efficiency"] 235 | 236 | lhs = ( 237 | n.model["Link-p"].loc[:, production] 238 | * n.snapshot_weightings.generators 239 | * efficiency 240 | ).sum() 241 | 242 | cname_upper = f"H2_production_limit_upper-{ct}" 243 | cname_lower = f"H2_production_limit_lower-{ct}" 244 | 245 | n.model.add_constraints( 246 | lhs <= limit_upper, name=f"GlobalConstraint-{cname_upper}" 247 | ) 248 | 249 | n.model.add_constraints( 250 | lhs >= limit_lower, name=f"GlobalConstraint-{cname_lower}" 251 | ) 252 | 253 | if cname_upper not in n.global_constraints.index: 254 | n.add( 255 | "GlobalConstraint", 256 | cname_upper, 257 | constant=limit_upper, 258 | sense="<=", 259 | type="", 260 | carrier_attribute="", 261 | ) 262 | if cname_lower not in n.global_constraints.index: 263 | n.add( 264 | "GlobalConstraint", 265 | cname_lower, 266 | constant=limit_lower, 267 | sense=">=", 268 | type="", 269 | carrier_attribute="", 270 | ) 271 | 272 | 273 | def electricity_import_limits(n, investment_year, limits_volume_max): 274 | 275 | for ct in limits_volume_max["electricity_import"]: 276 | limit = limits_volume_max["electricity_import"][ct][investment_year] * 1e6 277 | 278 | logger.info(f"limiting electricity imports in {ct} to {limit/1e6} TWh/a") 279 | 280 | incoming_line = n.lines.index[ 281 | (n.lines.carrier == "AC") 282 | & (n.lines.bus0.str[:2] != ct) 283 | & (n.lines.bus1.str[:2] == ct) 284 | ] 285 | outgoing_line = n.lines.index[ 286 | (n.lines.carrier == "AC") 287 | & (n.lines.bus0.str[:2] == ct) 288 | & (n.lines.bus1.str[:2] != ct) 289 | ] 290 | 291 | incoming_link = n.links.index[ 292 | (n.links.carrier == "DC") 293 | & (n.links.bus0.str[:2] != ct) 294 | & (n.links.bus1.str[:2] == ct) 295 | ] 296 | outgoing_link = n.links.index[ 297 | (n.links.carrier == "DC") 298 | & (n.links.bus0.str[:2] == ct) 299 | & (n.links.bus1.str[:2] != ct) 300 | ] 301 | 302 | incoming_line_p = ( 303 | n.model["Line-s"].loc[:, incoming_line] * n.snapshot_weightings.generators 304 | ).sum() 305 | outgoing_line_p = ( 306 | n.model["Line-s"].loc[:, outgoing_line] * n.snapshot_weightings.generators 307 | ).sum() 308 | 309 | incoming_link_p = ( 310 | n.model["Link-p"].loc[:, incoming_link] * n.snapshot_weightings.generators 311 | ).sum() 312 | outgoing_link_p = ( 313 | n.model["Link-p"].loc[:, outgoing_link] * n.snapshot_weightings.generators 314 | ).sum() 315 | 316 | lhs = (incoming_link_p - outgoing_link_p) + (incoming_line_p - outgoing_line_p) 317 | 318 | cname = f"Electricity_import_limit-{ct}" 319 | 320 | n.model.add_constraints(lhs <= limit, name=f"GlobalConstraint-{cname}") 321 | 322 | if cname in n.global_constraints.index: 323 | logger.warning( 324 | f"Global constraint {cname} already exists. Dropping and adding it again." 325 | ) 326 | n.global_constraints.drop(cname, inplace=True) 327 | 328 | n.add( 329 | "GlobalConstraint", 330 | cname, 331 | constant=limit, 332 | sense="<=", 333 | type="", 334 | carrier_attribute="", 335 | ) 336 | 337 | 338 | def add_co2limit_country(n, limit_countries, snakemake, debug=False): 339 | """ 340 | Add a set of emissions limit constraints for specified countries. 341 | 342 | The countries and emissions limits are specified in the config file entry 'co2_budget_national'. 343 | 344 | Parameters 345 | ---------- 346 | n : pypsa.Network 347 | limit_countries : dict 348 | snakemake: snakemake object 349 | """ 350 | logger.info(f"Adding CO2 budget limit for each country as per unit of 1990 levels") 351 | 352 | nhours = n.snapshot_weightings.generators.sum() 353 | nyears = nhours / 8760 354 | 355 | sectors = determine_emission_sectors(n.config["sector"]) 356 | 357 | # convert MtCO2 to tCO2 358 | co2_totals = 1e6 * pd.read_csv(snakemake.input.co2_totals_name, index_col=0) 359 | 360 | co2_total_totals = co2_totals[sectors].sum(axis=1) * nyears 361 | 362 | for ct in limit_countries: 363 | limit = co2_total_totals[ct] * limit_countries[ct] 364 | logger.info( 365 | f"Limiting emissions in country {ct} to {limit_countries[ct]:.1%} of " 366 | f"1990 levels, i.e. {limit:,.2f} tCO2/a", 367 | ) 368 | 369 | lhs = [] 370 | 371 | for port in [col[3:] for col in n.links if col.startswith("bus")]: 372 | 373 | links = n.links.index[ 374 | (n.links.index.str[:2] == ct) 375 | & (n.links[f"bus{port}"] == "co2 atmosphere") 376 | & ( 377 | n.links.carrier != "kerosene for aviation" 378 | ) # first exclude aviation to multiply it with a domestic factor later 379 | ] 380 | 381 | logger.info( 382 | f"For {ct} adding following link carriers to port {port} CO2 constraint: {n.links.loc[links,'carrier'].unique()}" 383 | ) 384 | 385 | if port == "0": 386 | efficiency = -1.0 387 | elif port == "1": 388 | efficiency = n.links.loc[links, f"efficiency"] 389 | else: 390 | efficiency = n.links.loc[links, f"efficiency{port}"] 391 | 392 | lhs.append( 393 | ( 394 | n.model["Link-p"].loc[:, links] 395 | * efficiency 396 | * n.snapshot_weightings.generators 397 | ).sum() 398 | ) 399 | 400 | # Aviation demand 401 | energy_totals = pd.read_csv(snakemake.input.energy_totals, index_col=[0, 1]) 402 | domestic_aviation = energy_totals.loc[ 403 | ("DE", snakemake.params.energy_year), "total domestic aviation" 404 | ] 405 | international_aviation = energy_totals.loc[ 406 | ("DE", snakemake.params.energy_year), "total international aviation" 407 | ] 408 | domestic_factor = domestic_aviation / ( 409 | domestic_aviation + international_aviation 410 | ) 411 | aviation_links = n.links[ 412 | (n.links.index.str[:2] == ct) & (n.links.carrier == "kerosene for aviation") 413 | ] 414 | lhs.append 415 | ( 416 | n.model["Link-p"].loc[:, aviation_links.index] 417 | * aviation_links.efficiency2 418 | * n.snapshot_weightings.generators 419 | ).sum() * domestic_factor 420 | logger.info( 421 | f"Adding domestic aviation emissions for {ct} with a factor of {domestic_factor}" 422 | ) 423 | 424 | # Adding Efuel imports and exports to constraint 425 | incoming_oil = n.links.index[n.links.index == "EU renewable oil -> DE oil"] 426 | outgoing_oil = n.links.index[n.links.index == "DE renewable oil -> EU oil"] 427 | 428 | if not debug: 429 | lhs.append( 430 | ( 431 | -1 432 | * n.model["Link-p"].loc[:, incoming_oil] 433 | * 0.2571 434 | * n.snapshot_weightings.generators 435 | ).sum() 436 | ) 437 | lhs.append( 438 | ( 439 | n.model["Link-p"].loc[:, outgoing_oil] 440 | * 0.2571 441 | * n.snapshot_weightings.generators 442 | ).sum() 443 | ) 444 | 445 | incoming_methanol = n.links.index[n.links.index == "EU methanol -> DE methanol"] 446 | outgoing_methanol = n.links.index[n.links.index == "DE methanol -> EU methanol"] 447 | 448 | lhs.append( 449 | ( 450 | -1 451 | * n.model["Link-p"].loc[:, incoming_methanol] 452 | / snakemake.config["sector"]["MWh_MeOH_per_tCO2"] 453 | * n.snapshot_weightings.generators 454 | ).sum() 455 | ) 456 | 457 | lhs.append( 458 | ( 459 | n.model["Link-p"].loc[:, outgoing_methanol] 460 | / snakemake.config["sector"]["MWh_MeOH_per_tCO2"] 461 | * n.snapshot_weightings.generators 462 | ).sum() 463 | ) 464 | 465 | # Methane 466 | incoming_CH4 = n.links.index[n.links.index == "EU renewable gas -> DE gas"] 467 | outgoing_CH4 = n.links.index[n.links.index == "DE renewable gas -> EU gas"] 468 | 469 | lhs.append( 470 | ( 471 | -1 472 | * n.model["Link-p"].loc[:, incoming_CH4] 473 | * 0.198 474 | * n.snapshot_weightings.generators 475 | ).sum() 476 | ) 477 | 478 | lhs.append( 479 | ( 480 | n.model["Link-p"].loc[:, outgoing_CH4] 481 | * 0.198 482 | * n.snapshot_weightings.generators 483 | ).sum() 484 | ) 485 | 486 | lhs = sum(lhs) 487 | 488 | cname = f"co2_limit-{ct}" 489 | 490 | n.model.add_constraints( 491 | lhs <= limit, 492 | name=f"GlobalConstraint-{cname}", 493 | ) 494 | 495 | if cname in n.global_constraints.index: 496 | logger.warning( 497 | f"Global constraint {cname} already exists. Dropping and adding it again." 498 | ) 499 | n.global_constraints.drop(cname, inplace=True) 500 | 501 | n.add( 502 | "GlobalConstraint", 503 | cname, 504 | constant=limit, 505 | sense="<=", 506 | type="", 507 | carrier_attribute="", 508 | ) 509 | 510 | 511 | def force_boiler_profiles_existing_per_load(n): 512 | """ 513 | This scales the boiler dispatch to the load profile with a factor common to 514 | all boilers at load. 515 | """ 516 | 517 | logger.info("Forcing boiler profiles for existing ones") 518 | 519 | decentral_boilers = n.links.index[ 520 | n.links.carrier.str.contains("boiler") 521 | & ~n.links.carrier.str.contains("urban central") 522 | & ~n.links.p_nom_extendable 523 | ] 524 | 525 | if decentral_boilers.empty: 526 | return 527 | 528 | boiler_loads = n.links.loc[decentral_boilers, "bus1"] 529 | boiler_loads = boiler_loads[boiler_loads.isin(n.loads_t.p_set.columns)] 530 | decentral_boilers = boiler_loads.index 531 | boiler_profiles_pu = n.loads_t.p_set[boiler_loads].div( 532 | n.loads_t.p_set[boiler_loads].max(), axis=1 533 | ) 534 | boiler_profiles_pu.columns = decentral_boilers 535 | boiler_profiles = DataArray( 536 | boiler_profiles_pu.multiply(n.links.loc[decentral_boilers, "p_nom"], axis=1) 537 | ) 538 | 539 | boiler_load_index = pd.Index(boiler_loads.unique()) 540 | boiler_load_index.name = "Load" 541 | 542 | # per load scaling factor 543 | n.model.add_variables(coords=[boiler_load_index], name="Load-profile_factor") 544 | 545 | # clumsy indicator matrix to map boilers to loads 546 | df = pd.DataFrame(index=boiler_load_index, columns=decentral_boilers, data=0.0) 547 | for k, v in boiler_loads.items(): 548 | df.loc[v, k] = 1.0 549 | 550 | lhs = n.model["Link-p"].loc[:, decentral_boilers] - ( 551 | boiler_profiles * DataArray(df) * n.model["Load-profile_factor"] 552 | ).sum("Load") 553 | 554 | n.model.add_constraints(lhs, "=", 0, "Link-fixed_profile") 555 | 556 | # hack so that PyPSA doesn't complain there is nowhere to store the variable 557 | n.loads["profile_factor_opt"] = 0.0 558 | 559 | 560 | def force_boiler_profiles_existing_per_boiler(n): 561 | """ 562 | This scales each boiler dispatch to be proportional to the load profile. 563 | """ 564 | 565 | logger.info( 566 | "Forcing each existing boiler dispatch to be proportional to the load profile" 567 | ) 568 | 569 | decentral_boilers = n.links.index[ 570 | n.links.carrier.str.contains("boiler") 571 | & ~n.links.carrier.str.contains("urban central") 572 | & ~n.links.p_nom_extendable 573 | ] 574 | 575 | if decentral_boilers.empty: 576 | return 577 | 578 | boiler_loads = n.links.loc[decentral_boilers, "bus1"] 579 | boiler_loads = boiler_loads[boiler_loads.isin(n.loads_t.p_set.columns)] 580 | decentral_boilers = boiler_loads.index 581 | boiler_profiles_pu = n.loads_t.p_set[boiler_loads].div( 582 | n.loads_t.p_set[boiler_loads].max(), axis=1 583 | ) 584 | boiler_profiles_pu.columns = decentral_boilers 585 | boiler_profiles = DataArray( 586 | boiler_profiles_pu.multiply(n.links.loc[decentral_boilers, "p_nom"], axis=1) 587 | ) 588 | 589 | # will be per unit 590 | n.model.add_variables(coords=[decentral_boilers], name="Link-fixed_profile_scaling") 591 | 592 | lhs = (1, n.model["Link-p"].loc[:, decentral_boilers]), ( 593 | -boiler_profiles, 594 | n.model["Link-fixed_profile_scaling"], 595 | ) 596 | 597 | n.model.add_constraints(lhs, "=", 0, "Link-fixed_profile_scaling") 598 | 599 | # hack so that PyPSA doesn't complain there is nowhere to store the variable 600 | n.links["fixed_profile_scaling_opt"] = 0.0 601 | 602 | 603 | def add_h2_derivate_limit(n, investment_year, limits_volume_max): 604 | 605 | for ct in limits_volume_max["h2_derivate_import"]: 606 | limit = limits_volume_max["h2_derivate_import"][ct][investment_year] * 1e6 607 | 608 | logger.info(f"limiting H2 derivate imports in {ct} to {limit/1e6} TWh/a") 609 | 610 | incoming = n.links.loc[ 611 | [ 612 | "EU renewable oil -> DE oil", 613 | "EU methanol -> DE methanol", 614 | "EU renewable gas -> DE gas", 615 | ] 616 | ].index 617 | outgoing = n.links.loc[ 618 | [ 619 | "DE renewable oil -> EU oil", 620 | "DE methanol -> EU methanol", 621 | "DE renewable gas -> EU gas", 622 | ] 623 | ].index 624 | 625 | incoming_p = ( 626 | n.model["Link-p"].loc[:, incoming] * n.snapshot_weightings.generators 627 | ).sum() 628 | outgoing_p = ( 629 | n.model["Link-p"].loc[:, outgoing] * n.snapshot_weightings.generators 630 | ).sum() 631 | 632 | lhs = incoming_p - outgoing_p 633 | 634 | cname = f"H2_derivate_import_limit-{ct}" 635 | 636 | n.model.add_constraints(lhs <= limit, name=f"GlobalConstraint-{cname}") 637 | 638 | if cname in n.global_constraints.index: 639 | logger.warning( 640 | f"Global constraint {cname} already exists. Dropping and adding it again." 641 | ) 642 | n.global_constraints.drop(cname, inplace=True) 643 | 644 | n.add( 645 | "GlobalConstraint", 646 | cname, 647 | constant=limit, 648 | sense="<=", 649 | type="", 650 | carrier_attribute="", 651 | ) 652 | 653 | 654 | def adapt_nuclear_output(n): 655 | 656 | logger.info( 657 | f"limiting german electricity generation from nuclear to 2020 value of 61 TWh" 658 | ) 659 | limit = 61e6 660 | 661 | nuclear_de_index = n.links.index[ 662 | (n.links.carrier == "nuclear") & (n.links.index.str[:2] == "DE") 663 | ] 664 | 665 | nuclear_gen = ( 666 | n.model["Link-p"].loc[:, nuclear_de_index] 667 | * n.links.loc[nuclear_de_index, "efficiency"] 668 | * n.snapshot_weightings.generators 669 | ).sum() 670 | 671 | lhs = nuclear_gen 672 | 673 | cname = f"Nuclear_generation_limit-DE" 674 | 675 | n.model.add_constraints(lhs <= limit, name=f"GlobalConstraint-{cname}") 676 | 677 | if cname in n.global_constraints.index: 678 | logger.warning( 679 | f"Global constraint {cname} already exists. Dropping and adding it again." 680 | ) 681 | n.global_constraints.drop(cname, inplace=True) 682 | 683 | n.add( 684 | "GlobalConstraint", 685 | cname, 686 | constant=limit, 687 | sense="<=", 688 | type="", 689 | carrier_attribute="", 690 | ) 691 | 692 | 693 | def additional_functionality(n, snapshots, snakemake): 694 | 695 | logger.info("Adding Ariadne-specific functionality") 696 | 697 | investment_year = int(snakemake.wildcards.planning_horizons[-4:]) 698 | constraints = snakemake.params.solving["constraints"] 699 | 700 | add_capacity_limits( 701 | n, investment_year, constraints["limits_capacity_min"], "minimum" 702 | ) 703 | 704 | add_capacity_limits( 705 | n, investment_year, constraints["limits_capacity_max"], "maximum" 706 | ) 707 | 708 | add_power_limits(n, investment_year, constraints["limits_power_max"]) 709 | 710 | if int(snakemake.wildcards.clusters) != 1: 711 | h2_import_limits(n, investment_year, constraints["limits_volume_max"]) 712 | 713 | electricity_import_limits(n, investment_year, constraints["limits_volume_max"]) 714 | 715 | if investment_year >= 2025: 716 | h2_production_limits( 717 | n, 718 | investment_year, 719 | constraints["limits_volume_min"], 720 | constraints["limits_volume_max"], 721 | ) 722 | 723 | if not snakemake.config["run"]["debug_h2deriv_limit"]: 724 | add_h2_derivate_limit(n, investment_year, constraints["limits_volume_max"]) 725 | 726 | # force_boiler_profiles_existing_per_load(n) 727 | force_boiler_profiles_existing_per_boiler(n) 728 | 729 | if isinstance(constraints["co2_budget_national"], dict): 730 | limit_countries = constraints["co2_budget_national"][investment_year] 731 | add_co2limit_country( 732 | n, 733 | limit_countries, 734 | snakemake, 735 | debug=snakemake.config["run"]["debug_co2_limit"], 736 | ) 737 | else: 738 | logger.warning("No national CO2 budget specified!") 739 | 740 | if investment_year == 2020: 741 | adapt_nuclear_output(n) 742 | -------------------------------------------------------------------------------- /workflow/scripts/build_egon_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2024- The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | Load and prepares the data of the eGo^N DemandRegio project 7 | about district heating in Germany on NUTS3 level. 8 | 9 | Inputs: 10 | - resources/nuts3.geojson: Path to the GeoJSON file containing NUTS3 regions data. 11 | - resources/mapping_technologies.json: Path to the JSON file containing the mapping of technologies. 12 | - resources/demandregio_spatial.json: Path to the JSON file containing spatially resolved heating structure data from the DemandRegio project. 13 | - resources/mapping_38_to_4.json: Path to the JSON file containing the mapping of regions. 14 | 15 | Outputs: 16 | - resources/heating_technologies_nuts3.geojson: Path to the GeoJSON file where the processed heating technologies data will be saved. 17 | """ 18 | 19 | import logging 20 | 21 | logger = logging.getLogger(__name__) 22 | import json 23 | import re 24 | 25 | import geopandas as gpd 26 | import pandas as pd 27 | from _helpers import configure_logging 28 | 29 | if __name__ == "__main__": 30 | if "snakemake" not in globals(): 31 | import os 32 | import sys 33 | 34 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 35 | 36 | path = "../submodules/pypsa-eur/scripts" 37 | sys.path.insert(0, os.path.abspath(path)) 38 | from _helpers import mock_snakemake 39 | 40 | snakemake = mock_snakemake( 41 | "build_egon_data", 42 | run="KN2045_Bal_v4", 43 | ) 44 | configure_logging(snakemake) 45 | 46 | logger.info("Retrieving and cleaning egon data") 47 | 48 | nuts3 = gpd.read_file(snakemake.input.nuts3)[ 49 | ["index", "pop", "geometry"] 50 | ] # Keep only necessary columns 51 | 52 | # Parse dictionary from string 53 | description = pd.read_json(snakemake.input.mapping_technologies).loc["resources"][ 54 | "oep_metadata" 55 | ][0]["schema"]["fields"][5]["description"] 56 | # Extract the part after the colon 57 | key_value_part = description.split(":", 1)[1].strip() 58 | 59 | # Split into individual key-value pairs 60 | pairs = re.split(r";\s*", key_value_part) 61 | 62 | # Initialize dictionary 63 | internal_id = {} 64 | 65 | # Process each pair 66 | for pair in pairs: 67 | key, value = pair.split(":", 1) 68 | internal_id[int(key.strip())] = value.strip() 69 | 70 | with open(snakemake.input.demandregio_spatial) as datafile: 71 | data = json.load(datafile)["data"] 72 | df = pd.DataFrame(data) 73 | 74 | id_region = pd.read_json(snakemake.input.mapping_38_to_4) 75 | 76 | df["internal_id"] = df["internal_id"].apply(lambda x: x[0]) 77 | 78 | df["nuts3"] = df.id_region.map(id_region.set_index(id_region.id_region_from).kuerzel_to) 79 | 80 | heat_tech_per_region = df.groupby([df.nuts3, df.internal_id]).sum().value.unstack() 81 | heat_tech_per_region.rename(columns=internal_id, inplace=True) 82 | 83 | egon_df = heat_tech_per_region.merge(nuts3, left_on="nuts3", right_on="index") 84 | egon_gdf = gpd.GeoDataFrame(egon_df) # Convert merged DataFrame to GeoDataFrame 85 | egon_gdf = egon_gdf.to_crs("EPSG:4326") 86 | 87 | egon_gdf.to_file(snakemake.output.heating_technologies_nuts3) 88 | -------------------------------------------------------------------------------- /workflow/scripts/build_existing_chp_de.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2024- The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | Using BNetzA data to get a high resolution map of German CHP plants. 7 | 8 | (https://open-mastr.readthedocs.io/en/latest/). 9 | """ 10 | 11 | import logging 12 | 13 | logger = logging.getLogger(__name__) 14 | import os 15 | import sys 16 | 17 | import geopandas as gpd 18 | import numpy as np 19 | import pandas as pd 20 | import pypsa 21 | from _helpers import configure_logging 22 | from powerplantmatching.export import map_country_bus 23 | 24 | 25 | def clean_data(combustion, biomass, geodata): 26 | """ 27 | Clean the data and return a dataframe with the relevant information. 28 | 29 | PLZ is translated to longitude and latitude using the pyGeoDb data. 30 | """ 31 | biomass.dropna(subset="Postleitzahl", inplace=True) 32 | biomass.rename( 33 | columns={"NameStromerzeugungseinheit": "NameKraftwerk"}, inplace=True 34 | ) 35 | biomass["Einsatzort"] = "" 36 | 37 | data = pd.concat([biomass, combustion], join="inner", ignore_index=True) 38 | 39 | data["IndustryStatus"] = data["Einsatzort"].str.contains("Industrie") 40 | data["IndustryStatus"] = data["IndustryStatus"].apply( 41 | lambda x: False if pd.isna(x) else x 42 | ) 43 | 44 | # Get only CHP plants 45 | CHP_raw = data.query("ThermischeNutzleistung > 0").copy() 46 | CHP_raw.NameKraftwerk = CHP_raw.apply( 47 | lambda x: x.EinheitMastrNummer if pd.isna(x.NameKraftwerk) else x.NameKraftwerk, 48 | axis=1, 49 | ) 50 | 51 | rename_columns = { 52 | "KwkMastrNummer": "ID", 53 | "NameKraftwerk": "Name", 54 | "Energietraeger": "Fueltype", 55 | "Technologie": "Technology", 56 | "ElektrischeKwkLeistung": "Capacity", 57 | "ThermischeNutzleistung": "Capacity_thermal", 58 | "Inbetriebnahmedatum": "DateIn", 59 | "DatumEndgueltigeStilllegung": "DateOut", 60 | "Postleitzahl": "Postleitzahl", 61 | "IndustryStatus": "Industry", 62 | } 63 | CHP_sel = CHP_raw[rename_columns.keys()].rename(columns=rename_columns) 64 | 65 | # change date format 66 | CHP_sel.DateIn = CHP_sel.DateIn.str[:4].astype(float) 67 | CHP_sel.DateOut = CHP_sel.DateOut.str[:4].astype(float) 68 | 69 | # delete duplicates identified by KwkMastrNummer 70 | strategies = { 71 | "Name": "first", 72 | "Fueltype": "first", 73 | "Technology": "first", 74 | "Capacity": "mean", # dataset duplicates full KWK capacity for each block 75 | "Capacity_thermal": "mean", # dataset duplicates full KWK capacity for each block 76 | "DateIn": "mean", 77 | "DateOut": "mean", 78 | "Postleitzahl": "first", 79 | "Industry": "first", 80 | } 81 | CHP_sel = CHP_sel.groupby("ID").agg(strategies).reset_index() 82 | 83 | # set missing information to match the powerplant data format 84 | CHP_sel[["Set", "Country", "Efficiency"]] = ["CHP", "DE", ""] 85 | CHP_sel[["lat", "lon"]] = [float("nan"), float("nan")] 86 | 87 | # get location from PLZ 88 | CHP_sel.fillna({"lat": CHP_sel.Postleitzahl.map(geodata.lat)}, inplace=True) 89 | CHP_sel.fillna({"lon": CHP_sel.Postleitzahl.map(geodata.lon)}, inplace=True) 90 | 91 | fueltype = { 92 | "Erdgas": "Natural Gas", 93 | "Mineralölprodukte": "Oil", 94 | "Steinkohle": "Coal", 95 | "Braunkohle": "Lignite", 96 | "andere Gase": "Natural Gas", 97 | "nicht biogenere Abfälle": "Waste", 98 | "nicht biogener Abfall": "Waste", 99 | "Wärme": "Other", 100 | "Biomasse": "Bioenergy", 101 | "Wasserstoff": "Hydrogen", 102 | } 103 | technology = { 104 | "Verbrennungsmotor": "", 105 | "Gasturbinen mit Abhitzekessel": "CCGT", 106 | "Brennstoffzelle": "Fuel Cell", 107 | "Strilingmotor": "", 108 | "Stirlingmotor": "", 109 | "Kondensationsmaschine mit Entnahme": "Steam Turbine", 110 | "Sonstige": "", 111 | "Gasturbinen ohne Abhitzekessel": "OCGT", 112 | "Dampfmotor": "Steam Turbine", 113 | "Gegendruckmaschine mit Entnahme": "Steam Turbine", 114 | "Gegendruckmaschine ohne Entnahme": "Steam Turbine", 115 | "Gasturbinen mit nachgeschalteter Dampfturbine": "CCGT", 116 | "ORC (Organic Rankine Cycle)-Anlage": "Steam Turbine", 117 | "Kondensationsmaschine ohne Entnahme": "Steam Turbine", 118 | } 119 | 120 | CHP_sel.replace({"Fueltype": fueltype, "Technology": technology}, inplace=True) 121 | 122 | def lookup_geodata(missing_plz): 123 | for i in range(10): 124 | plz = missing_plz[:-1] + str(i) 125 | if plz in geodata.index: 126 | return geodata.loc[plz] 127 | for i in range(100): 128 | prefix = "0" if i < 10 else "" 129 | plz = missing_plz[:-2] + prefix + str(i) 130 | if plz in geodata.index: 131 | return geodata.loc[plz] 132 | 133 | return pd.Series((pd.NA, pd.NA)) 134 | 135 | missing_i = CHP_sel.lat.isna() | CHP_sel.lon.isna() 136 | CHP_sel.loc[missing_i, ["lat", "lon"]] = CHP_sel.loc[ 137 | missing_i, "Postleitzahl" 138 | ].apply(lookup_geodata) 139 | 140 | cols = [ 141 | "Name", 142 | "Fueltype", 143 | "Technology", 144 | "Set", 145 | "Country", 146 | "Capacity", 147 | "Efficiency", 148 | "DateIn", 149 | "DateOut", 150 | "lat", 151 | "lon", 152 | "Capacity_thermal", 153 | "Industry", 154 | ] 155 | 156 | # convert unit of capacities from kW to MW 157 | CHP_sel.loc[:, ["Capacity", "Capacity_thermal"]] /= 1e3 158 | 159 | # add missing Fueltype for plants > 100 MW 160 | fuelmap = { 161 | "GuD Mitte": "Natural Gas", 162 | "HKW Mitte": "Natural Gas", 163 | "GuD Süd": "Natural Gas", 164 | "HKW Lichterfelde": "Natural Gas", 165 | "GuD Niehl 2 RheinEnergie": "Natural Gas", 166 | "HKW Marzahn": "Natural Gas", 167 | "Gasturbinen Heizkraftwerk Nossener Brücke": "Natural Gas", 168 | "SEE916495905242": "Natural Gas", 169 | "HKW Leipzig Nord": "Natural Gas", 170 | "HKW Reuter": "Waste", 171 | "Solvay Rb Kraftwerk": "Lignite", 172 | "GuD Süd Wolfsburg": "Natural Gas", 173 | "GuD Erfurt Ost": "Natural Gas", 174 | "KW Nord": "Natural Gas", 175 | "SEE904887370686": "Oil", 176 | "GuD2": "Natural Gas", 177 | "Heizkrafwerk Hafen der Stadtwerke Münster GmbH": "Waste", 178 | "Kraftwerk Ha": "Natural Gas", 179 | "Kraftwerk HA": "Natural Gas", 180 | "PKV Dampfsammelschienen-KWK-Anlage": "Natural Gas", 181 | } 182 | CHP_sel["Fueltype"] = ( 183 | CHP_sel["Name"].map(fuelmap).combine_first(CHP_sel["Fueltype"]) 184 | ) 185 | 186 | return CHP_sel[cols].copy() 187 | 188 | 189 | def calculate_efficiency(CHP_de): 190 | """ 191 | Calculate the efficiency of the CHP plants depending on Capacity and 192 | DateIn. 193 | 194 | Following Triebs et al. ( 195 | https://doi.org/10.1016/j.ecmx.2020.100068) 196 | """ 197 | 198 | def EXT(cap, year): 199 | # returns the efficiency for extraction condensing turbine 200 | return ((44 / 2400) * cap + 0.125 * year - 204.75) / 100 201 | 202 | def BP(cap, year): 203 | # returns the efficiency for back pressure turbine 204 | return ((5e-3) * cap + 0.325 * year - 611.75) / 100 205 | 206 | # TODO: differentiate between extraction condensing turbine and back pressure turbine 207 | CHP_de["Efficiency"] = CHP_de.apply( 208 | lambda row: BP(row["Capacity"], row["DateIn"]), axis=1 209 | ) 210 | 211 | return CHP_de 212 | 213 | 214 | if __name__ == "__main__": 215 | if "snakemake" not in globals(): 216 | path = "../submodules/pypsa-eur/scripts" 217 | sys.path.insert(0, os.path.abspath(path)) 218 | from _helpers import mock_snakemake 219 | 220 | snakemake = mock_snakemake( 221 | "build_existing_chp_de", 222 | clusters=27, 223 | run="KN2045_Bal_v4", 224 | ) 225 | 226 | configure_logging(snakemake) 227 | logger.info("Retrieving and cleaning CHP data from BNetzA") 228 | biomass = pd.read_csv( 229 | snakemake.input.mastr_biomass, dtype={"Postleitzahl": str}, low_memory=False 230 | ) 231 | combustion = pd.read_csv( 232 | snakemake.input.mastr_combustion, dtype={"Postleitzahl": str}, low_memory=False 233 | ) 234 | 235 | geodata = pd.read_csv( 236 | snakemake.input.plz_mapping, 237 | index_col="plz", 238 | dtype={"plz": str}, 239 | names=["plz", "lat", "lon"], 240 | skiprows=1, 241 | ) 242 | 243 | logger.info("Cleaning data") 244 | CHP_de = clean_data(combustion, biomass, geodata) 245 | 246 | logger.info( 247 | "Calculating efficiency of CHP plants depending on capacity and build year." 248 | ) 249 | CHP_de = calculate_efficiency(CHP_de) 250 | 251 | logger.info("Mapping CHP plants to regions") 252 | regions = gpd.read_file(snakemake.input.regions).set_index("name") 253 | geometry = gpd.points_from_xy(CHP_de["lon"], CHP_de["lat"]) 254 | gdf = gpd.GeoDataFrame(geometry=geometry, crs=4326) 255 | CHP_de["bus"] = gpd.sjoin_nearest(gdf, regions, how="left")["name"] 256 | 257 | CHP_de.to_csv(snakemake.output.german_chp, index=False) 258 | -------------------------------------------------------------------------------- /workflow/scripts/build_mobility_demand.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | 4 | import pandas as pd 5 | 6 | logger = logging.getLogger(__name__) 7 | from _helpers import configure_logging 8 | 9 | 10 | def get_transport_data(db): 11 | """ 12 | Retrieve the German mobility demand from the transport_data model. 13 | 14 | Sum over the subsectors Bus, LDV, Rail, and Truck for the fuels 15 | electricity, hydrogen, and synthetic fuels. 16 | """ 17 | # get transport_data data 18 | 19 | df = db.loc[snakemake.params.leitmodelle["transport"]] 20 | 21 | subsectors = ["Bus", "LDV", "Rail", "Truck"] 22 | fuels = ["Electricity", "Hydrogen", "Liquids"] 23 | 24 | transport_demand = pd.Series(0.0, index=fuels) 25 | 26 | for fuel in fuels: 27 | for subsector in subsectors: 28 | key = f"Final Energy|Transportation|{subsector}|{fuel}" 29 | if snakemake.params.db_name == "ariadne": 30 | transport_demand.loc[fuel] += df.get((key, "TWh/yr"), 0.0) * 3.6 31 | else: 32 | transport_demand.loc[fuel] += df.loc[key]["PJ/yr"] 33 | 34 | transport_demand = transport_demand.div(3.6e-6) # convert PJ to MWh 35 | 36 | if "transport_stock" in snakemake.params.leitmodelle: 37 | df = db.loc[snakemake.params.leitmodelle["transport_stock"]] 38 | 39 | transport_demand["number_of_cars"] = df.loc[ 40 | "Stock|Transportation|LDV|BEV", "million" 41 | ] 42 | 43 | return transport_demand 44 | 45 | 46 | if __name__ == "__main__": 47 | if "snakemake" not in globals(): 48 | import os 49 | import sys 50 | 51 | path = "../submodules/pypsa-eur/scripts" 52 | sys.path.insert(0, os.path.abspath(path)) 53 | from _helpers import mock_snakemake 54 | 55 | snakemake = mock_snakemake( 56 | "build_mobility_demand", 57 | simpl="", 58 | clusters=22, 59 | opts="", 60 | ll="vopt", 61 | sector_opts="none", 62 | planning_horizons="2020", 63 | run="KN2045_Bal_v4", 64 | ) 65 | configure_logging(snakemake) 66 | 67 | db = pd.read_csv( 68 | snakemake.input.ariadne, 69 | index_col=["model", "scenario", "region", "variable", "unit"], 70 | ).loc[ 71 | :, 72 | snakemake.params.reference_scenario, 73 | "Deutschland", 74 | :, 75 | :, 76 | ][ 77 | snakemake.wildcards.planning_horizons 78 | ] 79 | 80 | logger.info( 81 | f"Retrieving German mobility demand from {snakemake.params.leitmodelle["transport"]} transport model." 82 | ) 83 | # get transport_data data 84 | transport_data = get_transport_data(db) 85 | 86 | # get German mobility weighting 87 | pop_layout = pd.read_csv(snakemake.input.clustered_pop_layout, index_col=0) 88 | # only get German data 89 | pop_layout = pop_layout[pop_layout.ct == "DE"].fraction 90 | 91 | mobility_demand = pd.DataFrame( 92 | pop_layout.values[:, None] * transport_data.values, 93 | index=pop_layout.index, 94 | columns=transport_data.index, 95 | ) 96 | 97 | mobility_demand.to_csv(snakemake.output.mobility_demand) 98 | -------------------------------------------------------------------------------- /workflow/scripts/build_scenarios.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2024- The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | # This script reads in data from the IIASA database to create the scenario.yaml file 7 | import logging 8 | 9 | from _helpers import configure_logging 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | import os 14 | from pathlib import Path 15 | 16 | import pandas as pd 17 | import ruamel.yaml 18 | 19 | 20 | def get_transport_growth(df, planning_horizons): 21 | try: 22 | aviation = df.loc["Final Energy|Bunkers|Aviation", "PJ/yr"] 23 | except KeyError: 24 | aviation = df.loc["Final Energy|Bunkers|Aviation", "TWh/yr"] * 3.6 # TWh to PJ 25 | 26 | aviation_growth_factor = aviation / aviation[2020] 27 | 28 | return aviation_growth_factor[planning_horizons] 29 | 30 | 31 | def get_primary_steel_share(df, planning_horizons): 32 | # Get share of primary steel production 33 | model = snakemake.params.leitmodelle["industry"] 34 | total_steel = df.loc[model, "Production|Steel"] 35 | primary_steel = df.loc[model, "Production|Steel|Primary"] 36 | 37 | primary_steel_share = primary_steel / total_steel 38 | primary_steel_share = primary_steel_share[planning_horizons] 39 | 40 | if ( 41 | model == "FORECAST v1.0" 42 | and (planning_horizons[0] == 2020) 43 | and snakemake.params.db_name == "ariadne2_intern" 44 | ): 45 | logger.warning( 46 | "FORECAST v1.0 does not have data for 2020. Using 2021 data for Production|Steel instead." 47 | ) 48 | primary_steel_share[2020] = primary_steel[2021] / total_steel[2021] 49 | 50 | return primary_steel_share.set_index(pd.Index(["Primary_Steel_Share"])) 51 | 52 | 53 | def get_DRI_share(df, planning_horizons): 54 | # Get share of DRI steel production 55 | model = "FORECAST v1.0" 56 | total_steel = df.loc[model, "Production|Steel|Primary"] 57 | # Assuming that only hydrogen DRI steel is sustainable and DRI using natural gas is phased out 58 | DRI_steel = df.loc[model, "Production|Steel|Primary|Direct Reduction Hydrogen"] 59 | 60 | DRI_steel_share = DRI_steel / total_steel 61 | 62 | if model == "FORECAST v1.0" and planning_horizons[0] == 2020: 63 | logger.warning( 64 | "FORECAST v1.0 does not have data for 2020. Using 2021 data for DRI fraction instead." 65 | ) 66 | DRI_steel_share[2020] = DRI_steel_share[2021] / total_steel[2021] 67 | 68 | DRI_steel_share = DRI_steel_share[planning_horizons] 69 | 70 | return DRI_steel_share.set_index(pd.Index(["DRI_Steel_Share"])) 71 | 72 | 73 | def get_co2_budget(df, source): 74 | # relative to the DE emissions in 1990 *including bunkers*; also 75 | # account for non-CO2 GHG and allow extra room for international 76 | # bunkers which are excluded from the national targets 77 | 78 | # Baseline emission in DE in 1990 in Mt as understood by the KSG and by PyPSA 79 | baseline_co2 = 1251 80 | baseline_pypsa = 1052 81 | if source == "KSG": 82 | ## GHG targets according to KSG 83 | initial_years_co2 = pd.Series( 84 | index=[2020, 2025, 2030], 85 | data=[813, 643, 438], 86 | ) 87 | 88 | later_years_co2 = pd.Series( 89 | index=[2035, 2040, 2045, 2050], 90 | data=[0.77, 0.88, 1.0, 1.0], 91 | ) 92 | 93 | targets_co2 = pd.concat( 94 | [initial_years_co2, (1 - later_years_co2) * baseline_co2], 95 | ) 96 | elif source == "UBA": 97 | ## For Zielverfehlungsszenarien use UBA Projektionsbericht 98 | targets_co2 = pd.Series( 99 | index=[2020, 2025, 2030, 2035, 2040, 2045, 2050], 100 | data=[813, 655, 455, 309, 210, 169, 157], 101 | ) 102 | else: 103 | raise ValueError("Invalid source for CO2 budget.") 104 | ## Compute nonco2 from Ariadne-Leitmodell (REMIND) 105 | 106 | # co2 = ( 107 | # df.loc["Emissions|CO2 incl Bunkers","Mt CO2/yr"] 108 | # - df.loc["Emissions|CO2|Land-Use Change","Mt CO2-equiv/yr"] 109 | # - df.loc["Emissions|CO2|Energy|Demand|Bunkers","Mt CO2/yr"] 110 | # ) 111 | # ghg = ( 112 | # df.loc["Emissions|Kyoto Gases","Mt CO2-equiv/yr"] 113 | # - df.loc["Emissions|Kyoto Gases|Land-Use Change","Mt CO2-equiv/yr"] 114 | # # No Kyoto Gas emissions for Bunkers recorded in Ariadne DB 115 | # ) 116 | 117 | try: 118 | co2_land_use_change = df.loc["Emissions|CO2|Land-Use Change", "Mt CO2-equiv/yr"] 119 | except KeyError: # Key not in Ariadne public database 120 | co2_land_use_change = df.loc["Emissions|CO2|AFOLU", "Mt CO2/yr"] 121 | 122 | co2 = df.loc["Emissions|CO2", "Mt CO2/yr"] - co2_land_use_change 123 | 124 | try: 125 | kyoto_land_use_change = df.loc[ 126 | "Emissions|Kyoto Gases|Land-Use Change", "Mt CO2-equiv/yr" 127 | ] 128 | except KeyError: # Key not in Ariadne public database 129 | # Guesstimate of difference from Ariadne 2 data 130 | kyoto_land_use_change = co2_land_use_change + 4.5 131 | 132 | ghg = df.loc["Emissions|Kyoto Gases", "Mt CO2-equiv/yr"] - kyoto_land_use_change 133 | 134 | nonco2 = ghg - co2 135 | 136 | ## PyPSA disregards nonco2 GHG emissions, but includes bunkers 137 | 138 | targets_pypsa = targets_co2 - nonco2 139 | 140 | target_fractions_pypsa = targets_pypsa.loc[targets_co2.index] / baseline_pypsa 141 | 142 | return target_fractions_pypsa.round(3) 143 | 144 | 145 | def write_to_scenario_yaml(input, output, scenarios, df): 146 | # read in yaml file 147 | yaml = ruamel.yaml.YAML() 148 | file_path = Path(input) 149 | config = yaml.load(file_path) 150 | for scenario in scenarios: 151 | reference_scenario = config[scenario]["iiasa_database"]["reference_scenario"] 152 | fallback_reference_scenario = config[scenario]["iiasa_database"][ 153 | "fallback_reference_scenario" 154 | ] 155 | 156 | planning_horizons = [ 157 | 2020, 158 | 2025, 159 | 2030, 160 | 2035, 161 | 2040, 162 | 2045, 163 | ] # for 2050 we still need data 164 | 165 | aviation_demand_factor = get_transport_growth( 166 | df.loc[snakemake.params.leitmodelle["transport"], reference_scenario, :], 167 | planning_horizons, 168 | ) 169 | 170 | if reference_scenario.startswith( 171 | "KN2045plus" 172 | ): # Still waiting for REMIND uploads 173 | fallback_reference_scenario = reference_scenario 174 | 175 | co2_budget_source = config[scenario]["co2_budget_DE_source"] 176 | 177 | if fallback_reference_scenario != reference_scenario: 178 | logger.warning( 179 | f"For CO2 budget: Using {fallback_reference_scenario} as fallback reference scenario for {scenario}." 180 | ) 181 | co2_budget_fractions = get_co2_budget( 182 | df.loc[ 183 | snakemake.params.leitmodelle["general"], fallback_reference_scenario 184 | ], 185 | co2_budget_source, 186 | ) 187 | 188 | if not config[scenario].get("sector"): 189 | config[scenario]["sector"] = {} 190 | 191 | config[scenario]["sector"]["aviation_demand_factor"] = {} 192 | for year in planning_horizons: 193 | config[scenario]["sector"]["aviation_demand_factor"][year] = round( 194 | aviation_demand_factor.loc[year].item(), 4 195 | ) 196 | 197 | if not snakemake.params.db_name == "ariadne": 198 | st_primary_fraction = get_primary_steel_share( 199 | df.loc[:, reference_scenario, :], planning_horizons 200 | ) 201 | 202 | dri_fraction = get_DRI_share( 203 | df.loc[:, reference_scenario, :], planning_horizons 204 | ) 205 | 206 | config[scenario]["industry"]["St_primary_fraction"] = {} 207 | config[scenario]["industry"]["DRI_fraction"] = {} 208 | for year in st_primary_fraction.columns: 209 | config[scenario]["industry"]["St_primary_fraction"][year] = round( 210 | st_primary_fraction.loc["Primary_Steel_Share", year].item(), 4 211 | ) 212 | config[scenario]["industry"]["DRI_fraction"][year] = round( 213 | dri_fraction.loc["DRI_Steel_Share", year].item(), 4 214 | ) 215 | 216 | config[scenario]["solving"]["constraints"]["co2_budget_national"] = {} 217 | for year, target in co2_budget_fractions.items(): 218 | config[scenario]["solving"]["constraints"]["co2_budget_national"][year] = {} 219 | config[scenario]["solving"]["constraints"]["co2_budget_national"][year][ 220 | "DE" 221 | ] = target 222 | 223 | # write back to yaml file 224 | yaml.dump(config, Path(output)) 225 | 226 | 227 | if __name__ == "__main__": 228 | if "snakemake" not in globals(): 229 | import os 230 | import sys 231 | 232 | path = "../submodules/pypsa-eur/scripts" 233 | sys.path.insert(0, os.path.abspath(path)) 234 | from _helpers import mock_snakemake 235 | 236 | snakemake = mock_snakemake("build_scenarios") 237 | 238 | configure_logging(snakemake) 239 | # Set USERNAME and PASSWORD for the Ariadne DB 240 | ariadne_db = pd.read_csv( 241 | snakemake.input.ariadne_database, 242 | index_col=["model", "scenario", "region", "variable", "unit"], 243 | ) 244 | ariadne_db.columns = ariadne_db.columns.astype(int) 245 | 246 | df = ariadne_db.loc[:, :, "Deutschland"] 247 | 248 | scenarios = snakemake.params.scenarios 249 | 250 | input = snakemake.input.scenario_yaml 251 | output = snakemake.output.scenario_yaml 252 | 253 | # for scenario in scenarios: 254 | write_to_scenario_yaml(input, output, scenarios, df) 255 | -------------------------------------------------------------------------------- /workflow/scripts/build_wasserstoff_kernnetz.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2020-2023 The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | Preprocess hydrogen kernnetz based on data from FNB Gas 7 | (https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/). 8 | """ 9 | 10 | import logging 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | import os 15 | import sys 16 | import uuid 17 | 18 | import geopandas as gpd 19 | import numpy as np 20 | import pandas as pd 21 | from pypsa.geo import haversine_pts 22 | from shapely import wkt 23 | from shapely.geometry import LineString, Point, Polygon 24 | from shapely.ops import nearest_points 25 | 26 | paths = ["workflow/submodules/pypsa-eur/scripts", "../submodules/pypsa-eur/scripts"] 27 | for path in paths: 28 | sys.path.insert(0, os.path.abspath(path)) 29 | from _helpers import configure_logging 30 | from build_gas_network import diameter_to_capacity 31 | 32 | MANUAL_ADDRESSES = { 33 | "Oude Statenzijl": (7.205108658430258, 53.20183834422634), 34 | "Helgoland": (7.882663327316698, 54.183393795580166), 35 | "SEN-1": (6.5, 55.0), 36 | "AWZ": (14.220711180456643, 54.429208831326804), 37 | "Bremen": (8.795818388451732, 53.077669699449594), 38 | "Bad Lauchstädt": (11.869106908389433, 51.38797498313352), 39 | "Großkugel": (12.151584743366769, 51.4166927585755), 40 | "Bobbau": (12.269345975889912, 51.69045938775995), 41 | "Visbeck": (8.310468203836264, 52.834518912466216), 42 | "Elbe-Süd": (9.608042769377906, 53.57422954537108), 43 | "Salzgitter": (10.386847343138689, 52.13861418123843), 44 | "Wefensleben": (11.15557835653467, 52.176005656180244), 45 | "Fessenheim": (7.5352027843079, 47.91300212650956), 46 | "Hittistetten": (10.09644829589717, 48.32667870548472), 47 | "Lindau": (9.690886766574819, 47.55387858107057), 48 | "Ludwigshafen": (8.444314472678961, 49.477207809634784), 49 | "Niederhohndorf": (12.466430165766688, 50.7532612203904), 50 | "Rückersdorf": (12.21941992347776, 50.822251899358236), 51 | "Bissingen": (10.6158383, 48.7177493), 52 | "Rehden": (8.476178919627396, 52.60675277527164), 53 | "Eynatten": (6.083339457526605, 50.69260916361823), 54 | "Vlieghuis": (6.8382504272201095, 52.66036497820981), 55 | "Kalle": (6.921180663621839, 52.573992586428425), 56 | "Carling": (6.713267207127634, 49.16738919353264), 57 | "Legden": (7.099754098013676, 52.03269789265483), 58 | "Ledgen": (7.099754098013676, 52.03269789265483), 59 | "Reiningen": (8.374879149975513, 52.50849502371421), 60 | "Buchholz": (12.929212986885771, 52.15737808332214), 61 | "Sandkrug": (8.257391972093515, 53.05387937393471), 62 | } 63 | 64 | 65 | def diameter_to_capacity_h2(pipe_diameter_mm): 66 | """ 67 | Calculate pipe capacity in MW based on diameter in mm. Linear 68 | interpolation. 69 | 70 | 20 inch (500 mm) 50 bar -> 1.2 GW H2 pipe capacity (LHV) 71 | 36 inch (900 mm) 50 bar -> 4.7 GW H2 pipe capacity (LHV) 72 | 48 inch (1200mm) 80 bar -> 13.0 GW H2 pipe capacity (LHV) 73 | 74 | old source: table 4 of 75 | https://ehb.eu/files/downloads/EHB-Analysing-the-future-demand-supply-and-transport-of-hydrogen-June-2021-v3.pdf 76 | new source: https://github.com/PyPSA/pypsa-ariadne/pull/167 77 | """ 78 | # slopes definitions 79 | m0 = (1200 - 0) / (500 - 0) 80 | m1 = (4700 - 1200) / (900 - 500) 81 | m2 = (13000 - 4700) / (1200 - 900) 82 | # intercepts 83 | a0 = 0 84 | a1 = 1200 - m1 * 500 85 | a2 = 4700 - m2 * 900 86 | 87 | if pipe_diameter_mm < 500: 88 | return a0 + m0 * pipe_diameter_mm 89 | elif pipe_diameter_mm < 900: 90 | return a1 + m1 * pipe_diameter_mm 91 | else: 92 | return a2 + m2 * pipe_diameter_mm 93 | 94 | 95 | def load_and_merge_raw(fn1, fn2, fn3): 96 | # load, clean and merge 97 | 98 | # potential further projects 99 | df_po = pd.read_excel(fn1, skiprows=2, skipfooter=2) 100 | # Neubau 101 | df_ne = pd.read_excel(fn2, skiprows=3, skipfooter=4) 102 | # Umstellung (retrofit) 103 | df_re = pd.read_excel(fn3, skiprows=3, skipfooter=10) 104 | 105 | for df in [df_po, df_ne, df_re]: 106 | df.columns = [c.replace("\n", "") for c in df.columns.values.astype(str)] 107 | 108 | # clean first dataset 109 | # drop lines not in Kernetz 110 | df_po.drop(index=0, inplace=True) 111 | df_po = df_po[df_po["Berücksichtigung im Kernnetz [ja/nein/zurückgezogen]"] == "ja"] 112 | 113 | to_keep = [ 114 | "Name (Lfd.Nr.-Von-Nach)", 115 | "Umstellungsdatum/ Planerische Inbetriebnahme", 116 | "Anfangspunkt(Ort)", 117 | "Endpunkt(Ort)", 118 | "Nenndurchmesser (DN)", 119 | "Länge (km)", 120 | "Druckstufe (DP)[mind. 30 barg]", 121 | "Bundesland", 122 | "Bestand/Umstellung/Neubau", 123 | "IPCEI-Projekt(ja/ nein)", 124 | "IPCEI-Projekt(Name/ nein)", 125 | "Investitionskosten(Mio. Euro),Kostenschätzung", 126 | "PCI-Projekt beantragt Dezember 2022(Name/ nein)", 127 | ] 128 | 129 | to_rename = { 130 | "Name (Lfd.Nr.-Von-Nach)": "name", 131 | "Umstellungsdatum/ Planerische Inbetriebnahme": "build_year", 132 | "Nenndurchmesser (DN)": "diameter_mm", 133 | "Länge (km)": "length", 134 | "Druckstufe (DP)[mind. 30 barg]": "max_pressure_bar", 135 | "Bestand/Umstellung/Neubau": "retrofitted", 136 | "IPCEI-Projekt(ja/ nein)": "ipcei", 137 | "IPCEI-Projekt(Name/ nein)": "ipcei_name", 138 | "Investitionskosten(Mio. Euro),Kostenschätzung": "investment_costs (Mio. Euro)", 139 | "PCI-Projekt beantragt Dezember 2022(Name/ nein)": "pci", 140 | } 141 | 142 | df_po = df_po[to_keep].rename(columns=to_rename) 143 | 144 | # extract info on retrofitted 145 | df_po["retrofitted"] = df_po.retrofitted != "Neubau" 146 | 147 | # clean second dataset 148 | # select only pipes 149 | df_ne = df_ne[df_ne["Maßnahmenart"] == "Leitung"] 150 | 151 | to_keep = [ 152 | "Name", 153 | "Planerische Inbetriebnahme", 154 | "Anfangspunkt(Ort)", 155 | "Endpunkt(Ort)", 156 | "Nenndurchmesser (DN)", 157 | "Länge (km)", 158 | "Druckstufe (DP)[mind. 30 barg]", 159 | "Bundesland", 160 | "retrofitted", 161 | "IPCEI-Projekt(Name/ nein)", 162 | "Investitionskosten*(Mio. Euro)", 163 | "PCI-Projekt bestätigt April 2024(Name/ nein)", 164 | ] 165 | 166 | to_rename = { 167 | "Name": "name", 168 | "Planerische Inbetriebnahme": "build_year", 169 | "Nenndurchmesser (DN)": "diameter_mm", 170 | "Länge (km)": "length", 171 | "Druckstufe (DP)[mind. 30 barg]": "max_pressure_bar", 172 | "IPCEI-Projekt(Name/ nein)": "ipcei_name", 173 | "Investitionskosten*(Mio. Euro)": "investment_costs (Mio. Euro)", 174 | "PCI-Projekt bestätigt April 2024(Name/ nein)": "pci", 175 | } 176 | 177 | df_ne["retrofitted"] = False 178 | df_re["retrofitted"] = True 179 | df_ne_re = pd.concat([df_ne, df_re])[to_keep].rename(columns=to_rename) 180 | df = pd.concat([df_po, df_ne_re]) 181 | df.reset_index(drop=True, inplace=True) 182 | 183 | return df 184 | 185 | 186 | def prepare_dataset(df): 187 | 188 | df = df.copy() 189 | 190 | # clean length 191 | df["length"] = pd.to_numeric(df["length"], errors="coerce") 192 | df = df.dropna(subset=["length"]) 193 | 194 | # clean diameter 195 | df.diameter_mm = ( 196 | df.diameter_mm.astype(str) 197 | .str.extractall(r"(\d+)") 198 | .groupby(level=0) 199 | .last() 200 | .astype(int) 201 | ) 202 | 203 | # clean max pressure 204 | df.max_pressure_bar = ( 205 | df.max_pressure_bar.astype(str) 206 | .str.extractall(r"(\d+[.,]?\d*)") 207 | .groupby(level=0) 208 | .last() 209 | .squeeze() 210 | .str.replace(",", ".") 211 | .astype(float) 212 | ) 213 | 214 | # clean build_year 215 | df.build_year = ( 216 | df.build_year.astype(str).str.extract(r"(\b\d{4}\b)").astype(float).fillna(2032) 217 | ) 218 | 219 | # create bidirectional and set true 220 | df["bidirectional"] = True 221 | 222 | df[["BL1", "BL2"]] = ( 223 | df["Bundesland"] 224 | .apply(lambda bl: [bl.split("/")[0].strip(), bl.split("/")[-1].strip()]) 225 | .apply(pd.Series) 226 | ) 227 | 228 | # calc capa 229 | df["p_nom"] = df.diameter_mm.apply(diameter_to_capacity_h2) 230 | 231 | # eliminated gas capacity from retrofitted pipes 232 | df["removed_gas_cap"] = df.diameter_mm.apply(diameter_to_capacity) 233 | df[df.retrofitted == False]["removed_gas_cap"] == 0 234 | 235 | # eliminate leading and trailing spaces 236 | df["Anfangspunkt(Ort)"] = df["Anfangspunkt(Ort)"].str.strip() 237 | df["Endpunkt(Ort)"] = df["Endpunkt(Ort)"].str.strip() 238 | 239 | # drop pipes with same start and end 240 | df = df[df["Anfangspunkt(Ort)"] != df["Endpunkt(Ort)"]] 241 | 242 | # drop pipes with length smaller than 5 km 243 | df = df[df.length > 5] 244 | 245 | # clean ipcei and pci entry 246 | df["ipcei"] = df["ipcei"].fillna(df["ipcei_name"]) 247 | df["ipcei"] = df["ipcei"].replace( 248 | {"nein": "no", "indirekter Partner": "no", "Nein": "no"} 249 | ) 250 | df["pci"] = df["pci"].replace({"nein": "no"}) 251 | 252 | # reindex 253 | df.reset_index(drop=True, inplace=True) 254 | 255 | return df 256 | 257 | 258 | def geocode_locations(df): 259 | 260 | df = df.copy() 261 | 262 | try: 263 | from geopy.extra.rate_limiter import RateLimiter 264 | from geopy.geocoders import Nominatim 265 | except: 266 | raise ModuleNotFoundError( 267 | "Optional dependency 'geopy' not found." 268 | "Install via 'conda install -c conda-forge geopy'" 269 | ) 270 | 271 | locator = Nominatim(user_agent=str(uuid.uuid4())) 272 | geocode = RateLimiter(locator.geocode, min_delay_seconds=2) 273 | # load state data for checking 274 | gdf_state = gpd.read_file(snakemake.input.gadm).set_index("GID_1") 275 | 276 | def get_location(row): 277 | def get_loc_A(loc="location", add_info=""): 278 | loc_A = Point( 279 | gpd.tools.geocode(row[loc] + ", " + add_info, timeout=7)["geometry"][0] 280 | ) 281 | return loc_A 282 | 283 | def get_loc_B(loc="location", add_info=""): 284 | loc_B = geocode([row[loc], add_info], timeout=7) 285 | if loc_B is not None: 286 | loc_B = Point(loc_B.longitude, loc_B.latitude) 287 | else: 288 | loc_B = Point(0, 0) 289 | return loc_B 290 | 291 | def is_in_state(point, state="state"): 292 | if (row[state] in gdf_state.NAME_1.tolist()) & (point is not None): 293 | polygon_geometry = gdf_state[ 294 | gdf_state.NAME_1 == row[state] 295 | ].geometry.squeeze() 296 | return point.within(polygon_geometry) 297 | else: 298 | return False 299 | 300 | loc = get_loc_A("location", "Deutschland") 301 | 302 | # check if location is in Bundesland 303 | if not is_in_state(loc, "state"): 304 | # check if other loc is in Bundesland 305 | loc = get_loc_B("location", "Deutschland") 306 | # if both methods do not return loc in Bundesland, add Bundesland info 307 | if not is_in_state(loc, "state"): 308 | loc = get_loc_A("location", row["state"] + ", Deutschland") 309 | # if no location in Bundesland can be found 310 | if not is_in_state(loc, "state"): 311 | loc = Point(0, 0) 312 | 313 | return loc 314 | 315 | # extract locations and state 316 | locations1, locations2 = ( 317 | df[["Anfangspunkt(Ort)", "BL1"]], 318 | df[["Endpunkt(Ort)", "BL2"]], 319 | ) 320 | locations1.columns, locations2.columns = ["location", "state"], [ 321 | "location", 322 | "state", 323 | ] 324 | locations = pd.concat([locations1, locations2], axis=0) 325 | locations.drop_duplicates(inplace=True) 326 | 327 | # (3min) 328 | locations["point"] = locations.apply(lambda row: get_location(row), axis=1) 329 | 330 | # map manual locations (NOT FOUND OR WRONG) 331 | locations.point = locations.apply( 332 | lambda row: Point( 333 | MANUAL_ADDRESSES.get(row.location) 334 | if row.location in MANUAL_ADDRESSES.keys() 335 | else row.point 336 | ), 337 | axis=1, 338 | ) 339 | 340 | return locations 341 | 342 | 343 | def assign_locations(df, locations): 344 | 345 | df = df.copy() 346 | 347 | # manual cleaning 348 | df.loc[ 349 | (df["Endpunkt(Ort)"] == "Wettringen") & (df["BL2"] == "Niedersachsen"), "BL2" 350 | ] = "Nordrhein-Westfalen" 351 | 352 | df["point0"] = pd.merge( 353 | df, 354 | locations, 355 | left_on=["Anfangspunkt(Ort)", "BL1"], 356 | right_on=["location", "state"], 357 | how="left", 358 | )["point"] 359 | df["point1"] = pd.merge( 360 | df, 361 | locations, 362 | left_on=["Endpunkt(Ort)", "BL2"], 363 | right_on=["location", "state"], 364 | how="left", 365 | )["point"] 366 | 367 | # calc length of points 368 | length_factor = 1.0 369 | df["length_haversine"] = df.apply( 370 | lambda p: length_factor 371 | * haversine_pts([p.point0.x, p.point0.y], [p.point1.x, p.point1.y]), 372 | axis=1, 373 | ) 374 | 375 | # calc length ratio 376 | df["length_ratio"] = df.apply( 377 | lambda row: max(row.length, row.length_haversine) 378 | / (min(row.length, row.length_haversine) + 1), 379 | axis=1, 380 | ) 381 | 382 | # only keep pipes with realistic length ratio 383 | df = df.query("retrofitted or length_ratio <= 2").copy() 384 | 385 | # calc LineString 386 | df["geometry"] = df.apply(lambda x: LineString([x["point0"], x["point1"]]), axis=1) 387 | 388 | return df 389 | 390 | 391 | def concat_gdf(gdf_list, crs="EPSG:4326"): 392 | """ 393 | Concatenate multiple geopandas dataframes with common coordinate reference 394 | system (crs). 395 | """ 396 | return gpd.GeoDataFrame(pd.concat(gdf_list), crs=crs) 397 | 398 | 399 | def load_bus_regions(onshore_path, offshore_path): 400 | """ 401 | Load pypsa-eur on- and offshore regions and concat. 402 | """ 403 | bus_regions_offshore = gpd.read_file(offshore_path) 404 | bus_regions_onshore = gpd.read_file(onshore_path) 405 | bus_regions = concat_gdf([bus_regions_offshore, bus_regions_onshore]) 406 | bus_regions = bus_regions.dissolve(by="name", aggfunc="first") 407 | 408 | return bus_regions 409 | 410 | 411 | def find_point_across_ring(point, ring, distance=1000): 412 | if isinstance(point, gpd.GeoSeries): 413 | point = point.iloc[0] 414 | elif isinstance(point, gpd.GeoDataFrame): 415 | point = point.geometry.iloc[0] 416 | 417 | if isinstance(ring, gpd.GeoSeries): 418 | ring = ring.iloc[0] 419 | elif isinstance(ring, gpd.GeoDataFrame): 420 | ring = ring.geometry.iloc[0] 421 | 422 | nearest_point_on_ring = nearest_points(ring, point)[0] 423 | direction = np.array(point.coords[0]) - np.array(nearest_point_on_ring.coords[0]) 424 | direction_normalized = direction / np.linalg.norm(direction) 425 | new_point_coords = ( 426 | np.array(nearest_point_on_ring.coords[0]) - direction_normalized * distance 427 | ) 428 | 429 | return Point(new_point_coords) 430 | 431 | 432 | def create_border_crossing(wkn, regions_onshore, regions_offshore): 433 | 434 | # get shapes 435 | regions = load_bus_regions(regions_onshore, regions_offshore) 436 | 437 | # extract DE border 438 | de = regions[regions.country.isin(["DE"])].union_all() 439 | de_border = de.exterior 440 | de_border_projected = gpd.GeoSeries([de_border], crs="EPSG:4326").to_crs(epsg=32633) 441 | de_polygon = Polygon(de_border_projected.iloc[0]) 442 | 443 | # project to calc distance in meters 444 | wkn_proj = gpd.GeoDataFrame(wkn, geometry="point0", crs="EPSG:4326").to_crs( 445 | epsg=32633 446 | ) 447 | wkn_proj["point1"] = gpd.GeoDataFrame( 448 | wkn, geometry="point1", crs="EPSG:4326" 449 | ).to_crs(epsg=32633)["point1"] 450 | 451 | wkn_proj["point0_dist"] = ( 452 | wkn_proj["point0"].distance(de_border_projected.iloc[0]) / 1e3 453 | ) # in km 454 | wkn_proj["point1_dist"] = ( 455 | wkn_proj["point1"].distance(de_border_projected.iloc[0]) / 1e3 456 | ) 457 | 458 | wkn_proj["point0_in"] = wkn_proj.point0.within(de_polygon) 459 | wkn_proj["point1_in"] = wkn_proj.point1.within(de_polygon) 460 | 461 | # flter border points 462 | border_distance = 3 # km 463 | wkn_proj_sub = wkn_proj[ 464 | ( 465 | (wkn_proj["point0_dist"] < border_distance) 466 | | (wkn_proj["point1_dist"] < border_distance) 467 | ) 468 | & (wkn_proj["point0_in"] == wkn_proj["point1_in"]) 469 | ] 470 | 471 | # manually delete pipes that are not indicated for imports at all (according to source data) 472 | names = [ 473 | "18-Fürstenhausen-Carling", 474 | "Emden-Ost-Nüttermoor", 475 | "Lengthal-Burgkirchen", 476 | "Hittistetten-Lindau", 477 | "Hüthum-Praest", 478 | ] 479 | wkn_proj_sub = wkn_proj_sub[~wkn_proj_sub.name.isin(names)] 480 | 481 | # find points across the border 482 | for idx, row in wkn_proj_sub.iterrows(): 483 | col = "point0" if row["point0_dist"] < row["point1_dist"] else "point1" 484 | wkn_proj_sub.loc[idx, col] = find_point_across_ring( 485 | row[col], de_border_projected.iloc[0], distance=5000 486 | ) 487 | 488 | # convert back to xy coordinates and calc linestring 489 | wkn_proj_sub["point0_epsg4326"] = wkn_proj_sub["point0"].to_crs(epsg=4326) 490 | wkn_proj_sub["point1_epsg4326"] = wkn_proj_sub["point1"].to_crs(epsg=4326) 491 | wkn_proj_sub["geometry"] = wkn_proj_sub.apply( 492 | lambda x: LineString([x["point0_epsg4326"], x["point1_epsg4326"]]), axis=1 493 | ) 494 | 495 | # paste data back to original dataframe 496 | wkn.loc[wkn_proj_sub.index, "point0"] = wkn_proj_sub["point0"] 497 | wkn.loc[wkn_proj_sub.index, "point1"] = wkn_proj_sub["point1"] 498 | wkn.loc[wkn_proj_sub.index, "geometry"] = wkn_proj_sub["geometry"] 499 | 500 | return wkn 501 | 502 | 503 | def filter_kernnetz( 504 | wkn, ipcei_pci_only=False, cutoff_year=2050, force_all_ipcei_pci=False 505 | ): 506 | """ 507 | Filters the projects in the wkn DataFrame based on IPCEI participation and 508 | build years. 509 | 510 | Parameters: 511 | wkn : DataFrame 512 | The DataFrame containing project data for Wasserstoff Kernnetz. 513 | 514 | ipcei_pci_only : bool, optional (default: False) 515 | If True, only projects that are part of IPCEI and PCI are considered for inclusion. 516 | 517 | cutoff_year : int, optional (default: 2050) 518 | The latest year by which projects can be built. Projects with a 'build_year' later than the 519 | cutoff year will be excluded unless `force_all_ipcei_pci` is set to True. 520 | 521 | force_all_ipcei_pci : bool, optional (default: False) 522 | If True, IPCEI and PCI projects are included, even if their 'build_year' exceeds the cutoff year, 523 | but non-IPCEI and non-PCI projects are still excluded beyond the cutoff year. 524 | 525 | Returns: 526 | DataFrame 527 | A filtered DataFrame based on the provided conditions. 528 | """ 529 | 530 | # Filter for only IPCEI projects if ipcei_only is True 531 | if ipcei_pci_only: 532 | logger.info("Filtering for IPCEI and PCI projects only") 533 | wkn = wkn.query("(ipcei != 'no') or (pci != 'no')") 534 | 535 | # Apply the logic when force_all_ipcei is True 536 | if force_all_ipcei_pci: 537 | # Keep all IPCEI projects regardless of cutoff, but restrict non-IPCEI projects to cutoff year 538 | logger.info( 539 | f"Forcing all IPCEI and PCI projects to be included until {cutoff_year}" 540 | ) 541 | wkn = wkn.query( 542 | "(build_year <= @cutoff_year) or (ipcei != 'no') or (pci != 'no')" 543 | ) 544 | else: 545 | # Default filtering, exclude all projects beyond the cutoff year 546 | logger.info(f"Filtering for projects built until {cutoff_year}") 547 | wkn = wkn.query("build_year <= @cutoff_year") 548 | 549 | return wkn 550 | 551 | 552 | if __name__ == "__main__": 553 | if "snakemake" not in globals(): 554 | import os 555 | import sys 556 | 557 | path = "../submodules/pypsa-eur/scripts" 558 | sys.path.insert(0, os.path.abspath(path)) 559 | from _helpers import mock_snakemake 560 | 561 | snakemake = mock_snakemake("build_wasserstoff_kernnetz") 562 | 563 | configure_logging(snakemake) 564 | kernnetz_cf = snakemake.params.kernnetz 565 | 566 | logger.info("Collecting raw data from FNB Gas") 567 | wasserstoff_kernnetz = load_and_merge_raw( 568 | snakemake.input.wasserstoff_kernnetz_1, 569 | snakemake.input.wasserstoff_kernnetz_2, 570 | snakemake.input.wasserstoff_kernnetz_3, 571 | ) 572 | logger.info("Data retrievel successful. Preparing dataset ...") 573 | 574 | wasserstoff_kernnetz = prepare_dataset(wasserstoff_kernnetz) 575 | 576 | if kernnetz_cf["reload_locations"]: 577 | locations = geocode_locations(wasserstoff_kernnetz) 578 | else: 579 | locations = pd.read_csv(snakemake.input.locations, index_col=0) 580 | locations["point"] = locations["point"].apply(wkt.loads) 581 | 582 | wasserstoff_kernnetz = assign_locations(wasserstoff_kernnetz, locations) 583 | 584 | if kernnetz_cf["border_crossing"]: 585 | wasserstoff_kernnetz = create_border_crossing( 586 | wasserstoff_kernnetz, 587 | snakemake.input.regions_onshore, 588 | snakemake.input.regions_offshore, 589 | ) 590 | 591 | wasserstoff_kernnetz = filter_kernnetz( 592 | wasserstoff_kernnetz, 593 | kernnetz_cf["ipcei_pci_only"], 594 | kernnetz_cf["cutoff_year"], 595 | kernnetz_cf["force_all_ipcei_pci"], 596 | ) 597 | 598 | wasserstoff_kernnetz.to_csv(snakemake.output.cleaned_wasserstoff_kernnetz) 599 | -------------------------------------------------------------------------------- /workflow/scripts/cluster_wasserstoff_kernnetz.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2024 The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | Cluster Wasserstoff Kernnetz to clustered model regions. 7 | """ 8 | 9 | import logging 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | import os 14 | import sys 15 | 16 | import geopandas as gpd 17 | import pandas as pd 18 | import pyproj 19 | from pypsa.geo import haversine_pts 20 | from shapely import wkt 21 | from shapely.geometry import LineString, Point 22 | from shapely.ops import transform 23 | 24 | paths = ["workflow/submodules/pypsa-eur/scripts", "../submodules/pypsa-eur/scripts"] 25 | for path in paths: 26 | sys.path.insert(0, os.path.abspath(path)) 27 | from _helpers import configure_logging 28 | from cluster_gas_network import load_bus_regions, reindex_pipes 29 | 30 | # Define a function for projecting points to meters 31 | project_to_meters = pyproj.Transformer.from_proj( 32 | pyproj.Proj("epsg:4326"), # assuming WGS84 33 | pyproj.Proj(proj="utm", zone=33, ellps="WGS84"), # adjust the projection as needed 34 | always_xy=True, 35 | ).transform 36 | 37 | # Define a function for projecting points back to decimal degrees 38 | project_to_degrees = pyproj.Transformer.from_proj( 39 | pyproj.Proj(proj="utm", zone=33, ellps="WGS84"), # adjust the projection as needed 40 | pyproj.Proj("epsg:4326"), 41 | always_xy=True, 42 | ).transform 43 | 44 | 45 | def split_line_by_length(line, segment_length_km): 46 | """ 47 | Split a Shapely LineString into segments of a specified length. 48 | 49 | Parameters: 50 | - line (LineString): The original LineString to be split. 51 | - segment_length_km (float): The desired length of each resulting segment in kilometers. 52 | 53 | Returns: 54 | list: A list of Shapely LineString objects representing the segments. 55 | """ 56 | 57 | # Convert segment length from kilometers to meters 58 | segment_length_meters = segment_length_km * 1000 59 | 60 | # Project the LineString to a suitable metric projection 61 | projected_line = transform(project_to_meters, line) 62 | 63 | total_length = projected_line.length 64 | num_segments = int(total_length / segment_length_meters) 65 | 66 | # Return early if no segmentation required 67 | if num_segments <= 1: 68 | return [line] 69 | 70 | segments = [] 71 | for i in range(1, num_segments + 1): 72 | start_point = projected_line.interpolate((i - 1) * segment_length_meters) 73 | end_point = projected_line.interpolate(i * segment_length_meters) 74 | 75 | # Extract x and y coordinates from the tuples 76 | start_point_coords = (start_point.x, start_point.y) 77 | end_point_coords = (end_point.x, end_point.y) 78 | 79 | # Create Shapely Point objects 80 | start_point_degrees = Point(start_point_coords) 81 | end_point_degrees = Point(end_point_coords) 82 | 83 | # Project the points back to decimal degrees 84 | start_point_degrees = transform(project_to_degrees, start_point_degrees) 85 | end_point_degrees = transform(project_to_degrees, end_point_degrees) 86 | 87 | # last point without interpolation 88 | if i == num_segments: 89 | end_point_degrees = Point(line.coords[-1]) 90 | 91 | segment = LineString([start_point_degrees, end_point_degrees]) 92 | segments.append(segment) 93 | 94 | return segments 95 | 96 | 97 | def divide_pipes(df, segment_length=10): 98 | """ 99 | Divide a GeoPandas DataFrame of LineString geometries into segments of a 100 | specified length. 101 | 102 | Parameters: 103 | - df (GeoDataFrame): The input DataFrame containing LineString geometries. 104 | - segment_length (float): The desired length of each resulting segment in kilometers. 105 | 106 | Returns: 107 | GeoDataFrame: A new GeoDataFrame with additional rows representing the segmented pipes. 108 | """ 109 | 110 | result = pd.DataFrame(columns=df.columns) 111 | 112 | for index, pipe in df.iterrows(): 113 | segments = split_line_by_length(pipe.geometry, segment_length) 114 | 115 | for i, segment in enumerate(segments): 116 | res_row = pipe.copy() 117 | res_row.geometry = segment 118 | res_row.point0 = Point(segment.coords[0]) 119 | res_row.point1 = Point(segment.coords[1]) 120 | res_row.length_haversine = segment_length 121 | result.loc[f"{index}-{i}"] = res_row 122 | 123 | return result 124 | 125 | 126 | def build_clustered_h2_network( 127 | df, bus_regions, recalculate_length=True, length_factor=1.25 128 | ): 129 | for i in [0, 1]: 130 | gdf = gpd.GeoDataFrame(geometry=df[f"point{i}"], crs="EPSG:4326") 131 | 132 | bus_mapping = gpd.sjoin(gdf, bus_regions, how="left", predicate="within")[ 133 | "name" 134 | ] 135 | bus_mapping = bus_mapping.groupby(bus_mapping.index).first() 136 | 137 | df[f"bus{i}"] = bus_mapping 138 | 139 | df[f"point{i}"] = df[f"bus{i}"].map( 140 | bus_regions.to_crs(3035).centroid.to_crs(4326) 141 | ) 142 | 143 | # drop pipes where not both buses are inside regions 144 | df = df.loc[~df.bus0.isna() & ~df.bus1.isna()] 145 | 146 | # drop pipes within the same region 147 | df = df.loc[df.bus1 != df.bus0] 148 | 149 | if df.empty: 150 | return df 151 | 152 | if recalculate_length: 153 | logger.info("Recalculating pipe lengths as center to center * length factor") 154 | # recalculate lengths as center to center * length factor 155 | df["length"] = df.apply( 156 | lambda p: length_factor 157 | * haversine_pts([p.point0.x, p.point0.y], [p.point1.x, p.point1.y]), 158 | axis=1, 159 | ) 160 | 161 | # tidy and create new numbered index 162 | df.drop(["point0", "point1"], axis=1, inplace=True) 163 | df.reset_index(drop=True, inplace=True) 164 | 165 | return df 166 | 167 | 168 | def aggregate_parallel_pipes(df, aggregate_build_years="mean"): 169 | strategies = { 170 | "bus0": "first", 171 | "bus1": "first", 172 | "p_nom": "sum", 173 | "p_nom_diameter": "sum", 174 | "max_pressure_bar": "mean", 175 | "build_year": aggregate_build_years, 176 | "diameter_mm": "mean", 177 | "length": "mean", 178 | "name": " ".join, 179 | "p_min_pu": "min", 180 | "investment_costs (Mio. Euro)": "sum", 181 | "removed_gas_cap": "sum", 182 | "ipcei": " ".join, 183 | "pci": " ".join, 184 | "retrofitted": lambda x: (x.sum() / len(x)) 185 | > 0.6, # consider as retrofit if more than 60% of pipes are retrofitted (relevant for costs) 186 | } 187 | return df.groupby(df.index).agg(strategies) 188 | 189 | 190 | if __name__ == "__main__": 191 | if "snakemake" not in globals(): 192 | import os 193 | import sys 194 | 195 | path = "../submodules/pypsa-eur/scripts" 196 | sys.path.insert(0, os.path.abspath(path)) 197 | from _helpers import mock_snakemake 198 | 199 | snakemake = mock_snakemake( 200 | "cluster_wasserstoff_kernnetz", 201 | simpl="", 202 | clusters=27, 203 | run="KN2045_Bal_v4", 204 | opts="", 205 | ll="vopt", 206 | sector_opts="none", 207 | planning_horizons="2020", 208 | ) 209 | 210 | configure_logging(snakemake) 211 | 212 | fn = snakemake.input.cleaned_h2_network 213 | df = pd.read_csv(fn, index_col=0) 214 | for col in ["point0", "point1", "geometry"]: 215 | df[col] = df[col].apply(wkt.loads) 216 | 217 | bus_regions = load_bus_regions( 218 | snakemake.input.regions_onshore, snakemake.input.regions_offshore 219 | ) 220 | logger.info(f"Clustering Wasserstoff Kernnetz for {list(bus_regions.index)}") 221 | kernnetz_cf = snakemake.params.kernnetz 222 | 223 | if kernnetz_cf["divide_pipes"]: 224 | segment_length = kernnetz_cf["pipes_segment_length"] 225 | df = divide_pipes(df, segment_length=segment_length) 226 | 227 | wasserstoff_kernnetz = build_clustered_h2_network( 228 | df, 229 | bus_regions, 230 | recalculate_length=kernnetz_cf["recalculate_length"], 231 | length_factor=1.25, 232 | ) 233 | 234 | if kernnetz_cf["divide_pipes"] & (not kernnetz_cf["aggregate_parallel_pipes"]): 235 | # Set length to 0 for duplicates from the 2nd occurrence onwards and make name unique 236 | logger.info( 237 | f"Setting length to 0 for splitted pipes as Kernnetz pipes are segmented (divide pipes: {kernnetz_cf["divide_pipes"]}) and paralle pipes not aggregated (aggregate_parallel_pipes: {kernnetz_cf["aggregate_parallel_pipes"]})." 238 | ) 239 | wasserstoff_kernnetz["occurrence"] = ( 240 | wasserstoff_kernnetz.groupby("name").cumcount() + 1 241 | ) 242 | wasserstoff_kernnetz.loc[wasserstoff_kernnetz["occurrence"] > 1, "length"] = 0 243 | wasserstoff_kernnetz["name"] = wasserstoff_kernnetz.apply( 244 | lambda row: ( 245 | f"{row['name']}-split{row['occurrence']}" 246 | if row["occurrence"] > 1 247 | else row["name"] 248 | ), 249 | axis=1, 250 | ) 251 | wasserstoff_kernnetz = wasserstoff_kernnetz.drop(columns="occurrence") 252 | 253 | if not wasserstoff_kernnetz.empty: 254 | wasserstoff_kernnetz[["bus0", "bus1"]] = ( 255 | wasserstoff_kernnetz[["bus0", "bus1"]] 256 | .apply(sorted, axis=1) 257 | .apply(pd.Series) 258 | ) 259 | 260 | wasserstoff_kernnetz["p_min_pu"] = 0 261 | wasserstoff_kernnetz["p_nom_diameter"] = 0 262 | 263 | if kernnetz_cf["aggregate_parallel_pipes"]: 264 | 265 | reindex_pipes(wasserstoff_kernnetz, prefix="H2 pipeline") 266 | wasserstoff_kernnetz = aggregate_parallel_pipes( 267 | wasserstoff_kernnetz, kernnetz_cf["aggregate_build_years"] 268 | ) 269 | 270 | else: 271 | wasserstoff_kernnetz.index = wasserstoff_kernnetz.name.astype(str) 272 | 273 | wasserstoff_kernnetz.to_csv(snakemake.output.clustered_h2_network) 274 | -------------------------------------------------------------------------------- /workflow/scripts/modify_cost_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | import re 6 | 7 | import numpy as np 8 | import pandas as pd 9 | from _helpers import configure_logging 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def carbon_component_fossils(costs, co2_price): 15 | """ 16 | Add carbon component to fossil fuel costs. 17 | """ 18 | 19 | carriers = ["gas", "oil", "lignite", "coal"] 20 | # specific emissions in tons CO2/MWh according to n.links[n.links.carrier =="your_carrier].efficiency2.unique().item() 21 | specific_emisisons = { 22 | "oil": 0.2571, 23 | "gas": 0.198, # OCGT 24 | "coal": 0.3361, 25 | "lignite": 0.4069, 26 | } 27 | 28 | for c in carriers: 29 | carbon_add_on = specific_emisisons[c] * co2_price 30 | costs.at[(c, "fuel"), "value"] += carbon_add_on 31 | add_str = f" (added carbon component of {round(carbon_add_on,4)} €/MWh according to co2 price of {co2_price} €/t co2 and carbon intensity of {specific_emisisons[c]} t co2/MWh)" 32 | if pd.isna(costs.at[(c, "fuel"), "further description"]): 33 | costs.at[(c, "fuel"), "further description"] = add_str 34 | else: 35 | costs.at[(c, "fuel"), "further description"] = ( 36 | str(costs.at[(c, "fuel"), "further description"]) + add_str 37 | ) 38 | 39 | return costs 40 | 41 | 42 | if __name__ == "__main__": 43 | if "snakemake" not in globals(): 44 | import sys 45 | 46 | path = "../submodules/pypsa-eur/scripts" 47 | sys.path.insert(0, os.path.abspath(path)) 48 | from _helpers import mock_snakemake 49 | 50 | snakemake = mock_snakemake( 51 | "modify_cost_data", 52 | planning_horizons="2020", 53 | file_path="../data/costs/", 54 | file_name="costs_2020.csv", 55 | cost_horizon="mean", 56 | run="KN2045_Bal_v4", 57 | ) 58 | configure_logging(snakemake) 59 | 60 | # read in cost data from technology-data library 61 | costs = os.path.join( 62 | snakemake.params.file_path, 63 | snakemake.params.cost_horizon, 64 | snakemake.params.file_name, 65 | ) 66 | 67 | # cost_horizon is a setting for technology-data and specifies either 68 | # mean, pessimist or optimist cost scenarios 69 | # the cost modifications file contains specific cost assumptions for 70 | # germany, developed in the ARIADNE project 71 | # here pessimist and optimistic scenarios correspond to a delay or a 72 | # speed up in cost reductions 73 | 74 | costs = pd.read_csv(costs, index_col=[0, 1]).sort_index() 75 | 76 | matched_year = int( 77 | re.search( 78 | r"costs_(\d{4})-modifications\.csv", snakemake.input.modifications 79 | ).group(1) 80 | ) 81 | 82 | if matched_year <= 2020 or snakemake.params.cost_horizon == "mean": 83 | logger.info(f"Mean cost scenario for {matched_year}.") 84 | new_year = matched_year 85 | elif snakemake.params.cost_horizon == "pessimist": 86 | logger.info(f"Pessimistic cost scenario for {matched_year}.") 87 | new_year = matched_year + 5 88 | elif snakemake.params.cost_horizon == "optimist": 89 | logger.info(f"Optimistic cost scenario for {matched_year}.") 90 | new_year = matched_year - 5 91 | else: 92 | logger.error( 93 | "Invalid specification of cost options. Please choose 'mean', 'pessimist' or 'optimist' as config[costs][horizon]." 94 | ) 95 | raise ValueError("Invalid specification of cost options.") 96 | 97 | new_filename = re.sub( 98 | r"costs_\d{4}-modifications\.csv", 99 | f"costs_{new_year}-modifications.csv", 100 | snakemake.input.modifications, 101 | ) 102 | modifications = pd.read_csv(new_filename, index_col=[0, 1]).sort_index() 103 | if snakemake.params.NEP == 2021: 104 | modifications = modifications.query("source != 'NEP2023'") 105 | elif snakemake.params.NEP == 2023: 106 | modifications = modifications.query("source != 'NEP2021'") 107 | else: 108 | logger.warning( 109 | f"NEP year {snakemake.params.NEP} is not in modifications file. Falling back to NEP2021." 110 | ) 111 | modifications = modifications.query("source != 'NEP2023'") 112 | 113 | costs.loc[modifications.index] = modifications 114 | logger.info( 115 | f"Modifications to the following technologies are applied:\n{list(costs.loc[modifications.index].index.get_level_values(0))}." 116 | ) 117 | 118 | # add carbon component to fossil fuel costs 119 | investment_year = int(snakemake.wildcards.planning_horizons[-4:]) 120 | if (snakemake.params.co2_price_add_on_fossils is not None) and ( 121 | investment_year in snakemake.params.co2_price_add_on_fossils.keys() 122 | ): 123 | co2_price = snakemake.params.co2_price_add_on_fossils[investment_year] 124 | logger.info( 125 | f"Adding carbon component according to a co2 price of {co2_price} €/t to fossil fuel costs." 126 | ) 127 | costs = carbon_component_fossils(costs, co2_price) 128 | 129 | logger.info( 130 | f"Scaling onwind costs towards Fh-ISE for Germany: {costs.loc["onwind", "investment"].value} {costs.loc['onwind', 'investment'].unit}." 131 | ) 132 | # https://github.com/PyPSA/pypsa-ariadne/issues/179 133 | # https://www.ise.fraunhofer.de/de/veroeffentlichungen/studien/studie-stromgestehungskosten-erneuerbare-energien.html 134 | costs.at[("onwind", "investment"), "value"] *= 1.12 135 | 136 | # Assumption based on doi:10.1016/j.rser.2019.109506 137 | costs.at[("biomass boiler", "pelletizing cost"), "value"] += 8.8 138 | logger.info( 139 | f"Adding transport costs of 8.8 EUR/MWh to solid biomass pelletizing costs. New value: {costs.loc['biomass boiler', 'pelletizing cost'].value} {costs.loc['biomass boiler', 'pelletizing cost'].unit}." 140 | ) 141 | 142 | # Klimaschutz- und Energieagentur Baden-Württemberg (KEA) Technikkatalog 143 | 144 | costs.at[("central water tank storage", "investment"), "value"] *= ( 145 | 1.12 / 0.6133 146 | ) # KEA costs / 2020 costs 147 | logger.info( 148 | f"Scaling central water tank storage investment costs to KEA Technikkatalog: {costs.loc['central water tank storage', 'investment'].value} {costs.loc['central water tank storage', 'investment'].unit}." 149 | ) 150 | 151 | # increase central gas CHP lifetime to 40 years 152 | costs.at[("central gas CHP", "lifetime"), "value"] = 40 153 | logger.info( 154 | f"Setting lifetime of central gas CHP to {costs.at[("central gas CHP" , "lifetime") , "value"]} {costs.at[("central gas CHP" , "lifetime") , "unit"]}." 155 | ) 156 | 157 | costs.to_csv(snakemake.output[0]) 158 | -------------------------------------------------------------------------------- /workflow/scripts/modify_district_heat_share.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2024- The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | This script modifies district heating shares based on eGo^N data for NUTS3 7 | regions in Germany. 8 | 9 | Inputs: 10 | - resources/heating_technologies_nuts3.geojson: Path to the GeoJSON file containing heating technologies data for NUTS3 regions. 11 | - resources/regions_onshore.geojson: Path to the GeoJSON file containing onshore regions data. 12 | - resources/district_heat_share.csv: Path to the CSV file containing district heating shares. 13 | 14 | Outputs: 15 | - resources/updated_district_heat_share.csv: Path to the CSV file where the updated district heating shares will be saved. 16 | 17 | Parameters: 18 | - sector.district_heating["potential"]: Maximum potential district heating share. 19 | - sector.district_heating["progress"]: Progress of district heating share over planning horizons. 20 | - wildcards.planning_horizons: Planning horizon year. 21 | """ 22 | 23 | import logging 24 | 25 | logger = logging.getLogger(__name__) 26 | import geopandas as gpd 27 | import pandas as pd 28 | from shapely.geometry import Point 29 | 30 | 31 | def cluster_egon(heat_techs, regions_onshore): 32 | """ 33 | Map NUTS3 regions of egon data to corresponding clusters according to 34 | maximum overlap. 35 | 36 | Inputs: 37 | - heat_techs (GeoDataFrame): GeoDataFrame containing heating technologies data for NUTS3 regions. 38 | - regions_onshore (GeoDataFrame): GeoDataFrame containing onshore regions data of network clusters. 39 | 40 | Outputs: 41 | - GeoDataFrame: Updated GeoDataFrame with NUTS3 regions aggregated according to cluster structure. 42 | """ 43 | 44 | regions_onshore.set_index("name", inplace=True) 45 | 46 | # Map NUTS3 regions of egon data to corresponding clusters according to maximum overlap 47 | 48 | heat_techs["cluster"] = heat_techs.apply( 49 | lambda x: regions_onshore.geometry.intersection(x.geometry).area.idxmax(), 50 | axis=1, 51 | ) 52 | 53 | # Group and aggregate by cluster 54 | heat_techs_clustered = heat_techs.groupby("cluster").sum(numeric_only=True) 55 | 56 | return heat_techs_clustered 57 | 58 | 59 | def update_district_heat_share(heat_techs_clustered, dh_shares): 60 | """ 61 | Update district heating demands of clusters according to shares in eGo^N 62 | data on NUTS3 level for Germany taking into account expansion of systems. 63 | 64 | Inputs: 65 | - heat_techs_clustered (GeoDataFrame): GeoDataFrame containing clustered heating technologies data. 66 | - dh_shares (DataFrame): DataFrame containing district heating shares and urban fractions to be updated. 67 | 68 | Outputs: 69 | - DataFrame: Updated DataFrame with adjusted district heating shares and urban fractions. 70 | """ 71 | 72 | nodal_dh_shares = heat_techs_clustered[ 73 | "Fernwaerme" 74 | ] / heat_techs_clustered.drop( # Fernwaerme is the German term for district heating 75 | "pop", axis=1 76 | ).sum( 77 | axis=1 78 | ) 79 | 80 | urban_fraction = dh_shares["urban fraction"] 81 | max_dh_share = snakemake.params.district_heating["potential"] 82 | progress = snakemake.params.district_heating["progress"][ 83 | int(snakemake.wildcards.planning_horizons) 84 | ] 85 | 86 | diff = ((urban_fraction * max_dh_share) - nodal_dh_shares).clip(lower=0).dropna() 87 | nodal_dh_shares += diff * progress 88 | nodal_dh_shares = nodal_dh_shares.filter(regex="DE") 89 | dh_shares.loc[nodal_dh_shares.index, "district fraction of node"] = nodal_dh_shares 90 | dh_shares.loc[nodal_dh_shares.index, "urban fraction"] = pd.concat( 91 | [urban_fraction.loc[nodal_dh_shares.index], nodal_dh_shares], axis=1 92 | ).max(axis=1) 93 | 94 | return dh_shares 95 | 96 | 97 | if __name__ == "__main__": 98 | if "snakemake" not in globals(): 99 | import os 100 | import sys 101 | 102 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 103 | 104 | path = "../submodules/pypsa-eur/scripts" 105 | sys.path.insert(0, os.path.abspath(path)) 106 | from _helpers import mock_snakemake 107 | 108 | snakemake = mock_snakemake( 109 | "modify_district_heat_share", 110 | simpl="", 111 | clusters=44, 112 | opts="", 113 | ll="vopt", 114 | sector_opts="none", 115 | planning_horizons="2020", 116 | run="KN2045_Bal_v4", 117 | ) 118 | 119 | logging.basicConfig(level=snakemake.config["logging"]["level"]) 120 | logger.info("Updating district heating shares with egon data") 121 | 122 | heat_techs = gpd.read_file(snakemake.input.heating_technologies_nuts3) 123 | regions_onshore = gpd.read_file(snakemake.input.regions_onshore) 124 | dh_shares = pd.read_csv(snakemake.input.district_heat_share, index_col=0) 125 | 126 | heat_techs_clustered = cluster_egon(heat_techs, regions_onshore) 127 | 128 | dh_shares = update_district_heat_share(heat_techs_clustered, dh_shares) 129 | 130 | dh_shares.to_csv(snakemake.output.district_heat_share) 131 | -------------------------------------------------------------------------------- /workflow/scripts/modify_existing_heating.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | 4 | import pandas as pd 5 | from _helpers import configure_logging 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | if __name__ == "__main__": 11 | if "snakemake" not in globals(): 12 | import os 13 | import sys 14 | 15 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 16 | 17 | path = "../submodules/pypsa-eur/scripts" 18 | sys.path.insert(0, os.path.abspath(path)) 19 | from _helpers import mock_snakemake 20 | 21 | snakemake = mock_snakemake( 22 | "modify_existing_heating", 23 | run="KN2045_Bal_v4", 24 | ) 25 | 26 | configure_logging(snakemake) 27 | 28 | leitmodell = snakemake.params.leitmodelle["buildings"] 29 | logger.info(f"Using {leitmodell} for heating demand modification.") 30 | 31 | existing_heating = pd.read_csv(snakemake.input.existing_heating, index_col=0) 32 | 33 | ariadne = pd.read_csv( 34 | snakemake.input.ariadne, 35 | index_col=["model", "scenario", "region", "variable", "unit"], 36 | ).loc[ 37 | leitmodell, 38 | snakemake.params.fallback_reference_scenario, 39 | "Deutschland", 40 | :, 41 | "million", 42 | ] 43 | 44 | logger.info(f"Heating demand before modification:{existing_heating.loc['Germany']}") 45 | 46 | mapping = { 47 | "gas boiler": "Gas Boiler", 48 | "oil boiler": "Oil Boiler", 49 | "air heat pump": "Heat Pump|Electrical|Air", 50 | "ground heat pump": "Heat Pump|Electrical|Ground", 51 | "biomass boiler": "Biomass Boiler", 52 | } 53 | 54 | new_values = pd.Series() 55 | 56 | year = "2020" 57 | for tech in mapping: 58 | stock = ariadne.at[ 59 | f"Stock|Space Heating|{mapping[tech]}", 60 | year, 61 | ] 62 | 63 | peak = ( 64 | stock 65 | * existing_heating.loc["Germany"].sum() 66 | / ariadne.at[f"Stock|Space Heating", year] 67 | ) 68 | new_values[tech] = peak 69 | 70 | if any(new_values.isna()): 71 | logger.warning( 72 | f"Missing values for {new_values[new_values.isna()].index.to_list()}. Switching to hard coded values from a previous REMod run." 73 | ) 74 | 75 | total_stock = 23.28 # million 76 | existing_factor = existing_heating.loc["Germany"].sum() / total_stock 77 | 78 | new_values["gas boiler"] = 11.44 79 | new_values["oil boiler"] = 5.99 80 | new_values["air heat pump"] = 0.38 81 | new_values["ground heat pump"] = 0.38 82 | new_values["biomass boiler"] = 2.8 83 | 84 | logger.info(new_values) 85 | logger.warning(f"Total stock: {total_stock}, New stock: {new_values.sum()}") 86 | logger.warning( 87 | f"District heating is not correctly accounted for in the new stock." 88 | ) 89 | new_values *= existing_factor 90 | 91 | for tech, peak in new_values.items(): 92 | existing_heating.at["Germany", tech] = peak 93 | 94 | logger.info(f"Heating demand after modification: {existing_heating.loc['Germany']}") 95 | 96 | existing_heating.to_csv(snakemake.output.existing_heating) 97 | -------------------------------------------------------------------------------- /workflow/scripts/modify_industry_demand.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2023-2024 The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | This script modifies the industrial production values to match the FORECAST 7 | model. 8 | 9 | This includes 10 | - Production|Non-Metallic Minerals|Cement 11 | - Production|Steel 12 | - Production|Chemicals|Ammonia 13 | - Production|Chemicals|Methanol 14 | - Production|Non-Ferrous Metals 15 | - Production|Pulp and Paper 16 | """ 17 | import logging 18 | 19 | logger = logging.getLogger(__name__) 20 | import pandas as pd 21 | from _helpers import configure_logging 22 | 23 | if __name__ == "__main__": 24 | if "snakemake" not in globals(): 25 | import os 26 | import sys 27 | 28 | path = "../submodules/pypsa-eur/scripts" 29 | sys.path.insert(0, os.path.abspath(path)) 30 | from _helpers import mock_snakemake 31 | 32 | snakemake = mock_snakemake( 33 | "modify_industry_demand", 34 | simpl="", 35 | clusters=22, 36 | opts="", 37 | ll="vopt", 38 | sector_opts="None", 39 | run="KN2045_Bal_v4", 40 | planning_horizons=2020, 41 | ) 42 | 43 | configure_logging(snakemake) 44 | # leitmodell for industry demand 45 | leitmodell = "FORECAST v1.0" 46 | 47 | year = snakemake.input.industrial_production_per_country_tomorrow.split("_")[ 48 | -1 49 | ].split(".")[0] 50 | if snakemake.params.db_name == "ariadne2_intern" and year == "2020": 51 | logger.warning(f"Assuming {leitmodell} uses 2021 as base year instead of 2020.") 52 | year = "2021" 53 | existing_industry = pd.read_csv( 54 | snakemake.input.industrial_production_per_country_tomorrow, index_col=0 55 | ) 56 | 57 | # read in ariadne database 58 | ariadne = ( 59 | pd.read_csv( 60 | snakemake.input.ariadne, 61 | index_col=["model", "scenario", "region", "variable", "unit"], 62 | ) 63 | .loc[ 64 | leitmodell, 65 | snakemake.params.reference_scenario, 66 | "Deutschland", 67 | :, 68 | "Mt/yr", 69 | ] 70 | .multiply(1000) 71 | ) 72 | 73 | logger.info( 74 | "German industry demand before modification", 75 | ) 76 | logger.info( 77 | existing_industry.loc[ 78 | "DE", 79 | [ 80 | "Cement", 81 | "Electric arc", 82 | "Integrated steelworks", 83 | "DRI + Electric arc", 84 | "Ammonia", 85 | "Methanol", 86 | "Pulp production", 87 | "Paper production", 88 | "Ceramics & other NMM", 89 | ], 90 | ], 91 | ) 92 | 93 | # write Cement, Ammonia and Methanol directly to dataframe 94 | existing_industry.loc["DE", "Cement"] = ariadne.loc[ 95 | "Production|Non-Metallic Minerals|Cement", year 96 | ] 97 | existing_industry.loc["DE", "Ammonia"] = ariadne.loc[ 98 | "Production|Chemicals|Ammonia", year 99 | ] 100 | existing_industry.loc["DE", "Methanol"] = ariadne.loc[ 101 | "Production|Chemicals|Methanol", year 102 | ] 103 | 104 | # get ratio of pulp and paper production 105 | pulp_ratio = existing_industry.loc["DE", "Pulp production"] / ( 106 | existing_industry.loc["DE", "Pulp production"] 107 | + existing_industry.loc["DE", "Paper production"] 108 | ) 109 | 110 | existing_industry.loc["DE", "Pulp production"] = ( 111 | ariadne.loc["Production|Pulp and Paper", year] * pulp_ratio 112 | ) 113 | existing_industry.loc["DE", "Paper production"] = ariadne.loc[ 114 | "Production|Pulp and Paper", year 115 | ] * (1 - pulp_ratio) 116 | 117 | # non-metallic minerals 118 | existing_industry.loc["DE", "Ceramics & other NMM"] = ( 119 | ariadne.loc["Production|Non-Metallic Minerals", year] 120 | - ariadne.loc["Production|Non-Metallic Minerals|Cement", year] 121 | ) 122 | 123 | # get steel ratios from existing_industry 124 | steel = existing_industry.loc[ 125 | "DE", ["Electric arc", "Integrated steelworks", "DRI + Electric arc"] 126 | ] 127 | ratio = steel / steel.sum() 128 | 129 | # multiply with steel production including primary and secondary steel since distinguishing is taken care of later 130 | existing_industry.loc[ 131 | "DE", ["Electric arc", "Integrated steelworks", "DRI + Electric arc"] 132 | ] = (ratio * ariadne.loc["Production|Steel", year]) 133 | 134 | logger.info("German demand after modification") 135 | logger.info( 136 | existing_industry.loc[ 137 | "DE", 138 | [ 139 | "Cement", 140 | "Electric arc", 141 | "Integrated steelworks", 142 | "DRI + Electric arc", 143 | "Ammonia", 144 | "Methanol", 145 | "Pulp production", 146 | "Paper production", 147 | "Ceramics & other NMM", 148 | ], 149 | ], 150 | ) 151 | 152 | existing_industry.to_csv( 153 | snakemake.output.industrial_production_per_country_tomorrow 154 | ) 155 | -------------------------------------------------------------------------------- /workflow/scripts/plot_ariadne_scenario_comparison.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | import matplotlib.pyplot as plt 6 | import pandas as pd 7 | 8 | 9 | def scenario_plot(df, var): 10 | unit = df._get_label_or_level_values("Unit")[0] 11 | if var.startswith("Investment"): 12 | unit = "billion EUR2020/yr" 13 | df = df.droplevel("Unit") 14 | ax = df.T.plot(xlabel="years", ylabel=str(unit), title=str(var)) 15 | plt.close() 16 | prefix = snakemake.config["run"]["prefix"] 17 | var = var.replace("|", "-").replace("\\", "-").replace(" ", "-").replace("/", "-") 18 | ax.figure.savefig(f"results/{prefix}/ariadne_comparison/{var}", bbox_inches="tight") 19 | 20 | 21 | if __name__ == "__main__": 22 | if "snakemake" not in globals(): 23 | import sys 24 | 25 | path = "../submodules/pypsa-eur/scripts" 26 | sys.path.insert(0, os.path.abspath(path)) 27 | from _helpers import mock_snakemake 28 | 29 | snakemake = mock_snakemake( 30 | "ariadne_all", 31 | # simpl="", 32 | # clusters=22, 33 | # opts="", 34 | # ll="vopt", 35 | # sector_opts="None", 36 | # planning_horizons="2050", 37 | # run="KN2045_Bal_v4" 38 | ) 39 | 40 | dfs = [] 41 | for file in snakemake.input.exported_variables: 42 | _df = pd.read_excel( 43 | file, index_col=list(range(5)), sheet_name="data" 44 | ).droplevel(["Model", "Region"]) 45 | dfs.append(_df) 46 | 47 | df = pd.concat(dfs, axis=0) 48 | 49 | prefix = snakemake.config["run"]["prefix"] 50 | if not os.path.exists(f"results/{prefix}/ariadne_comparison/"): 51 | os.mkdir(f"results/{prefix}/ariadne_comparison/") 52 | 53 | for var in df._get_label_or_level_values("Variable"): 54 | scenario_plot(df.xs(var, level="Variable"), var) 55 | 56 | var = "Price|Carbon" 57 | -------------------------------------------------------------------------------- /workflow/scripts/plot_hydrogen_network_incl_kernnetz.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: : 2020-2024 The PyPSA-Eur Authors 3 | # 4 | # SPDX-License-Identifier: MIT 5 | """ 6 | Creates map of optimised hydrogen network including the Kernnetz, storage and 7 | selected other infrastructure. 8 | """ 9 | 10 | import logging 11 | import os 12 | import sys 13 | 14 | import geopandas as gpd 15 | import matplotlib.pyplot as plt 16 | import numpy as np 17 | import pandas as pd 18 | import pypsa 19 | 20 | path = "../submodules/pypsa-eur/scripts" 21 | sys.path.insert(0, os.path.abspath(path)) 22 | from _helpers import configure_logging, set_scenario_config 23 | from plot_power_network import assign_location, load_projection 24 | from pypsa.plot import add_legend_circles, add_legend_lines, add_legend_patches 25 | 26 | logger = logging.getLogger(__name__) 27 | 28 | 29 | def group_pipes(df, drop_direction=False): 30 | """ 31 | Group pipes which connect same buses and return overall capacity. 32 | """ 33 | df = df.copy() 34 | if drop_direction: 35 | positive_order = df.bus0 < df.bus1 36 | df_p = df[positive_order] 37 | swap_buses = {"bus0": "bus1", "bus1": "bus0"} 38 | df_n = df[~positive_order].rename(columns=swap_buses) 39 | df = pd.concat([df_p, df_n]) 40 | 41 | # there are pipes for each investment period rename to AC buses name for plotting 42 | df["index_orig"] = df.index 43 | df.index = df.apply( 44 | lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}", 45 | axis=1, 46 | ) 47 | return df.groupby(level=0).agg( 48 | {"p_nom_opt": "sum", "bus0": "first", "bus1": "first", "index_orig": "first"} 49 | ) 50 | 51 | 52 | def plot_h2_map(n, regions): 53 | # if "H2 pipeline" not in n.links.carrier.unique(): 54 | # return 55 | 56 | assign_location(n) 57 | 58 | h2_storage = n.stores.query("carrier == 'H2'") 59 | regions["H2"] = ( 60 | h2_storage.rename(index=h2_storage.bus.map(n.buses.location)) 61 | .e_nom_opt.groupby(level=0) 62 | .sum() 63 | .div(1e6) 64 | ) # TWh 65 | regions["H2"] = regions["H2"].where(regions["H2"] > 0.1) 66 | 67 | bus_size_factor = 1e5 68 | linewidth_factor = 7e3 69 | # MW below which not drawn 70 | line_lower_threshold = 750 71 | 72 | # Drop non-electric buses so they don't clutter the plot 73 | n.buses.drop(n.buses.index[n.buses.carrier != "AC"], inplace=True) 74 | 75 | carriers = ["H2 Electrolysis", "H2 Fuel Cell"] 76 | 77 | elec = n.links[n.links.carrier.isin(carriers)].index 78 | 79 | bus_sizes = ( 80 | n.links.loc[elec, "p_nom_opt"].groupby([n.links["bus0"], n.links.carrier]).sum() 81 | / bus_size_factor 82 | ) 83 | 84 | # make a fake MultiIndex so that area is correct for legend 85 | bus_sizes.rename(index=lambda x: x.replace(" H2", ""), level=0, inplace=True) 86 | # drop all links which are not H2 pipelines 87 | n.links.drop( 88 | n.links.index[~n.links.carrier.str.contains("H2 pipeline")], inplace=True 89 | ) 90 | 91 | h2_new = n.links[n.links.carrier == "H2 pipeline"] 92 | h2_retro = n.links[n.links.carrier == "H2 pipeline retrofitted"] 93 | h2_kern = n.links[n.links.carrier == "H2 pipeline (Kernnetz)"] 94 | 95 | # safe index of pipes from current period 96 | investment_year = snakemake.wildcards.planning_horizons 97 | h2_kern_current = n.links[ 98 | (n.links.carrier == "H2 pipeline (Kernnetz)") 99 | & (n.links.index.str.contains(investment_year)) 100 | & (~(n.links.index.str.contains("reversed"))) 101 | ] 102 | h2_kern_current = h2_kern_current.index.str[:-5] 103 | 104 | if snakemake.params.foresight == "myopic": 105 | # sum capacitiy for pipelines from different investment periods 106 | h2_new = group_pipes(h2_new) 107 | 108 | if not h2_retro.empty: 109 | h2_retro = ( 110 | group_pipes(h2_retro, drop_direction=True) 111 | .reindex(h2_new.index) 112 | .fillna(0) 113 | ) 114 | 115 | if not h2_kern.empty: 116 | h2_kern = ( 117 | group_pipes(h2_kern, drop_direction=True) 118 | .reindex(h2_new.index) 119 | .fillna(0) 120 | ) 121 | 122 | if not h2_retro.empty: 123 | if snakemake.params.foresight != "myopic": 124 | positive_order = h2_retro.bus0 < h2_retro.bus1 125 | h2_retro_p = h2_retro[positive_order] 126 | swap_buses = {"bus0": "bus1", "bus1": "bus0"} 127 | h2_retro_n = h2_retro[~positive_order].rename(columns=swap_buses) 128 | h2_retro = pd.concat([h2_retro_p, h2_retro_n]) 129 | 130 | h2_retro["index_orig"] = h2_retro.index 131 | h2_retro.index = h2_retro.apply( 132 | lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}", 133 | axis=1, 134 | ) 135 | 136 | retro_w_new_i = h2_retro.index.intersection(h2_new.index) 137 | h2_retro_w_new = h2_retro.loc[retro_w_new_i] 138 | 139 | retro_wo_new_i = h2_retro.index.difference(h2_new.index) 140 | h2_retro_wo_new = h2_retro.loc[retro_wo_new_i] 141 | h2_retro_wo_new.index = h2_retro_wo_new.index_orig 142 | 143 | to_concat = [h2_new, h2_retro_w_new, h2_retro_wo_new] 144 | h2_total = pd.concat(to_concat).p_nom_opt.groupby(level=0).sum() 145 | 146 | elif not h2_kern.empty: 147 | if snakemake.params.foresight != "myopic": 148 | positive_order = h2_kern.bus0 < h2_kern.bus1 149 | h2_kern_p = h2_kern[positive_order] 150 | swap_buses = {"bus0": "bus1", "bus1": "bus0"} 151 | h2_kern_n = h2_kern[~positive_order].rename(columns=swap_buses) 152 | h2_kern = pd.concat([h2_kern_p, h2_kern_n]) 153 | 154 | h2_kern["index_orig"] = h2_kern.index 155 | h2_kern.index = h2_kern.apply( 156 | lambda x: f"H2 pipeline {x.bus0.replace(' H2', '')} -> {x.bus1.replace(' H2', '')}", 157 | axis=1, 158 | ) 159 | 160 | kern_w_new_i = h2_kern.index.intersection(h2_new.index) 161 | h2_kern_w_new = h2_kern.loc[kern_w_new_i] 162 | 163 | kern_wo_new_i = h2_kern.index.difference(h2_new.index) 164 | h2_kern_wo_new = h2_kern.loc[kern_wo_new_i] 165 | h2_kern_wo_new.index = h2_kern_wo_new.index_orig 166 | 167 | if not h2_retro.empty: 168 | to_concat = [ 169 | h2_new, 170 | h2_new, 171 | h2_retro_w_new, 172 | h2_retro_wo_new, 173 | h2_kern_w_new, 174 | h2_kern_wo_new, 175 | ] 176 | h2_total = pd.concat(to_concat).p_nom_opt.groupby(level=0).sum() 177 | 178 | else: 179 | to_concat = [h2_new, h2_new, h2_kern_w_new, h2_kern_wo_new] 180 | h2_total = pd.concat(to_concat).p_nom_opt.groupby(level=0).sum() 181 | 182 | else: 183 | h2_total = h2_new.p_nom_opt 184 | 185 | link_widths_total = h2_total / linewidth_factor 186 | 187 | n.links.rename(index=lambda x: x.split("-2")[0], inplace=True) 188 | n.links = n.links.groupby(level=0).first() 189 | link_widths_total = link_widths_total.reindex(n.links.index).fillna(0.0) 190 | link_widths_total[n.links.p_nom_opt < line_lower_threshold] = 0.0 191 | 192 | retro = n.links.p_nom_opt.where( 193 | n.links.carrier == "H2 pipeline retrofitted", other=0.0 194 | ) 195 | link_widths_retro = retro / linewidth_factor 196 | link_widths_retro[n.links.p_nom_opt < line_lower_threshold] = 0.0 197 | 198 | kern = n.links.p_nom_opt.where( 199 | n.links.index.isin(h2_kern_current.tolist()), 200 | other=0.0, 201 | ) 202 | 203 | link_widths_kern = kern / linewidth_factor 204 | link_widths_kern[n.links.p_nom_opt < line_lower_threshold] = 0.0 205 | 206 | n.links.bus0 = n.links.bus0.str.replace(" H2", "") 207 | n.links.bus1 = n.links.bus1.str.replace(" H2", "") 208 | 209 | regions = regions.to_crs(proj.proj4_init) 210 | 211 | fig, ax = plt.subplots(figsize=(7, 6), subplot_kw={"projection": proj}) 212 | 213 | color_h2_pipe = "#b3f3f4" 214 | color_retrofit = "#499a9c" 215 | color_kern = "#6b3161" 216 | 217 | bus_colors = {"H2 Electrolysis": "#ff29d9", "H2 Fuel Cell": "#805394"} 218 | 219 | n.plot( 220 | geomap=True, 221 | bus_sizes=bus_sizes, 222 | bus_colors=bus_colors, 223 | link_colors=color_h2_pipe, 224 | link_widths=link_widths_total, 225 | branch_components=["Link"], 226 | ax=ax, 227 | **map_opts, 228 | ) 229 | 230 | n.plot( 231 | geomap=True, 232 | bus_sizes=0, 233 | link_colors=color_retrofit, 234 | link_widths=link_widths_retro, 235 | branch_components=["Link"], 236 | ax=ax, 237 | **map_opts, 238 | ) 239 | 240 | n.plot( 241 | geomap=True, 242 | bus_sizes=0, 243 | link_colors=color_kern, 244 | link_widths=link_widths_kern, 245 | branch_components=["Link"], 246 | ax=ax, 247 | **map_opts, 248 | ) 249 | 250 | regions.plot( 251 | ax=ax, 252 | column="H2", 253 | cmap="Blues", 254 | linewidths=0, 255 | legend=True, 256 | vmax=6, 257 | vmin=0, 258 | legend_kwds={ 259 | "label": "Hydrogen Storage [TWh]", 260 | "shrink": 0.7, 261 | "extend": "max", 262 | }, 263 | ) 264 | 265 | sizes = [50, 10] 266 | labels = [f"{s} GW" for s in sizes] 267 | sizes = [s / bus_size_factor * 1e3 for s in sizes] 268 | 269 | legend_kw = dict( 270 | loc="upper left", 271 | bbox_to_anchor=(0, 1), 272 | labelspacing=0.8, 273 | handletextpad=0, 274 | frameon=False, 275 | ) 276 | 277 | add_legend_circles( 278 | ax, 279 | sizes, 280 | labels, 281 | srid=n.srid, 282 | patch_kw=dict(facecolor="lightgrey"), 283 | legend_kw=legend_kw, 284 | ) 285 | 286 | sizes = [30, 10] 287 | labels = [f"{s} GW" for s in sizes] 288 | scale = 1e3 / linewidth_factor 289 | sizes = [s * scale for s in sizes] 290 | 291 | legend_kw = dict( 292 | loc="upper left", 293 | bbox_to_anchor=(0.23, 1), 294 | frameon=False, 295 | labelspacing=0.8, 296 | handletextpad=1, 297 | ) 298 | 299 | add_legend_lines( 300 | ax, 301 | sizes, 302 | labels, 303 | patch_kw=dict(color="lightgrey"), 304 | legend_kw=legend_kw, 305 | ) 306 | 307 | colors = [bus_colors[c] for c in carriers] + [ 308 | color_h2_pipe, 309 | color_retrofit, 310 | color_kern, 311 | ] 312 | labels = carriers + [ 313 | "H2 pipeline (total)", 314 | "H2 pipeline (repurposed)", 315 | "H2 pipeline (Kernnetz)", 316 | ] 317 | 318 | legend_kw = dict( 319 | loc="upper left", 320 | bbox_to_anchor=(0, 1.13), 321 | ncol=2, 322 | frameon=False, 323 | ) 324 | 325 | add_legend_patches(ax, colors, labels, legend_kw=legend_kw) 326 | 327 | ax.set_facecolor("white") 328 | 329 | fig.savefig(snakemake.output.map, bbox_inches="tight") 330 | 331 | 332 | if __name__ == "__main__": 333 | if "snakemake" not in globals(): 334 | import os 335 | import sys 336 | 337 | from _helpers import mock_snakemake 338 | 339 | snakemake = mock_snakemake( 340 | "plot_hydrogen_network_incl_kernnetz", 341 | simpl="", 342 | clusters=22, 343 | opts="", 344 | ll="vopt", 345 | sector_opts="None", 346 | planning_horizons="2045", 347 | run="CurrentPolicies", 348 | ) 349 | 350 | configure_logging(snakemake) 351 | set_scenario_config(snakemake) 352 | 353 | n = pypsa.Network(snakemake.input.network) 354 | # fn= "/home/julian-geis/repos/pypsa-ariadne/results/20240426plotH2Kernnetz/CurrentPolicies/postnetworks/elec_s_22_lvopt__none_2030.nc" 355 | # n = pypsa.Network(fn) 356 | 357 | regions = gpd.read_file(snakemake.input.regions).set_index("name") 358 | 359 | map_opts = snakemake.params.plotting["map"] 360 | 361 | if map_opts["boundaries"] is None: 362 | map_opts["boundaries"] = regions.total_bounds[[0, 2, 1, 3]] + [-1, 1, -1, 1] 363 | 364 | proj = load_projection(snakemake.params.plotting) 365 | 366 | plot_h2_map(n, regions) 367 | -------------------------------------------------------------------------------- /workflow/scripts/retrieve_ariadne_database.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | 4 | import pyam 5 | from _helpers import configure_logging 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | if __name__ == "__main__": 10 | if "snakemake" not in globals(): 11 | import os 12 | import sys 13 | 14 | path = "../submodules/pypsa-eur/scripts" 15 | sys.path.insert(0, os.path.abspath(path)) 16 | from _helpers import mock_snakemake 17 | 18 | snakemake = mock_snakemake("retrieve_ariadne_database") 19 | 20 | configure_logging(snakemake) 21 | logger.info( 22 | f"Retrieving from IIASA database {snakemake.params.db_name}\nmodels {list(snakemake.params.leitmodelle.values())}\nscenarios {snakemake.params.scenarios}" 23 | ) 24 | 25 | db = pyam.read_iiasa( 26 | snakemake.params.db_name, 27 | model=snakemake.params.leitmodelle.values(), 28 | scenario=snakemake.params.scenarios, 29 | # Download only the most recent iterations of scenarios 30 | ) 31 | 32 | logger.info(f"Successfully retrieved database.") 33 | db.timeseries().to_csv(snakemake.output.data) 34 | -------------------------------------------------------------------------------- /workflow/submodules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyPSA/pypsa-ariadne/97b7c25e657bb65341354673334300d678eac973/workflow/submodules/.gitkeep --------------------------------------------------------------------------------