A 9-region capacity-expansion model of Austria's electricity system, built in PyPSA for a Data Science for Energy System Modelling course assignment. Given Austria's regions, 2019 weather-year renewable potential, existing conventional plants, and technology costs, the model finds the least-cost generation, storage, and transmission build-out under different CO2 constraints.
- Regions: Austria's 9 states (GADM level-1 polygons), each a single electrical bus.
- Generation: existing hydro + gas (CCGT) fleet, plus extendable onshore wind and utility solar sized from ERA5 weather data and land-eligibility constraints.
- Storage: battery (2h duration) and hydrogen (electrolysis + underground store + fuel cell).
- Transmission: extendable bidirectional links between neighbouring regions.
- Scenarios: no CO2 limit, 100% CO2 reduction (zero emissions), and sensitivity sweeps on electrolysis / fuel cell / combined hydrogen technology costs.
notebooks/ the pipeline - see table below, run in order
src/ shared helper code (paths.py, pypsa_helpers.py)
data/raw/ input datasets (not committed - see "Data sources" below)
data/processed/ intermediate + solved-network outputs (regenerated by running the notebooks)
results/ final figures used in the analysis (committed, so you can browse results without re-running anything)
| Notebook | Purpose |
|---|---|
00_setup_paths |
Verify data/raw has the expected input files and data/processed/results are writable |
01_regions_centroids |
Define Austria's 9 model regions and their centroids |
02_land_eligibility_solar |
Land eligibility analysis for utility solar |
03_land_eligibility_wind |
Land eligibility analysis for onshore wind |
04_weather_capacity_factors |
ERA5 (via atlite) wind/solar capacity factor time series per region |
05_existing_plant |
Existing hydro + gas fleet, aggregated per region |
06_building_the_PyPSA_model_draft |
Assembles the full PyPSA network (regions, load, generators, storage, transmission) and exports it |
07_scenario_no_co2_limit |
Solves the network with no CO2 constraint |
08_scenario_100pct_co2_reduction |
Solves the network with a 0 t/yr CO2 cap |
09_sensitivity_electrolysis_cost |
Re-solves the zero-CO2 network at 75/50/25/0% of baseline electrolysis capital cost |
10_sensitivity_results_analysis |
Charts/tables across the electrolysis cost sweep |
11_co2_scenario_comparison |
Charts/tables comparing the two mandatory scenarios (07 vs. 08) |
12_sensitivity_fuel_cell_cost |
Same sweep, but on fuel cell capital cost |
13_sensitivity_combined_h2_cost |
Both electrolysis and fuel cell cost scaled together |
14_h2_technology_comparison |
Cost-elasticity comparison: electrolysis vs. fuel cell |
Run notebooks in numeric order the first time; each one reads what the previous ones wrote to
data/processed/.
The model was developed against a conda environment with (at minimum):
pypsa,highspy(or another PyPSA-supported solver - the notebooks call HiGHS explicitly)pandas,numpy,xarray,geopandas,rasterioatlite(ERA5 capacity factors)matplotlibjupyter
conda create -n esm python=3.11
conda activate esm
pip install pypsa highspy atlite geopandas rasterio matplotlib jupyterIf your data lives somewhere other than <repo>/data/raw and <repo>/data/processed (a
different drive, a shared folder, ...), copy src/local_paths.example.py to src/local_paths.py
(gitignored) and set RAW/PROCESSED there instead of editing every notebook.
Raw input data is not committed to this repository (data/raw/ is gitignored - these
datasets are large and most have their own redistribution terms). 00_setup_paths.ipynb checks
for the following files under data/raw/:
| Path | Source |
|---|---|
gadm/gadm_410-levels-ADM_1-AUT.gpkg |
GADM administrative boundaries |
wdpa/WDPA_Oct2022_Public_shp-AUT.tif |
World Database on Protected Areas |
copernicus-glc/PROBAV_LC100_...tif |
Copernicus Global Land Cover |
gebco/GEBCO_2014_2D-AT.nc |
GEBCO bathymetry/elevation |
ne_10m_airports.gpkg, ne_10m_roads.gpkg |
Natural Earth |
AU-2019.nc |
ERA5 weather cutout (via atlite) for Austria, 2019 |
global-power-plant-database/global_power_plant_database.csv |
Global Power Plant Database |
gegis/load.csv |
Regional load time series |
Each dataset remains under its own original license - check the source before redistributing.
Generator/storage/transmission cost assumptions come from
PyPSA's technology-data (fetched live from GitHub
in 06_building_the_PyPSA_model_draft.ipynb, cost year 2030).
SNAPSHOT_FREQ in 06_building_the_PyPSA_model_draft.ipynb controls the model's time
resolution and defaults to "3h" (2,920 snapshots/year). The results and figures already
committed under results/pypsa/ were generated with SNAPSHOT_FREQ = "6h" instead: in testing,
HiGHS's interior-point solver was still converging at 3-hourly resolution but too slowly for a
reasonable time budget on typical hardware, for this 9-region, multi-technology
capacity-expansion LP. "3h" is left as the default for anyone with more time or compute to
spare; set it back to "6h" to reproduce the committed results at their original speed.
See results/pypsa/:
scenarios/- built capacity vs. actual generation for each of the two mandatory scenariosco2_comparison/- side-by-side comparison of the no-CO2-limit and 100%-reduction scenarios (cost, capacity, generation mix, curtailment, storage operation, price duration curves)sensitivity/- the electrolysis capital cost sweep (the chosen "Technology Costs" deep-dive)h2_comparison/- electrolysis vs. fuel cell cost-elasticity comparison
Parts of this project (notebook code, helper functions, plotting, documentation) were written with the help of an AI assistant (Claude). All AI-generated code was manually reviewed before being relied on - checked against the assignment requirements, run, and compared to the expected model behavior - rather than accepted as-is. Modeling decisions, assumptions, and interpretation of the results are the author's own.
Code in this repository is MIT-licensed (see LICENSE). Third-party input datasets keep their
own licenses - see "Data sources" above.