Skip to content

Commit c9652bc

Browse files
FBumannclaude
andauthored
feat(tutorials): ship tutorial data + downloadable example systems (#706)
* feat(tutorials): ship tutorial data + downloadable example systems Notebook tutorial data was only reachable by cloning the repo or copying files out of GitHub, so users hit dead imports on a plain `pip install`. Add a public `fx.tutorials` API that makes every notebook standalone: - `get_data(name)` returns the synthetic datasets for notebooks 01-07, generated from numpy/pandas with no files or network. - `load_example(name)` downloads a pre-built FlowSystem for notebooks 08-09 from the project's GitHub releases (cached + hash-verified via pooch), so the heavy demandlib/pvlib/holidays generation no longer runs at user runtime. Gated behind the new `flixopt[tutorials]` extra. The dataset/example names live once each in the `DataName`/`ExampleName` Literals; lists, validation and builder dispatch all derive from them. Add `scripts/build_tutorial_datasets.py` and a manual `tutorial-data` workflow to build and upload the example artefacts to the data release, and migrate all notebooks to the new API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(tutorials): disable checkout credential persistence The tutorial-data job is `contents: write` and authenticates its release steps via an explicit GH_TOKEN, so the checkout action's persisted git credentials are unnecessary. Set persist-credentials: false to reduce token exposure (per CodeRabbit review on #706). Action version tags left as-is to match the repo-wide convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 56eed06 commit c9652bc

22 files changed

Lines changed: 396 additions & 60 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Tutorial data
2+
3+
# Builds the pre-built example FlowSystems and uploads them (plus registry.txt) as
4+
# assets to the GitHub release that `flixopt.tutorials.load_example` downloads from.
5+
# Run manually whenever the example systems change. The release tag must match
6+
# `flixopt.tutorials._examples.DATA_RELEASE` (default: tutorial-data-v1).
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
release_tag:
12+
description: "Release tag to (re)upload the data assets to (must match DATA_RELEASE)."
13+
required: true
14+
default: "tutorial-data-v1"
15+
16+
env:
17+
PYTHON_VERSION: "3.11"
18+
19+
jobs:
20+
build-and-upload:
21+
name: Build and upload example systems
22+
runs-on: ubuntu-24.04
23+
permissions:
24+
contents: write
25+
steps:
26+
- uses: actions/checkout@v6
27+
with:
28+
fetch-depth: 0
29+
persist-credentials: false
30+
31+
- uses: astral-sh/setup-uv@v7
32+
with:
33+
version: "0.10.9"
34+
enable-cache: true
35+
36+
- uses: actions/setup-python@v6
37+
with:
38+
python-version: ${{ env.PYTHON_VERSION }}
39+
40+
# docs extra provides demandlib/pvlib/holidays used by the example generators.
41+
- name: Install build dependencies
42+
run: uv pip install --system -e ".[docs,full]"
43+
44+
- name: Verify DATA_RELEASE matches the requested tag
45+
run: |
46+
EXPECTED=$(python -c "from flixopt.tutorials._examples import DATA_RELEASE; print(DATA_RELEASE)")
47+
if [[ "$EXPECTED" != "$TAG" ]]; then
48+
echo "::error::DATA_RELEASE is '$EXPECTED' but the workflow was asked to upload to '$TAG'."
49+
echo "Update flixopt/tutorials/_examples.py or pass the matching tag."
50+
exit 1
51+
fi
52+
env:
53+
TAG: ${{ inputs.release_tag }}
54+
55+
- name: Build example systems
56+
run: python scripts/build_tutorial_datasets.py --out-dir dist/tutorial_datasets
57+
58+
- name: Create the data release if it does not exist
59+
run: |
60+
if ! gh release view "$TAG" >/dev/null 2>&1; then
61+
gh release create "$TAG" \
62+
--title "Tutorial data ($TAG)" \
63+
--notes "Pre-built example FlowSystems downloaded by flixopt.tutorials.load_example." \
64+
--prerelease
65+
fi
66+
env:
67+
GH_TOKEN: ${{ github.token }}
68+
TAG: ${{ inputs.release_tag }}
69+
70+
- name: Upload data assets
71+
run: gh release upload "$TAG" dist/tutorial_datasets/* --clobber
72+
env:
73+
GH_TOKEN: ${{ github.token }}
74+
TAG: ${{ inputs.release_tag }}

docs/notebooks/02-heat-system.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@
5757
"metadata": {},
5858
"outputs": [],
5959
"source": [
60-
"from data.tutorial_data import get_heat_system_data\n",
61-
"\n",
62-
"data = get_heat_system_data()\n",
60+
"data = fx.tutorials.get_data('heat_system')\n",
6361
"timesteps = data['timesteps']\n",
6462
"heat_demand = data['heat_demand']\n",
6563
"gas_price = data['gas_price']"

docs/notebooks/03-investment-optimization.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@
8282
"metadata": {},
8383
"outputs": [],
8484
"source": [
85-
"from data.tutorial_data import get_investment_data\n",
86-
"\n",
87-
"data = get_investment_data()\n",
85+
"data = fx.tutorials.get_data('investment')\n",
8886
"timesteps = data['timesteps']\n",
8987
"solar_profile = data['solar_profile']\n",
9088
"pool_demand = data['pool_demand']\n",

docs/notebooks/04-operational-constraints.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@
7171
"metadata": {},
7272
"outputs": [],
7373
"source": [
74-
"from data.tutorial_data import get_constraints_data\n",
75-
"\n",
76-
"data = get_constraints_data()\n",
74+
"data = fx.tutorials.get_data('constraints')\n",
7775
"timesteps = data['timesteps']\n",
7876
"steam_demand = data['steam_demand']"
7977
]

docs/notebooks/05-multi-carrier-system.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@
8383
"metadata": {},
8484
"outputs": [],
8585
"source": [
86-
"from data.tutorial_data import get_multicarrier_data\n",
87-
"\n",
88-
"data = get_multicarrier_data()\n",
86+
"data = fx.tutorials.get_data('multicarrier')\n",
8987
"timesteps = data['timesteps']\n",
9088
"electricity_demand = data['electricity_demand']\n",
9189
"heat_demand = data['heat_demand']\n",

docs/notebooks/06a-time-varying-parameters.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@
7777
"metadata": {},
7878
"outputs": [],
7979
"source": [
80-
"from data.tutorial_data import get_time_varying_data\n",
81-
"\n",
82-
"data = get_time_varying_data()\n",
80+
"data = fx.tutorials.get_data('time_varying')\n",
8381
"timesteps = data['timesteps']\n",
8482
"outdoor_temp = data['outdoor_temp']\n",
8583
"heat_demand = data['heat_demand']\n",

docs/notebooks/07-scenarios-and-periods.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@
7171
"metadata": {},
7272
"outputs": [],
7373
"source": [
74-
"from data.tutorial_data import get_scenarios_data\n",
75-
"\n",
76-
"data = get_scenarios_data()\n",
74+
"data = fx.tutorials.get_data('scenarios')\n",
7775
"timesteps = data['timesteps']\n",
7876
"periods = data['periods']\n",
7977
"scenarios = data['scenarios']\n",

docs/notebooks/08a-aggregation.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@
5959
"metadata": {},
6060
"outputs": [],
6161
"source": [
62-
"from data.generate_example_systems import create_district_heating_system\n",
63-
"\n",
64-
"flow_system = create_district_heating_system()\n",
62+
"flow_system = fx.tutorials.load_example('district_heating')\n",
6563
"flow_system.connect_and_transform() # Align all data as xarray\n",
6664
"\n",
6765
"timesteps = flow_system.timesteps\n",

docs/notebooks/08b-rolling-horizon.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@
6363
"metadata": {},
6464
"outputs": [],
6565
"source": [
66-
"from data.generate_example_systems import create_operational_system\n",
67-
"\n",
68-
"flow_system = create_operational_system().transform.resample('1h')\n",
66+
"flow_system = fx.tutorials.load_example('operational').transform.resample('1h')\n",
6967
"flow_system.connect_and_transform() # Align all data as xarray\n",
7068
"\n",
7169
"timesteps = flow_system.timesteps\n",

docs/notebooks/08c-clustering.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@
5454
"metadata": {},
5555
"outputs": [],
5656
"source": [
57-
"from data.generate_example_systems import create_district_heating_system\n",
58-
"\n",
59-
"flow_system = create_district_heating_system()\n",
57+
"flow_system = fx.tutorials.load_example('district_heating')\n",
6058
"flow_system.connect_and_transform()\n",
6159
"\n",
6260
"timesteps = flow_system.timesteps\n",

0 commit comments

Comments
 (0)