Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
44ea1cd
feat: first draft of layered PTES model
amos-schledorn Mar 11, 2026
b6727d0
refactor: remove expand_ptes_layers function and related validation f…
amos-schledorn Mar 12, 2026
9bc232e
feat: add layered PTES heat pump capacity constraint function
amos-schledorn Mar 12, 2026
1d2d5d8
introduce HP boosting and cost ptes discharging
amos-schledorn Mar 12, 2026
30fc522
fix volume capacity weights
amos-schledorn Mar 12, 2026
ee02599
Merge tag 'v2026.02.0' into refactor-ptes-boosting
amos-schledorn Mar 20, 2026
bf30c43
use conducive heat transfer for interlayer transfer
amos-schledorn Mar 26, 2026
12ce641
fix: update marginal cost for central water pit charger
amos-schledorn Mar 26, 2026
d6c377a
feat: add additional PTES layers to heat source enumeration
amos-schledorn Mar 26, 2026
1cf12d9
first draft: simplify boosting
amos-schledorn Apr 1, 2026
c253e0d
feat: add interlayer heat transfer coefficient to PTES operations
amos-schledorn Apr 7, 2026
1645334
Merge tag 'v2026.02.0' into volume-layers
amos-schledorn Apr 7, 2026
ad5f1de
update boosting logic
amos-schledorn Apr 7, 2026
c416824
fix boosting logic:
amos-schledorn Apr 8, 2026
d52a69f
fix faulty HP input for inexhaustible heat sources
amos-schledorn Apr 8, 2026
fd178c2
clean up docstrings
amos-schledorn Apr 8, 2026
02b66f1
fix util link ratios
amos-schledorn Apr 13, 2026
a48ec40
working draft
amos-schledorn Apr 21, 2026
e0f2051
fix efficiencies
amos-schledorn Apr 28, 2026
ddad176
fix incorrekt HP sink inlet temperature
amos-schledorn Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,48 +82,48 @@ jobs:
df -h

- name: Skip - no source changes
if: steps.filter.outputs.src != 'true' && github.event_name != 'schedule'
if: steps.filter.outputs.src != 'true' && github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
run: echo "Skipping tests because no source code changes detected"

- name: Setup Pixi
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
uses: prefix-dev/setup-pixi@v0.9.4
with:
pixi-version: v0.59.0
cache: true
cache-write: ${{ github.event_name == 'push' && github.ref_name == 'master' }}

- name: Setup cache keys
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: |
echo "WEEK=$(date +'%Y%U')" >> $GITHUB_ENV # data
echo "MONTH=$(date +'%Y%m')" >> $GITHUB_ENV # cutouts
echo "VERSIONS_HASH=${{ hashFiles('data/versions.csv') }}" >> $GITHUB_ENV

- uses: actions/cache@v5
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
with:
path: data
key: data-${{ env.WEEK }}-${{ env.VERSIONS_HASH }}

- uses: actions/cache@v5
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
with:
path: cutouts
key: cutouts-${{ env.MONTH }}

- name: Restore git-tracked files in data/
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: git checkout HEAD -- data/

- name: Run pylint check on scripts
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
# check for undefined variables to reuse functions across scripts
run: |
pixi run pylint --disable=all --enable=E0601,E0606 --output-format=parseable scripts/add_* scripts/prepare_* scripts/solve_*

- name: Run snakemake test workflows
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule'
if: steps.filter.outputs.src == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
env:
SNAKEMAKE_STORAGE_CACHED_HTTP_CACHE: ""
SNAKEMAKE_STORAGE_CACHED_HTTP_SKIP_REMOTE_CHECKS: "1"
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ jobs:
# <scenario>/<plot_name>.png
plots: >
"
graphs/energy.svg
graphs/costs.svg
graphs/balances-energy.svg
graphs/balances-urban_central_heat.svg
graphs/balances-urban_decentral_heat.svg
graphs/balances-rural_heat.svg
graphs/energy.pdf
graphs/costs.pdf
graphs/balances-energy.pdf
graphs/balances-urban_central_heat.pdf
graphs/balances-urban_decentral_heat.pdf
graphs/balances-rural_heat.pdf
"
validator_key: ${{ secrets.VALIDATOR_KEY }}
5 changes: 4 additions & 1 deletion .pixi/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
# SPDX-License-Identifier: CC0-1.0

pinning-strategy = "latest-up"
run-post-link-scripts = "false" # set to "insecure" to allow running post-link scripts

# This aligns with default `conda` behaviour.
# Set to "false" to stop post-link scripts from running.
run-post-link-scripts = "insecure"
3 changes: 1 addition & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-merge-conflict
- id: check-added-large-files
args: ["--maxkb=2000"]

