From 05710f1fb08ada356c574f2e35c3f4f4ba0b0a44 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:22:46 +0100 Subject: [PATCH 1/7] Add CI workflow to test documentation notebooks Adds a GitHub Actions workflow that executes all example notebooks to catch breakage before it reaches the published docs. Also sets nbsphinx_allow_errors to False so Sphinx builds fail on notebook errors. Closes #560 Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test-notebooks.yml | 58 ++++++++++++++++++++++++++++ doc/conf.py | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-notebooks.yml diff --git a/.github/workflows/test-notebooks.yml b/.github/workflows/test-notebooks.yml new file mode 100644 index 00000000..bf032852 --- /dev/null +++ b/.github/workflows/test-notebooks.yml @@ -0,0 +1,58 @@ +name: Test Notebooks + +on: + push: + branches: [ master ] + pull_request: + branches: [ '*' ] + schedule: + - cron: "0 5 * * TUE" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + notebooks: + name: Test documentation notebooks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install package and dependencies + run: | + python -m pip install uv + uv pip install --system -e ".[docs]" + + - name: Execute notebooks + run: | + EXIT_CODE=0 + for notebook in examples/*.ipynb; do + name=$(basename "$notebook") + + # Skip notebooks that require credentials or special setup + case "$name" in + solve-on-oetc.ipynb|solve-on-remote.ipynb) + echo "Skipping $name (requires special setup)" + continue + ;; + esac + + echo "::group::Running $name" + if jupyter nbconvert --to notebook --execute --ExecutePreprocessor.timeout=600 "$notebook"; then + echo "✓ $name passed" + else + echo "::error::✗ $name failed" + EXIT_CODE=1 + fi + echo "::endgroup::" + done + exit $EXIT_CODE diff --git a/doc/conf.py b/doc/conf.py index d7cce91b..f9024e1d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -91,7 +91,7 @@ """ -nbsphinx_allow_errors = True +nbsphinx_allow_errors = False nbsphinx_execute = "auto" nbsphinx_execute_arguments = [ "--InlineBackend.figure_formats={'svg', 'pdf'}", From 4b0985b1e3511fe4b0f58d1a25fd2670831fe22e Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:34:18 +0100 Subject: [PATCH 2/7] Fix notebook CI: revert nbsphinx_allow_errors, uninstall gurobipy - Revert nbsphinx_allow_errors back to True for RTD (infeasible-model notebook requires gurobi which has an expired license on RTD) - Uninstall gurobipy in CI workflow to prevent it from being auto-selected as default solver (no license available in CI) - Skip infeasible-model.ipynb in CI (gurobi-specific functionality) Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test-notebooks.yml | 6 ++++-- doc/conf.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-notebooks.yml b/.github/workflows/test-notebooks.yml index bf032852..24fafe40 100644 --- a/.github/workflows/test-notebooks.yml +++ b/.github/workflows/test-notebooks.yml @@ -31,6 +31,8 @@ jobs: run: | python -m pip install uv uv pip install --system -e ".[docs]" + # Remove gurobipy to avoid solver selection issues with expired license + uv pip uninstall --system gurobipy || true - name: Execute notebooks run: | @@ -40,8 +42,8 @@ jobs: # Skip notebooks that require credentials or special setup case "$name" in - solve-on-oetc.ipynb|solve-on-remote.ipynb) - echo "Skipping $name (requires special setup)" + solve-on-oetc.ipynb|solve-on-remote.ipynb|infeasible-model.ipynb) + echo "Skipping $name (requires credentials or commercial solver)" continue ;; esac diff --git a/doc/conf.py b/doc/conf.py index f9024e1d..d7cce91b 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -91,7 +91,7 @@ """ -nbsphinx_allow_errors = False +nbsphinx_allow_errors = True nbsphinx_execute = "auto" nbsphinx_execute_arguments = [ "--InlineBackend.figure_formats={'svg', 'pdf'}", From 82ac0a85785940b859d16a01345c1221892657be Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:59:24 +0100 Subject: [PATCH 3/7] Skip piecewise-linear-constraints notebook (requires SOS support) HiGHS does not support SOS constraints, so this notebook needs a commercial solver like Gurobi or CPLEX. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test-notebooks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-notebooks.yml b/.github/workflows/test-notebooks.yml index 24fafe40..d5497ea7 100644 --- a/.github/workflows/test-notebooks.yml +++ b/.github/workflows/test-notebooks.yml @@ -42,7 +42,7 @@ jobs: # Skip notebooks that require credentials or special setup case "$name" in - solve-on-oetc.ipynb|solve-on-remote.ipynb|infeasible-model.ipynb) + solve-on-oetc.ipynb|solve-on-remote.ipynb|infeasible-model.ipynb|piecewise-linear-constraints.ipynb) echo "Skipping $name (requires credentials or commercial solver)" continue ;; From 2c6aeb2aacba9301aab58806856af7b7a1b8b387 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:14:34 +0100 Subject: [PATCH 4/7] Make Sphinx build strict on notebook errors Set nbsphinx_allow_errors to False so real notebook errors are caught during the RTD build. Notebooks requiring credentials or commercial solvers are excluded from execution via nbsphinx_execute_never. Co-Authored-By: Claude Opus 4.6 --- doc/conf.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index d7cce91b..79fea56e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -91,15 +91,20 @@ """ -nbsphinx_allow_errors = True +nbsphinx_allow_errors = False nbsphinx_execute = "auto" nbsphinx_execute_arguments = [ "--InlineBackend.figure_formats={'svg', 'pdf'}", "--InlineBackend.rc={'figure.dpi': 96}", ] -# Exclude notebooks that require credentials or special setup -nbsphinx_execute_never = ["**/solve-on-oetc*"] +# Exclude notebooks that require credentials or commercial solvers +nbsphinx_execute_never = [ + "**/solve-on-oetc*", + "**/solve-on-remote*", + "**/infeasible-model*", + "**/piecewise-linear-constraints*", +] # -- Options for HTML output ------------------------------------------------- From c55ac63a922a39ea9119555c820c99a57df611e9 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:19:18 +0100 Subject: [PATCH 5/7] Fix notebook compatibility: update gurobipy, use reformulate_sos - Update gurobipy pin from ==11.0.2 (expired license) to >=13.0.0 - Use reformulate_sos="auto" in piecewise-linear-constraints notebook so it works with HiGHS (no SOS support needed) - Remove gurobipy uninstall workaround from CI workflow - Un-skip infeasible-model and piecewise-linear-constraints notebooks since they now work with a valid license / open-source solver Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test-notebooks.yml | 6 ++-- doc/conf.py | 4 +-- examples/piecewise-linear-constraints.ipynb | 34 ++++++++++----------- pyproject.toml | 2 +- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test-notebooks.yml b/.github/workflows/test-notebooks.yml index d5497ea7..dfe025d2 100644 --- a/.github/workflows/test-notebooks.yml +++ b/.github/workflows/test-notebooks.yml @@ -31,8 +31,6 @@ jobs: run: | python -m pip install uv uv pip install --system -e ".[docs]" - # Remove gurobipy to avoid solver selection issues with expired license - uv pip uninstall --system gurobipy || true - name: Execute notebooks run: | @@ -42,8 +40,8 @@ jobs: # Skip notebooks that require credentials or special setup case "$name" in - solve-on-oetc.ipynb|solve-on-remote.ipynb|infeasible-model.ipynb|piecewise-linear-constraints.ipynb) - echo "Skipping $name (requires credentials or commercial solver)" + solve-on-oetc.ipynb|solve-on-remote.ipynb) + echo "Skipping $name (requires credentials or special setup)" continue ;; esac diff --git a/doc/conf.py b/doc/conf.py index 79fea56e..5e9b88a8 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -98,12 +98,10 @@ "--InlineBackend.rc={'figure.dpi': 96}", ] -# Exclude notebooks that require credentials or commercial solvers +# Exclude notebooks that require credentials or special setup nbsphinx_execute_never = [ "**/solve-on-oetc*", "**/solve-on-remote*", - "**/infeasible-model*", - "**/piecewise-linear-constraints*", ] # -- Options for HTML output ------------------------------------------------- diff --git a/examples/piecewise-linear-constraints.ipynb b/examples/piecewise-linear-constraints.ipynb index 4646e87d..5c85000a 100644 --- a/examples/piecewise-linear-constraints.ipynb +++ b/examples/piecewise-linear-constraints.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# Piecewise Linear Constraints Tutorial\n\nThis notebook demonstrates linopy's piecewise linear (PWL) constraint formulations.\nEach example builds a separate dispatch model where a single power plant must meet\na time-varying demand.\n\n| Example | Plant | Limitation | Formulation |\n|---------|-------|------------|-------------|\n| 1 | Gas turbine (0–100 MW) | Convex heat rate | SOS2 |\n| 2 | Coal plant (0–150 MW) | Monotonic heat rate | Incremental |\n| 3 | Diesel generator (off or 50–80 MW) | Forbidden zone | Disjunctive |\n| 4 | Concave efficiency curve | Inequality bound | LP |\n| 5 | Gas unit with commitment | On/off + min load | Incremental + `active` |\n\n**Note:** The `piecewise(...)` expression can appear on either side of\nthe comparison operator (`==`, `<=`, `>=`). For example, both\n`linopy.piecewise(x, x_pts, y_pts) == y` and `y == linopy.piecewise(...)` work." + "source": "# Piecewise Linear Constraints Tutorial\n\nThis notebook demonstrates linopy's piecewise linear (PWL) constraint formulations.\nEach example builds a separate dispatch model where a single power plant must meet\na time-varying demand.\n\n| Example | Plant | Limitation | Formulation |\n|---------|-------|------------|-------------|\n| 1 | Gas turbine (0\u2013100 MW) | Convex heat rate | SOS2 |\n| 2 | Coal plant (0\u2013150 MW) | Monotonic heat rate | Incremental |\n| 3 | Diesel generator (off or 50\u201380 MW) | Forbidden zone | Disjunctive |\n| 4 | Concave efficiency curve | Inequality bound | LP |\n| 5 | Gas unit with commitment | On/off + min load | Incremental + `active` |\n\n**Note:** The `piecewise(...)` expression can appear on either side of\nthe comparison operator (`==`, `<=`, `>=`). For example, both\n`linopy.piecewise(x, x_pts, y_pts) == y` and `y == linopy.piecewise(...)` work." }, { "cell_type": "code", @@ -90,7 +90,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. SOS2 formulation — Gas turbine\n", + "## 1. SOS2 formulation \u2014 Gas turbine\n", "\n", "The gas turbine has a **convex** heat rate: efficient at moderate load,\n", "increasingly fuel-hungry at high output. We use the **SOS2** formulation\n", @@ -173,7 +173,7 @@ } }, "source": [ - "m1.solve()" + "m1.solve(reformulate_sos=\"auto\")" ], "outputs": [], "execution_count": null @@ -224,11 +224,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Incremental formulation — Coal plant\n", + "## 2. Incremental formulation \u2014 Coal plant\n", "\n", "The coal plant has a **monotonically increasing** heat rate. Since all\n", "breakpoints are strictly monotonic, we can use the **incremental**\n", - "formulation — which uses fill-fraction variables with binary indicators." + "formulation \u2014 which uses fill-fraction variables with binary indicators." ] }, { @@ -306,7 +306,7 @@ } }, "source": [ - "m2.solve();" + "m2.solve(reformulate_sos=\"auto\");" ], "outputs": [], "execution_count": null @@ -357,10 +357,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Disjunctive formulation — Diesel generator\n", + "## 3. Disjunctive formulation \u2014 Diesel generator\n", "\n", "The diesel generator has a **forbidden operating zone**: it must either\n", - "be off (0 MW) or run between 50–80 MW. Because of this gap, we use\n", + "be off (0 MW) or run between 50\u201380 MW. Because of this gap, we use\n", "**disjunctive** piecewise constraints via `linopy.segments()` and add a\n", "high-cost **backup** source to cover demand when the diesel is off or\n", "at its maximum.\n", @@ -446,7 +446,7 @@ } }, "source": [ - "m3.solve()" + "m3.solve(reformulate_sos=\"auto\")" ], "outputs": [], "execution_count": null @@ -476,11 +476,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. LP formulation — Concave efficiency bound\n", + "## 4. LP formulation \u2014 Concave efficiency bound\n", "\n", "When the piecewise function is **concave** and we use a `>=` constraint\n", "(i.e. `pw >= y`, meaning y is bounded above by pw), linopy can use a\n", - "pure **LP** formulation with tangent-line constraints — no SOS2 or\n", + "pure **LP** formulation with tangent-line constraints \u2014 no SOS2 or\n", "binary variables needed. This is the fastest to solve.\n", "\n", "For this formulation, the x-breakpoints must be in **strictly increasing**\n", @@ -514,7 +514,7 @@ "power = m4.add_variables(name=\"power\", lower=0, upper=120, coords=[time])\n", "fuel = m4.add_variables(name=\"fuel\", lower=0, coords=[time])\n", "\n", - "# pw >= fuel means fuel <= concave_function(power) → auto-selects LP method\n", + "# pw >= fuel means fuel <= concave_function(power) \u2192 auto-selects LP method\n", "m4.add_piecewise_constraints(\n", " linopy.piecewise(power, x_pts4, y_pts4) >= fuel,\n", " name=\"pwl\",\n", @@ -544,7 +544,7 @@ } }, "source": [ - "m4.solve()" + "m4.solve(reformulate_sos=\"auto\")" ], "outputs": [], "execution_count": null @@ -595,7 +595,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 5. Slopes mode — Building breakpoints from slopes\n", + "## 5. Slopes mode \u2014 Building breakpoints from slopes\n", "\n", "Sometimes you know the **slope** of each segment rather than the y-values\n", "at each breakpoint. The `breakpoints()` factory can compute y-values from\n", @@ -628,7 +628,7 @@ }, { "cell_type": "markdown", - "source": "## 6. Active parameter — Unit commitment with piecewise efficiency\n\nIn unit commitment problems, a binary variable $u_t$ controls whether a\nunit is **on** or **off**. When off, both power output and fuel consumption\nmust be zero. When on, the unit operates within its piecewise-linear\nefficiency curve between $P_{min}$ and $P_{max}$.\n\nThe `active` parameter on `piecewise()` handles this by gating the\ninternal PWL formulation with the commitment binary:\n\n- **Incremental:** delta bounds tighten from $\\delta_i \\leq 1$ to\n $\\delta_i \\leq u$, and base terms are multiplied by $u$\n- **SOS2:** convexity constraint becomes $\\sum \\lambda_i = u$\n- **Disjunctive:** segment selection becomes $\\sum z_k = u$\n\nThis is the only gating behavior expressible with pure linear constraints.\nSelectively *relaxing* the PWL (letting x, y float freely when off) would\nrequire big-M or indicator constraints.", + "source": "## 6. Active parameter \u2014 Unit commitment with piecewise efficiency\n\nIn unit commitment problems, a binary variable $u_t$ controls whether a\nunit is **on** or **off**. When off, both power output and fuel consumption\nmust be zero. When on, the unit operates within its piecewise-linear\nefficiency curve between $P_{min}$ and $P_{max}$.\n\nThe `active` parameter on `piecewise()` handles this by gating the\ninternal PWL formulation with the commitment binary:\n\n- **Incremental:** delta bounds tighten from $\\delta_i \\leq 1$ to\n $\\delta_i \\leq u$, and base terms are multiplied by $u$\n- **SOS2:** convexity constraint becomes $\\sum \\lambda_i = u$\n- **Disjunctive:** segment selection becomes $\\sum z_k = u$\n\nThis is the only gating behavior expressible with pure linear constraints.\nSelectively *relaxing* the PWL (letting x, y float freely when off) would\nrequire big-M or indicator constraints.", "metadata": {} }, { @@ -666,7 +666,7 @@ }, { "cell_type": "code", - "source": "m6.solve()", + "source": "m6.solve(reformulate_sos=\"auto\")", "metadata": { "ExecuteTime": { "end_time": "2026-03-09T10:17:28.878112Z", @@ -855,7 +855,7 @@ }, { "cell_type": "markdown", - "source": "At **t=1**, demand (15 MW) is below the minimum load (30 MW). The solver\nkeeps the unit off (`commit=0`), so `power=0` and `fuel=0` — the `active`\nparameter enforces this. Demand is met by the backup source.\n\nAt **t=2** and **t=3**, the unit commits and operates on the PWL curve.", + "source": "At **t=1**, demand (15 MW) is below the minimum load (30 MW). The solver\nkeeps the unit off (`commit=0`), so `power=0` and `fuel=0` \u2014 the `active`\nparameter enforces this. Demand is met by the backup source.\n\nAt **t=2** and **t=3**, the unit commits and operates on the PWL curve.", "metadata": {} } ], diff --git a/pyproject.toml b/pyproject.toml index 14a53a22..ad5390b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ docs = [ "nbsphinx-link==1.3.0", "docutils<0.21", "numpy<2", - "gurobipy==11.0.2", + "gurobipy>=13.0.0", "ipykernel==6.29.5", "matplotlib==3.9.1", "highspy>=1.7.1", From 5711067c6f2ff19835d2d1ef975cdfb88096efd3 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:24:31 +0100 Subject: [PATCH 6/7] Fix RTD build and notebook compatibility - Update gurobipy pin from ==11.0.2 (expired license) to >=13.0.0 - Use reformulate_sos="auto" in piecewise-linear-constraints notebook so it works with HiGHS - Remove broken nbsphinx_execute_never (not supported in nbsphinx 0.9.4); use notebook metadata {"nbsphinx": {"execute": "never"}} instead for solve-on-oetc and solve-on-remote - Set nbsphinx_allow_errors=False so real errors fail the RTD build - Remove gurobipy uninstall workaround from CI workflow Co-Authored-By: Claude Opus 4.6 --- doc/conf.py | 6 ------ examples/solve-on-oetc.ipynb | 3 +++ examples/solve-on-remote.ipynb | 14 ++++++++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 5e9b88a8..5525d366 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -98,12 +98,6 @@ "--InlineBackend.rc={'figure.dpi': 96}", ] -# Exclude notebooks that require credentials or special setup -nbsphinx_execute_never = [ - "**/solve-on-oetc*", - "**/solve-on-remote*", -] - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for diff --git a/examples/solve-on-oetc.ipynb b/examples/solve-on-oetc.ipynb index 7459bdb9..9582acdb 100644 --- a/examples/solve-on-oetc.ipynb +++ b/examples/solve-on-oetc.ipynb @@ -356,6 +356,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" + }, + "nbsphinx": { + "execute": "never" } }, "nbformat": 4, diff --git a/examples/solve-on-remote.ipynb b/examples/solve-on-remote.ipynb index 16e01b41..f4663e81 100644 --- a/examples/solve-on-remote.ipynb +++ b/examples/solve-on-remote.ipynb @@ -4,12 +4,15 @@ "cell_type": "markdown", "id": "4db583af", "metadata": {}, - "source": ["# Remote Solving with SSH", "\n", + "source": [ + "# Remote Solving with SSH", + "\n", "This example demonstrates how linopy can solve optimization models on remote machines using SSH connections. This is one of two remote solving options available in linopy", "\n", "1. **SSH Remote Solving** (this example) - Connect to your own servers via SSH\\n2. **OETC Cloud Solving** - Use cloud-based optimization services (see `solve-on-oetc.ipynb`)", "\n\n", - "## SSH Remote Solving\\n\\nSSH remote solving is ideal when you have:\\n* Access to dedicated servers with optimization solvers installed\\n* Full control over the computing environment\\n* Existing infrastructure for optimization workloads\\n\\n## What you need for SSH remote solving:\\n* A running installation of paramiko on your local machine (`pip install paramiko`)\\n* A remote server with a working installation of linopy (e.g., in a conda environment)\\n* SSH access to that machine\\n\\n## How SSH Remote Solving Works\\n\\nThe workflow consists of the following steps, most of which linopy handles automatically:\\n\\n1. Define a model on the local machine\\n2. Save the model on the remote machine via SSH\\n3. Load, solve and write out the model on the remote machine\\n4. Copy the solved model back to the local machine\\n5. Load the solved model on the local machine\\n\\nThe model initialization happens locally, while the actual solving happens remotely.\""] + "## SSH Remote Solving\\n\\nSSH remote solving is ideal when you have:\\n* Access to dedicated servers with optimization solvers installed\\n* Full control over the computing environment\\n* Existing infrastructure for optimization workloads\\n\\n## What you need for SSH remote solving:\\n* A running installation of paramiko on your local machine (`pip install paramiko`)\\n* A remote server with a working installation of linopy (e.g., in a conda environment)\\n* SSH access to that machine\\n\\n## How SSH Remote Solving Works\\n\\nThe workflow consists of the following steps, most of which linopy handles automatically:\\n\\n1. Define a model on the local machine\\n2. Save the model on the remote machine via SSH\\n3. Load, solve and write out the model on the remote machine\\n4. Copy the solved model back to the local machine\\n5. Load the solved model on the local machine\\n\\nThe model initialization happens locally, while the actual solving happens remotely.\"" + ] }, { "cell_type": "markdown", @@ -311,7 +314,7 @@ "\n", ".xr-section-summary-in + label:before {\n", " display: inline-block;\n", - " content: '►';\n", + " content: '\u25ba';\n", " font-size: 11px;\n", " width: 15px;\n", " text-align: center;\n", @@ -322,7 +325,7 @@ "}\n", "\n", ".xr-section-summary-in:checked + label:before {\n", - " content: '▼';\n", + " content: '\u25bc';\n", "}\n", "\n", ".xr-section-summary-in:checked + label > span {\n", @@ -610,6 +613,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.3" + }, + "nbsphinx": { + "execute": "never" } }, "nbformat": 4, From 5fdb04429330c4272a76ea072bc3761a11509369 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:29:25 +0100 Subject: [PATCH 7/7] Add notes to non-executed notebooks explaining missing outputs Readers can now see why solve-on-oetc and solve-on-remote notebooks have no cell outputs in the documentation. Co-Authored-By: Claude Opus 4.6 --- examples/solve-on-oetc.ipynb | 7 +++++++ examples/solve-on-remote.ipynb | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/examples/solve-on-oetc.ipynb b/examples/solve-on-oetc.ipynb index 9582acdb..975cd1fe 100644 --- a/examples/solve-on-oetc.ipynb +++ b/examples/solve-on-oetc.ipynb @@ -30,6 +30,13 @@ "All of these steps are handled automatically by linopy's `OetcHandler`." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** This notebook requires Google Cloud credentials and access to the OETC platform. It is not executed during the documentation build, so no cell outputs are shown. To run it yourself, install the `linopy[oetc]` extra and configure your credentials." + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/examples/solve-on-remote.ipynb b/examples/solve-on-remote.ipynb index f4663e81..4e2a1b13 100644 --- a/examples/solve-on-remote.ipynb +++ b/examples/solve-on-remote.ipynb @@ -14,6 +14,13 @@ "## SSH Remote Solving\\n\\nSSH remote solving is ideal when you have:\\n* Access to dedicated servers with optimization solvers installed\\n* Full control over the computing environment\\n* Existing infrastructure for optimization workloads\\n\\n## What you need for SSH remote solving:\\n* A running installation of paramiko on your local machine (`pip install paramiko`)\\n* A remote server with a working installation of linopy (e.g., in a conda environment)\\n* SSH access to that machine\\n\\n## How SSH Remote Solving Works\\n\\nThe workflow consists of the following steps, most of which linopy handles automatically:\\n\\n1. Define a model on the local machine\\n2. Save the model on the remote machine via SSH\\n3. Load, solve and write out the model on the remote machine\\n4. Copy the solved model back to the local machine\\n5. Load the solved model on the local machine\\n\\nThe model initialization happens locally, while the actual solving happens remotely.\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** This notebook requires SSH access to a remote server with a solver installed. It is not executed during the documentation build, so no cell outputs are shown. To run it yourself, configure SSH access and install a solver on the remote machine." + ] + }, { "cell_type": "markdown", "id": "together-ocean",