From f8446d9249b9eee43b7360c3550d4e6b985d7593 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 11 May 2026 15:09:55 +0200 Subject: [PATCH 1/3] docs: silence HiGHS console output in tutorial notebooks HiGHS prints a banner + progress lines to the Python REPL on every m.solve() call by default. In a tutorial that calls solve many times, this drowns the actual lesson in solver chatter. Pass output_flag=False (a HiGHS solver option forwarded via **solver_options) to suppress it. Touches the four notebooks where solver_name="highs" is the only solver invoked: - create-a-model.ipynb - create-a-model-with-coordinates.ipynb - manipulating-models.ipynb (9 solves) - transport-tutorial.ipynb Left alone: - infeasible-model.ipynb (uses Gurobi, kwarg is OutputFlag there; also showing solver feedback may be pedagogically relevant for infeasibility detection). - solve-on-remote.ipynb / solve-on-oetc.ipynb (remote handler manages its own logging). - piecewise-*.ipynb (already addressed in #677). Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/create-a-model-with-coordinates.ipynb | 2 +- examples/create-a-model.ipynb | 2 +- examples/manipulating-models.ipynb | 18 +++++++++--------- examples/transport-tutorial.ipynb | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/create-a-model-with-coordinates.ipynb b/examples/create-a-model-with-coordinates.ipynb index f2b12eed..e84c21b9 100644 --- a/examples/create-a-model-with-coordinates.ipynb +++ b/examples/create-a-model-with-coordinates.ipynb @@ -150,7 +150,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")" + "m.solve(solver_name=\"highs\", output_flag=False)" ] }, { diff --git a/examples/create-a-model.ipynb b/examples/create-a-model.ipynb index a158e0cf..b6fc9705 100644 --- a/examples/create-a-model.ipynb +++ b/examples/create-a-model.ipynb @@ -215,7 +215,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")" + "m.solve(solver_name=\"highs\", output_flag=False)" ] }, { diff --git a/examples/manipulating-models.ipynb b/examples/manipulating-models.ipynb index 81106ab3..6903386b 100644 --- a/examples/manipulating-models.ipynb +++ b/examples/manipulating-models.ipynb @@ -48,9 +48,9 @@ "con2 = m.add_constraints(5 * x + 2 * y >= 3 * factor, name=\"con2\")\n", "\n", "m.add_objective(x + 2 * y)\n", - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "\n", - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -95,7 +95,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -137,7 +137,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -190,7 +190,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -242,7 +242,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -276,7 +276,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "sol = m.solution.to_dataframe()\n", "sol.plot(grid=True, ylabel=\"Optimal Value\")" ] @@ -326,7 +326,7 @@ "# Penalize activation of z in the objective\n", "m.objective = x + 3 * y + 10 * z\n", "\n", - "m.solve(solver_name=\"highs\")" + "m.solve(solver_name=\"highs\", output_flag=False)" ] }, { @@ -346,7 +346,7 @@ "source": [ "m.variables.binaries.fix()\n", "m.variables.binaries.relax()\n", - "m.solve(solver_name=\"highs\")\n", + "m.solve(solver_name=\"highs\", output_flag=False)\n", "\n", "# Dual values are now available on the constraints\n", "m.constraints[\"con1\"].dual" diff --git a/examples/transport-tutorial.ipynb b/examples/transport-tutorial.ipynb index b42e67e8..cd5cdccd 100644 --- a/examples/transport-tutorial.ipynb +++ b/examples/transport-tutorial.ipynb @@ -417,7 +417,7 @@ "outputs": [], "source": [ "# Solve the model\n", - "m.solve(solver_name=\"highs\")" + "m.solve(solver_name=\"highs\", output_flag=False)" ] }, { From 6d61112ec588a2b56e8c664a0bcebea9a8c53273 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Mon, 11 May 2026 15:24:47 +0200 Subject: [PATCH 2/3] docs: silence HiGHS console output in piecewise tutorials too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the log-silencing scope to the two piecewise tutorials, which together call m.solve() nine times. Same transformation as the other notebooks — output_flag=False as a HiGHS-specific kwarg forwarded via **solver_options. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/piecewise-inequality-bounds.ipynb | 40 ++++++++++----------- examples/piecewise-linear-constraints.ipynb | 16 ++++----- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/piecewise-inequality-bounds.ipynb b/examples/piecewise-inequality-bounds.ipynb index a79c612d..101f28d5 100644 --- a/examples/piecewise-inequality-bounds.ipynb +++ b/examples/piecewise-inequality-bounds.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Piecewise inequalities \u2014 per-tuple sign\n", + "# Piecewise inequalities — per-tuple sign\n", "\n", "`add_piecewise_formulation` accepts an optional third tuple element, `\"<=\"` or `\">=\"`, that marks one expression as **bounded** by the piecewise curve instead of pinned to it:\n", "\n", @@ -15,17 +15,17 @@ ")\n", "```\n", "\n", - "This notebook walks through the geometry, the curvature \u00d7 sign matching that lets `method=\"auto\"` skip MIP machinery entirely, and the feasible regions produced by each method (LP, SOS2, incremental). For the formulation math see the [reference page](piecewise-linear-constraints).\n", + "This notebook walks through the geometry, the curvature × sign matching that lets `method=\"auto\"` skip MIP machinery entirely, and the feasible regions produced by each method (LP, SOS2, incremental). For the formulation math see the [reference page](piecewise-linear-constraints).\n", "\n", "## Key points\n", "\n", "| Tuple form | Behaviour |\n", "|---|---|\n", "| `(expr, breaks)` | Pinned: `expr` lies exactly on the curve. |\n", - "| `(expr, breaks, \"<=\")` | Bounded above: `expr \u2264 f(other tuples)`. |\n", - "| `(expr, breaks, \">=\")` | Bounded below: `expr \u2265 f(other tuples)`. |\n", + "| `(expr, breaks, \"<=\")` | Bounded above: `expr ≤ f(other tuples)`. |\n", + "| `(expr, breaks, \">=\")` | Bounded below: `expr ≥ f(other tuples)`. |\n", "\n", - "Currently at most one tuple may carry a non-equality sign, and 3+ tuples must all be equality. Multi-bounded and N\u22653 inequality cases aren't supported yet \u2014 if you have a concrete use case, please open an issue at https://github.com/PyPSA/linopy/issues so we can scope it properly." + "Currently at most one tuple may carry a non-equality sign, and 3+ tuples must all be equality. Multi-bounded and N≥3 inequality cases aren't supported yet — if you have a concrete use case, please open an issue at https://github.com/PyPSA/linopy/issues so we can scope it properly." ] }, { @@ -48,7 +48,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## Setup \u2014 a concave curve\n\nWe use a concave, monotonically increasing curve. With a tuple bounded `<=`, the LP method is applicable (concave + `<=` is a tight relaxation)." + "source": "## Setup — a concave curve\n\nWe use a concave, monotonically increasing curve. With a tuple bounded `<=`, the LP method is applicable (concave + `<=` is a tight relaxation)." }, { "cell_type": "code", @@ -79,11 +79,11 @@ "\n", "With one tuple bounded `<=` and our concave curve, the three methods give the **same** feasible region within `[x_0, x_n]`:\n", "\n", - "- **`method=\"lp\"`** \u2014 tangent lines + domain bounds. No auxiliary variables.\n", - "- **`method=\"sos2\"`** \u2014 lambdas + SOS2 + split link (pinned equality, bounded signed). Solver picks the piece.\n", - "- **`method=\"incremental\"`** \u2014 delta fractions + binaries + split link. Same mathematics, MIP encoding instead of SOS2.\n", + "- **`method=\"lp\"`** — tangent lines + domain bounds. No auxiliary variables.\n", + "- **`method=\"sos2\"`** — lambdas + SOS2 + split link (pinned equality, bounded signed). Solver picks the piece.\n", + "- **`method=\"incremental\"`** — delta fractions + binaries + split link. Same mathematics, MIP encoding instead of SOS2.\n", "\n", - "`method=\"auto\"` dispatches to `\"lp\"` whenever applicable \u2014 it's always preferable because it's pure LP.\n", + "`method=\"auto\"` dispatches to `\"lp\"` whenever applicable — it's always preferable because it's pure LP.\n", "\n", "Let's verify they produce the same solution at `power=15`." ] @@ -98,17 +98,17 @@ } }, "outputs": [], - "source": "def solve(method, power_val):\n m = linopy.Model()\n power = m.add_variables(lower=0, upper=30, name=\"power\")\n fuel = m.add_variables(lower=0, upper=40, name=\"fuel\")\n m.add_piecewise_formulation(\n (fuel, y_pts, \"<=\"), # bounded\n (power, x_pts), # pinned\n method=method,\n )\n m.add_constraints(power == power_val)\n m.add_objective(-fuel) # maximise fuel to push against the bound\n m.solve()\n return float(m.solution[\"fuel\"]), list(m.variables), list(m.constraints)\n\n\nfor method in [\"lp\", \"sos2\", \"incremental\"]:\n fuel_val, vars_, cons_ = solve(method, 15)\n print(f\"{method:12}: fuel={fuel_val:.2f} vars={vars_} cons={cons_}\")" + "source": "def solve(method, power_val):\n m = linopy.Model()\n power = m.add_variables(lower=0, upper=30, name=\"power\")\n fuel = m.add_variables(lower=0, upper=40, name=\"fuel\")\n m.add_piecewise_formulation(\n (fuel, y_pts, \"<=\"), # bounded\n (power, x_pts), # pinned\n method=method,\n )\n m.add_constraints(power == power_val)\n m.add_objective(-fuel) # maximise fuel to push against the bound\n m.solve(output_flag=False)\n return float(m.solution[\"fuel\"]), list(m.variables), list(m.constraints)\n\n\nfor method in [\"lp\", \"sos2\", \"incremental\"]:\n fuel_val, vars_, cons_ = solve(method, 15)\n print(f\"{method:12}: fuel={fuel_val:.2f} vars={vars_} cons={cons_}\")" }, { "cell_type": "markdown", "metadata": {}, - "source": "All three give `fuel=25` at `power=15` (which is `f(15)` exactly) \u2014 the math is equivalent. The LP method is strictly cheaper: no auxiliary variables, just three chord constraints and two domain bounds.\n\nThe SOS2 and incremental methods create lambdas (or deltas + binaries) and split the link into a pinned-equality constraint plus a signed bounded link \u2014 but the feasible region is the same." + "source": "All three give `fuel=25` at `power=15` (which is `f(15)` exactly) — the math is equivalent. The LP method is strictly cheaper: no auxiliary variables, just three chord constraints and two domain bounds.\n\nThe SOS2 and incremental methods create lambdas (or deltas + binaries) and split the link into a pinned-equality constraint plus a signed bounded link — but the feasible region is the same." }, { "cell_type": "markdown", "metadata": {}, - "source": "## Visualising the feasible region\n\nThe feasible region for `(power, fuel)` with `fuel` bounded `<=` is the **hypograph** of `f` restricted to the curve's x-domain:\n\n$$\\{ (x, y) : x_0 \\le x \\le x_n,\\ y \\le f(x) \\}$$\n\nWe colour green feasible points, red infeasible ones. Three test points:\n\n- `(15, 15)` \u2014 inside the curve, `15 \u2264 f(15)=25` \u2713\n- `(15, 25)` \u2014 on the curve \u2713\n- `(15, 29)` \u2014 above `f(15)`, should be infeasible \u2717\n- `(35, 20)` \u2014 power beyond domain, infeasible \u2717" + "source": "## Visualising the feasible region\n\nThe feasible region for `(power, fuel)` with `fuel` bounded `<=` is the **hypograph** of `f` restricted to the curve's x-domain:\n\n$$\\{ (x, y) : x_0 \\le x \\le x_n,\\ y \\le f(x) \\}$$\n\nWe colour green feasible points, red infeasible ones. Three test points:\n\n- `(15, 15)` — inside the curve, `15 ≤ f(15)=25` ✓\n- `(15, 25)` — on the curve ✓\n- `(15, 29)` — above `f(15)`, should be infeasible ✗\n- `(35, 20)` — power beyond domain, infeasible ✗" }, { "cell_type": "code", @@ -144,7 +144,7 @@ "ax.set(\n", " xlabel=\"power\",\n", " ylabel=\"fuel\",\n", - " title=\"sign='<=' feasible region \u2014 hypograph of f(x) on [x_0, x_n]\",\n", + " title=\"sign='<=' feasible region — hypograph of f(x) on [x_0, x_n]\",\n", ")\n", "ax.grid(alpha=0.3)\n", "ax.legend()\n", @@ -157,16 +157,16 @@ "source": [ "## When is LP the right choice?\n", "\n", - "`tangent_lines` imposes the **intersection** of chord inequalities. Whether that intersection matches the true hypograph/epigraph of `f` depends on the curvature \u00d7 sign combination:\n", + "`tangent_lines` imposes the **intersection** of chord inequalities. Whether that intersection matches the true hypograph/epigraph of `f` depends on the curvature × sign combination:\n", "\n", "| curvature | bounded `<=` | bounded `>=` |\n", "|-----------|--------------|--------------|\n", - "| **concave** | **hypograph (exact \u2713)** | **wrong region** \u2014 requires `y \u2265 max_k chord_k(x) > f(x)` |\n", - "| **convex** | **wrong region** \u2014 requires `y \u2264 min_k chord_k(x) < f(x)` | **epigraph (exact \u2713)** |\n", + "| **concave** | **hypograph (exact ✓)** | **wrong region** — requires `y ≥ max_k chord_k(x) > f(x)` |\n", + "| **convex** | **wrong region** — requires `y ≤ min_k chord_k(x) < f(x)` | **epigraph (exact ✓)** |\n", "| linear | exact | exact |\n", "| mixed (non-convex) | convex hull of `f` (wrong for exact hypograph) | concave hull of `f` (wrong for exact epigraph) |\n", "\n", - "In the \u2717 cases, tangent lines do **not** give a loose relaxation \u2014 they give a **strictly wrong feasible region** that rejects points satisfying the true constraint. Example: for a concave `f` with `y \u2265 f(x)`, the chord of any piece extrapolated over another piece's x-range lies *above* `f`, so `y \u2265 max_k chord_k(x)` forbids `y = f(x)` itself.\n", + "In the ✗ cases, tangent lines do **not** give a loose relaxation — they give a **strictly wrong feasible region** that rejects points satisfying the true constraint. Example: for a concave `f` with `y ≥ f(x)`, the chord of any piece extrapolated over another piece's x-range lies *above* `f`, so `y ≥ max_k chord_k(x)` forbids `y = f(x)` itself.\n", "\n", "`method=\"auto\"` dispatches to LP only in the two **exact** cases (concave + `<=` or convex + `>=`). For the other combinations it falls back to SOS2 or incremental, which encode the hypograph/epigraph exactly via discrete piece selection.\n", "\n", @@ -185,12 +185,12 @@ } }, "outputs": [], - "source": "# 1. Non-convex curve: auto falls back (LP relaxation would be loose)\nx_nc = [0, 10, 20, 30]\ny_nc = [0, 20, 10, 30] # slopes change sign \u2192 mixed convexity\n\nm1 = linopy.Model()\nx1 = m1.add_variables(lower=0, upper=30, name=\"x\")\ny1 = m1.add_variables(lower=0, upper=40, name=\"y\")\nf1 = m1.add_piecewise_formulation((y1, y_nc, \"<=\"), (x1, x_nc))\nprint(f\"non-convex + '<=' \u2192 {f1.method}\")\n\n# 2. Concave curve + sign='>=': LP would be loose \u2192 auto falls back to MIP\nx_cc = [0, 10, 20, 30]\ny_cc = [0, 20, 30, 35] # concave\n\nm2 = linopy.Model()\nx2 = m2.add_variables(lower=0, upper=30, name=\"x\")\ny2 = m2.add_variables(lower=0, upper=40, name=\"y\")\nf2 = m2.add_piecewise_formulation((y2, y_cc, \">=\"), (x2, x_cc))\nprint(f\"concave + '>=' \u2192 {f2.method}\")\n\n# 3. Explicit method=\"lp\" with mismatched curvature raises\ntry:\n m3 = linopy.Model()\n x3 = m3.add_variables(lower=0, upper=30, name=\"x\")\n y3 = m3.add_variables(lower=0, upper=40, name=\"y\")\n m3.add_piecewise_formulation((y3, y_cc, \">=\"), (x3, x_cc), method=\"lp\")\nexcept ValueError as e:\n print(f\"lp(concave, '>=') \u2192 raises: {e}\")" + "source": "# 1. Non-convex curve: auto falls back (LP relaxation would be loose)\nx_nc = [0, 10, 20, 30]\ny_nc = [0, 20, 10, 30] # slopes change sign → mixed convexity\n\nm1 = linopy.Model()\nx1 = m1.add_variables(lower=0, upper=30, name=\"x\")\ny1 = m1.add_variables(lower=0, upper=40, name=\"y\")\nf1 = m1.add_piecewise_formulation((y1, y_nc, \"<=\"), (x1, x_nc))\nprint(f\"non-convex + '<=' → {f1.method}\")\n\n# 2. Concave curve + sign='>=': LP would be loose → auto falls back to MIP\nx_cc = [0, 10, 20, 30]\ny_cc = [0, 20, 30, 35] # concave\n\nm2 = linopy.Model()\nx2 = m2.add_variables(lower=0, upper=30, name=\"x\")\ny2 = m2.add_variables(lower=0, upper=40, name=\"y\")\nf2 = m2.add_piecewise_formulation((y2, y_cc, \">=\"), (x2, x_cc))\nprint(f\"concave + '>=' → {f2.method}\")\n\n# 3. Explicit method=\"lp\" with mismatched curvature raises\ntry:\n m3 = linopy.Model()\n x3 = m3.add_variables(lower=0, upper=30, name=\"x\")\n y3 = m3.add_variables(lower=0, upper=40, name=\"y\")\n m3.add_piecewise_formulation((y3, y_cc, \">=\"), (x3, x_cc), method=\"lp\")\nexcept ValueError as e:\n print(f\"lp(concave, '>=') → raises: {e}\")" }, { "cell_type": "markdown", "metadata": {}, - "source": "## Summary\n\n- Default is all-equality: every tuple lies on the curve.\n- Append `\"<=\"` or `\">=\"` as a third tuple element to mark one expression as bounded by the curve.\n- `method=\"auto\"` picks the most efficient formulation: LP for matching-curvature 2-variable inequalities, otherwise SOS2 or incremental.\n- At most one tuple may be bounded; with 3+ tuples all must be equality. Multi-bounded and N\u22653 inequality use cases \u2014 please open an issue at https://github.com/PyPSA/linopy/issues so we can scope them." + "source": "## Summary\n\n- Default is all-equality: every tuple lies on the curve.\n- Append `\"<=\"` or `\">=\"` as a third tuple element to mark one expression as bounded by the curve.\n- `method=\"auto\"` picks the most efficient formulation: LP for matching-curvature 2-variable inequalities, otherwise SOS2 or incremental.\n- At most one tuple may be bounded; with 3+ tuples all must be equality. Multi-bounded and N≥3 inequality use cases — please open an issue at https://github.com/PyPSA/linopy/issues so we can scope them." } ], "metadata": { diff --git a/examples/piecewise-linear-constraints.ipynb b/examples/piecewise-linear-constraints.ipynb index 392ca8f1..fe1da9b8 100644 --- a/examples/piecewise-linear-constraints.ipynb +++ b/examples/piecewise-linear-constraints.ipynb @@ -81,7 +81,7 @@ "pwf = m.add_piecewise_formulation((power, x_pts), (fuel, y_pts))\n", "m.add_constraints(power == demand, name=\"demand\")\n", "m.add_objective(fuel.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "\n", "print(pwf) # inspect the auto-resolved method\n", "m.solution[[\"power\", \"fuel\"]].to_pandas()" @@ -136,7 +136,7 @@ " m.add_piecewise_formulation((power, x_pts), (fuel, y_pts), method=method)\n", " m.add_constraints(power == demand, name=\"demand\")\n", " m.add_objective(fuel.sum())\n", - " m.solve(reformulate_sos=\"auto\")\n", + " m.solve(reformulate_sos=\"auto\", output_flag=False)\n", " return m.solution[\"fuel\"].to_pandas()\n", "\n", "\n", @@ -175,7 +175,7 @@ ")\n", "m.add_constraints(power + backup == xr.DataArray([15, 60, 75], coords=[time]))\n", "m.add_objective(cost.sum() + 10 * backup.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "m.solution[[\"power\", \"cost\", \"backup\"]].to_pandas()" ] }, @@ -225,7 +225,7 @@ ")\n", "m.add_constraints(power == xr.DataArray([30, 80, 100], coords=[time]))\n", "m.add_objective(-fuel.sum()) # push fuel against the bound\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "\n", "print(f\"resolved method={pwf.method}, curvature={pwf.convexity}\")\n", "m.solution[[\"power\", \"fuel\"]].to_pandas()" @@ -282,7 +282,7 @@ "# demand below p_min at t=1 — commit must be 0 and backup covers it\n", "m.add_constraints(power + backup == xr.DataArray([15, 80, 40], coords=[time]))\n", "m.add_objective(fuel.sum() + 50 * commit.sum() + 200 * backup.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "m.solution[[\"commit\", \"power\", \"fuel\", \"backup\"]].to_pandas()" ] }, @@ -330,7 +330,7 @@ ")\n", "m.add_constraints(fuel == xr.DataArray([20, 100, 160], coords=[time]))\n", "m.add_objective(power.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "m.solution[[\"power\", \"fuel\", \"heat\"]].to_pandas().round(2)" ] }, @@ -388,7 +388,7 @@ "m.add_piecewise_formulation((power, x_gen), (fuel, y_gen))\n", "m.add_constraints(power.sum(\"gen\") == xr.DataArray([80, 120, 50], coords=[time]))\n", "m.add_objective(fuel.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "m.solution[[\"power\", \"fuel\"]].to_dataframe()" ] }, @@ -417,7 +417,7 @@ ")\n", "m.add_constraints(power == demand, name=\"demand\")\n", "m.add_objective(fuel.sum())\n", - "m.solve(reformulate_sos=\"auto\")\n", + "m.solve(reformulate_sos=\"auto\", output_flag=False)\n", "\n", "m.solution[[\"power\", \"fuel\"]].to_pandas()" ] From ae018a38eb8d6e7aeb819c27c50a49f7ec571c89 Mon Sep 17 00:00:00 2001 From: FBumann <117816358+FBumann@users.noreply.github.com> Date: Wed, 13 May 2026 09:44:17 +0200 Subject: [PATCH 3/3] docs: fix broken toctree, refresh API reference, and clean up references - Add doc/coordinate-alignment.nblink so the index.rst toctree entry resolves to examples/coordinate-alignment.ipynb. - Update api.rst to match the current public API: add the missing solver classes (COPT, Knitro, MindOpt, PIPS, cuPDLPx), expose top-level helpers (align, merge, options, EvolvingAPIWarning, PerformanceWarning), add the missing Model methods (add_sos_constraints, reformulate_sos_constraints, compute_infeasibilities, format_infeasibilities), add Variable methods (to_linexpr, fix/unfix, relax/unrelax), add sections for QuadraticExpression, Objective, and RemoteHandler, remove the duplicate Variables.integers, and fix the "hook" -> "hood" typo. - contributing.rst: replace stale Black reference with ruff, correct the nblink example (proper JSON, right path, fixed RST indentation that was breaking pygments), and use pre-commit run --all-files. - benchmark.rst: fix the rendered objective, which read as a product of two variables; corrected to the actual linear benchmark (2x + y with x - y >= i-1, matching benchmark_linopy.py). - prerequisites.rst: add SCIP, give MOSEK a description, drop the dangling "-" after MindOpt, remove the outdated HiGHS-platforms claim, and clarify what the [solvers] extra actually pulls in. - conf.py + index.rst: bump copyright to 2026 and fix the "contnuous" typo on the landing page. Co-Authored-By: Claude Opus 4.7 (1M context) --- doc/api.rst | 58 +++++++++++++++++++++++++++++++-- doc/benchmark.rst | 4 +-- doc/conf.py | 2 +- doc/contributing.rst | 29 +++++++++-------- doc/coordinate-alignment.nblink | 3 ++ doc/index.rst | 4 +-- doc/prerequisites.rst | 13 +++++--- 7 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 doc/coordinate-alignment.nblink diff --git a/doc/api.rst b/doc/api.rst index f817d866..d4c8ca95 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -18,6 +18,7 @@ Creating a model model.Model.add_variables model.Model.add_constraints model.Model.add_objective + model.Model.add_sos_constraints model.Model.add_piecewise_formulation piecewise.PiecewiseFormulation piecewise.Slopes @@ -26,10 +27,26 @@ Creating a model piecewise.tangent_lines model.Model.linexpr model.Model.remove_constraints + model.Model.reformulate_sos_constraints + model.Model.compute_infeasibilities + model.Model.format_infeasibilities model.Model.copy -Classes under the hook +Top-level helpers +================= + +.. autosummary:: + :toctree: generated/ + + align + merge + options + EvolvingAPIWarning + PerformanceWarning + + +Classes under the hood ====================== Variable @@ -46,7 +63,11 @@ Variable variables.Variable.sum variables.Variable.where variables.Variable.sanitize - variables.Variables + variables.Variable.to_linexpr + variables.Variable.fix + variables.Variable.unfix + variables.Variable.relax + variables.Variable.unrelax variables.ScalarVariable Variables @@ -61,7 +82,6 @@ Variables variables.Variables.add variables.Variables.remove variables.Variables.continuous - variables.Variables.integers variables.Variables.binaries variables.Variables.integers variables.Variables.flat @@ -82,6 +102,24 @@ LinearExpressions expressions.merge expressions.ScalarLinearExpression + +QuadraticExpressions +-------------------- + +.. autosummary:: + :toctree: generated/ + + expressions.QuadraticExpression + + +Objective +--------- + +.. autosummary:: + :toctree: generated/ + + objective.Objective + Constraint ---------- @@ -167,13 +205,27 @@ Solvers :toctree: generated/ solvers.CBC + solvers.COPT solvers.Cplex solvers.GLPK solvers.Gurobi solvers.Highs + solvers.Knitro + solvers.MindOpt solvers.Mosek + solvers.PIPS solvers.SCIP solvers.Xpress + solvers.cuPDLPx + + +Remote solving +============== + +.. autosummary:: + :toctree: generated/ + + remote.RemoteHandler Solving diff --git a/doc/benchmark.rst b/doc/benchmark.rst index da2a98e9..db9ec407 100644 --- a/doc/benchmark.rst +++ b/doc/benchmark.rst @@ -13,9 +13,9 @@ for large problems. The following figure shows the memory usage and speed for so .. math:: - & \min \;\; \sum_{i,j} 2 x_{i,j} \; y_{i,j} \\ + & \min \;\; \sum_{i,j} 2 x_{i,j} + y_{i,j} \\ s.t. & \\ - & x_{i,j} - y_{i,j} \; \ge \; i \qquad \forall \; i,j \in \{1,...,N\} \\ + & x_{i,j} - y_{i,j} \; \ge \; i-1 \qquad \forall \; i,j \in \{1,...,N\} \\ & x_{i,j} + y_{i,j} \; \ge \; 0 \qquad \forall \; i,j \in \{1,...,N\} diff --git a/doc/conf.py b/doc/conf.py index 5525d366..54a3ffab 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = "linopy" -copyright = "2021, Fabian Hofmann" +copyright = "2021-2026, Fabian Hofmann" author = "Fabian Hofmann" # The full version, including alpha/beta/rc tags diff --git a/doc/contributing.rst b/doc/contributing.rst index 120683cb..4bb9b60a 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -13,14 +13,13 @@ You are invited to submit pull requests / issues to our Development Setup ================= -For linting, formatting and checking your code contributions -against our guidelines (e.g. we use `Black `_ as code style -and use `pre-commit `_: +For linting and formatting, we use `ruff `_ +and run it via `pre-commit `_: 1. Installation ``conda install -c conda-forge pre-commit`` or ``pip install pre-commit`` 2. Usage: * To automatically activate ``pre-commit`` on every ``git commit``: Run ``pre-commit install`` - * To manually run it: ``pre-commit run --all`` + * To manually run it: ``pre-commit run --all-files`` Running Tests ============= @@ -122,23 +121,25 @@ Then for every notebook: e.g. `Edit -> Clear all output` in JupyterLab. 3. Provide a link to the documentation: - Include a file ``foo.nblink`` located in ``doc/examples/foo.nblink`` + Include a file ``foo.nblink`` located in ``doc/foo.nblink`` with this content - .. code-block: - { - 'path' : '../../examples/foo.ipynb' - } + .. code-block:: json + + { + "path": "../examples/foo.ipynb" + } + + Adjust the path for your file's name. + This ``nblink`` allows us to link your notebook into the documentation. - Adjust the path for your file's name. - This ``nblink`` allows us to link your notebook into the documentation. 4. Link your file in the documentation: Either - * Include your ``examples/foo.nblink`` directly into one of - the documentations toctrees; or - * Tell us where in the documentation you want your example to show up + * Include your ``foo.nblink`` directly into one of + the documentation's toctrees; or + * Tell us where in the documentation you want your example to show up 5. Commit your changes. If the precommit hook you installed above kicks in, confirm diff --git a/doc/coordinate-alignment.nblink b/doc/coordinate-alignment.nblink new file mode 100644 index 00000000..ef588b91 --- /dev/null +++ b/doc/coordinate-alignment.nblink @@ -0,0 +1,3 @@ +{ + "path": "../examples/coordinate-alignment.ipynb" +} diff --git a/doc/index.rst b/doc/index.rst index a4d34ce7..c8906b74 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -23,7 +23,7 @@ Main features `xarray `__ which allows for many flexible data-handling features: -- Define (arrays of) contnuous or binary variables with +- Define (arrays of) continuous or binary variables with **coordinates**, e.g. time, consumers, etc. - Apply **arithmetic operations** on the variables like adding, subtracting, multiplying with all the **broadcasting** potentials of @@ -81,7 +81,7 @@ A BibTeX entry for LaTeX users is License ------- -Copyright 2021-2023 Fabian Hofmann +Copyright 2021-2026 Fabian Hofmann This package is published under MIT license. diff --git a/doc/prerequisites.rst b/doc/prerequisites.rst index 97d51296..afb0e8a7 100644 --- a/doc/prerequisites.rst +++ b/doc/prerequisites.rst @@ -36,21 +36,26 @@ CPU-based solvers - `Cbc `__ - open source, free, fast - `GLPK `__ - open source, free, not very fast - `HiGHS `__ - open source, free, fast +- `SCIP `__ - open source (Apache-2.0), fast MIP solver - `Gurobi `__ - closed source, commercial, very fast - `Xpress `__ - closed source, commercial, very fast (GPU acceleration available in v9.8+) - `Cplex `__ - closed source, commercial, very fast -- `MOSEK `__ -- `MindOpt `__ - +- `MOSEK `__ - closed source, commercial, strong on conic/QP +- `MindOpt `__ - closed source, commercial - `COPT `__ - closed source, commercial, very fast -For a subset of the solvers, Linopy provides a wrapper. +The ``linopy[solvers]`` extra installs the Python clients for the +supported solvers (HiGHS, SCIP, Gurobi, CPLEX, MOSEK, MindOpt, COPT, +Xpress, Knitro). For the commercial ones a separate license is still +required: .. code:: bash pip install linopy[solvers] -We recommend to install the HiGHS solver if possible, which is free and open source but not yet available on all platforms. +We recommend installing the HiGHS solver, which is free, open source, and +fast across a wide range of problem sizes: .. code:: bash