Expand All @@ -30,7 +29,7 @@ repos:
rev: v2.4.1
hooks:
- id: codespell
args: ['--ignore-regex="(\b[A-Z]+\b)"', '--ignore-words-list=fom,appartment,bage,ore,setis,tabacco,berfore,vor,pris,bund'] # Ignore capital case words, e.g. country codes
args: ['--ignore-regex="(\b[A-Z]+\b)"', '--ignore-words-list=fom,appartment,bage,ore,setis,tabacco,berfore,vor,pris,bund,ons'] # Ignore capital case words, e.g. country codes
types_or: [python, rst, markdown]
files: ^(scripts|doc)/

Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cff-version: 1.1.0
message: "If you use this package, please cite it in the following way."
title: "PyPSA-Eur: An open sector-coupled optimisation model of the European energy system"
repository: https://github.com/pypsa/pypsa-eur
version: v2025.07.0
version: v2026.02.0
license: MIT
authors:
- family-names: Brown
Expand Down
110 changes: 44 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,11 @@ SPDX-License-Identifier: CC-BY-4.0
# PyPSA-Eur: A Sector-Coupled Open Optimisation Model of the European Energy System

PyPSA-Eur is an open model dataset of the European energy system at the
transmission network level that covers the full ENTSO-E area. The model is suitable both for operational studies and generation and transmission expansion planning studies.
The continental scope and highly resolved spatial scale enables a proper description of the long-range
smoothing effects for renewable power generation and their varying resource availability.




The model is described in the [documentation](https://pypsa-eur.readthedocs.io)
and in the paper
[PyPSA-Eur: An Open Optimisation Model of the European Transmission
System](https://arxiv.org/abs/1806.01613), 2018,
[arXiv:1806.01613](https://arxiv.org/abs/1806.01613).
The model building routines are defined through a snakemake workflow.
Please see the [documentation](https://pypsa-eur.readthedocs.io/)
for installation instructions and other useful information about the snakemake workflow.
The model is designed to be imported into the open toolbox
[PyPSA](https://github.com/PyPSA/PyPSA).
transmission network level that covers the full ENTSO-E area and all energy sectors, including transport, heating, biomass, industry, and agriculture.
Besides the power grid, pipeline networks for gas, hydrogen, carbon dioxide, and liquid fuels are included.
The model is suitable both for planning studies and operational studies.
The model is built from open data using a Snakemake workflow and fully open source.
It is designed to be imported into the open-source energy system modelling framework [PyPSA](www.pypsa.org).

> [!NOTE]
> PyPSA-Eur has many contributors, with the maintenance currently led by the [Department of Digital Transformation in
Expand All @@ -42,65 +30,55 @@ The model is designed to be imported into the open toolbox
> Institute of Technology](http://www.kit.edu/english/index.php) funded by the
> [Helmholtz Association](https://www.helmholtz.de/en/).

> [!WARNING]
> PyPSA-Eur is under active development and has several
> [limitations](https://pypsa-eur.readthedocs.io/en/latest/limitations.html) which
> you should understand before using the model. The github repository
> [issues](https://github.com/PyPSA/pypsa-eur/issues) collect known topics we are
> working on (please feel free to help or make suggestions). The
> [documentation](https://pypsa-eur.readthedocs.io/) remains somewhat patchy. You
> can find showcases of the model's capabilities in the Joule paper [The potential
> role of a hydrogen network in
> Europe](https://doi.org/10.1016/j.joule.2023.06.016), another [paper in Joule
> with a description of the industry
> sector](https://doi.org/10.1016/j.joule.2022.04.016), or in [a 2021 presentation
> at EMP-E](https://nworbmot.org/energy/brown-empe.pdf). We do not recommend to
> use the full resolution network model for simulations. At high granularity the
> assignment of loads and generators to the nearest network node may not be a
> correct assumption, depending on the topology of the underlying distribution
> grid, and local grid bottlenecks may cause unrealistic load-shedding or
> generator curtailment. We recommend to cluster the network to a couple of
> hundred nodes to remove these local inconsistencies. See the discussion in
> Section 3.4 "Model validation" of the paper.


![PyPSA-Eur Grid Model](doc/img/elec.png)

The dataset consists of:

- A grid model based on a modified [GridKit](https://github.com/bdw/GridKit)
extraction of the [ENTSO-E Transmission System
Map](https://www.entsoe.eu/data/map/). The grid model contains 7072 lines
(alternating current lines at and above 220kV voltage level and all high
voltage direct current lines) and 3803 substations.

Among many other things, the dataset consists of:

- A power grid model based on [OpenStreetMap](https://zenodo.org/records/18619025) for voltage levels above 220kV (optional above 60kV).
- The open power plant database
[powerplantmatching](https://github.com/PyPSA/powerplantmatching).
- Electrical demand time series from the
[OPSD project](https://open-power-system-data.org/).
- Renewable time series based on ERA5 and SARAH, assembled using the [atlite tool](https://github.com/PyPSA/atlite).
- Geographical potentials for wind and solar generators based on land use (CORINE) and excluding nature reserves (Natura2000) are computed with the [atlite library](https://github.com/PyPSA/atlite).
- Electrical demand time series from the [ENTSO-E Transparency Platform](https://transparency.entsoe.eu/).
- Renewable time series based on ERA5 and SARAH-3, assembled using [atlite](https://github.com/PyPSA/atlite).
- Geographical potentials for wind and solar generators based land eligibility analysis in [atlite](https://github.com/PyPSA/atlite).
- Energy balances compiled from Eurostat and JRC-IDEES datasets.

The high-voltage grid and the power plant fleet are shown in this map of the unclustered model (as of 1 January 2026):

A sector-coupled extension adds demand
and supply for the following sectors: transport, space and water
heating, biomass, industry and industrial feedstocks, agriculture,
forestry and fishing. This completes the energy system and includes
all greenhouse gas emitters except waste management and land use.
![PyPSA-Eur Unclustered](doc/img/base.png)


For computational reasons the model is usually clustered down
to 50-250 nodes. The image below shows the electricity network and power plants clustered to NUTS2 regions:

![network diagram](doc/img/elec.png)

This diagram gives an overview of the sectors and the links between
them:
them within each model region:

![sector diagram](doc/img/multisector_figure.png)

Each of these sectors is built up on the transmission network nodes
from [PyPSA-Eur](https://github.com/PyPSA/pypsa-eur):

![network diagram](https://github.com/PyPSA/pypsa-eur/blob/master/doc/img/base.png?raw=true)

For computational reasons the model is usually clustered down
to 50-200 nodes.

Already-built versions of the model can be found in the accompanying [Zenodo
repository](https://doi.org/10.5281/zenodo.3601881).
# Warnings

PyPSA-Eur is under active development and has several
[limitations](https://pypsa-eur.readthedocs.io/en/latest/limitations.html) which
you should understand before using the model. The github repository
[issues](https://github.com/PyPSA/pypsa-eur/issues) collect known topics we are
working on (please feel free to help or make suggestions). The
[documentation](https://pypsa-eur.readthedocs.io/) remains somewhat patchy. You
can find showcases of the model's capabilities in the Joule paper [The potential
role of a hydrogen network in
Europe](https://doi.org/10.1016/j.joule.2023.06.016), another [paper in Joule
with a description of the industry
sector](https://doi.org/10.1016/j.joule.2022.04.016), or in [a 2021 presentation
at EMP-E](https://nworbmot.org/energy/brown-empe.pdf). We do not recommend to
use the full resolution network model for simulations. At high granularity the
assignment of loads and generators to the nearest network node may not be a
correct assumption, depending on the topology of the underlying distribution
grid, and local grid bottlenecks may cause unrealistic load-shedding or
generator curtailment. We recommend to cluster the network to a couple of
hundred nodes to remove these local inconsistencies. See the discussion in
Section 3.4 "Model validation" of the paper.

# Contributing and Support
We strongly welcome anyone interested in contributing to this project. If you have any ideas, suggestions or encounter problems, feel invited to file issues or make pull requests on GitHub.
Expand Down
3 changes: 1 addition & 2 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ path = [
"envs/*.pin.txt",
"envs/environment.yaml",
"config/config.default.yaml",
"config/schema.json"
"config/schema.default.json"
]
SPDX-FileCopyrightText = "The PyPSA-Eur Authors"
SPDX-License-Identifier = "CC0-1.0"
Expand All @@ -42,7 +42,6 @@ path = [
"data/parameter_corrections.yaml",
"data/retro/window_assumptions.csv",
"data/retro/retro_cost_germany.csv",
"data/switzerland-new_format-all_years.csv",
"data/transmission_projects/**",
"data/unit_commitment.csv",
"data/versions.csv",
Expand Down
3 changes: 1 addition & 2 deletions Snakefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ if config["foresight"] == "perfect":

rule all:
input:
expand(RESULTS + "graphs/costs.svg", run=config["run"]["name"]),
expand(RESULTS + "graphs/costs.pdf", run=config["run"]["name"]),
expand(resources("maps/power-network.pdf"), run=config["run"]["name"]),
expand(
resources("maps/power-network-s-{clusters}.pdf"),
Expand Down Expand Up @@ -290,7 +290,6 @@ rule rulegraph:

# Generate visualizations from the DOT file
if [ -s {output.dot} ]; then
dot -c

echo "[Rule rulegraph] Generating PDF from DOT"
dot -Tpdf -o {output.pdf} {output.dot} || {{ echo "Error: Failed to generate PDF. Is graphviz installed?" >&2; exit 1; }}
Expand Down
Loading