|
16 | 16 | }, |
17 | 17 | { |
18 | 18 | "cell_type": "code", |
| 19 | + "execution_count": null, |
19 | 20 | "id": "imports", |
20 | 21 | "metadata": { |
21 | 22 | "ExecuteTime": { |
22 | | - "end_time": "2025-11-26T10:12:07.434614Z", |
23 | | - "start_time": "2025-11-26T10:12:07.424509Z" |
| 23 | + "end_time": "2025-12-03T14:04:49.055855Z", |
| 24 | + "start_time": "2025-12-03T14:04:45.460505Z" |
24 | 25 | } |
25 | 26 | }, |
| 27 | + "outputs": [], |
26 | 28 | "source": [ |
27 | 29 | "import pandas as pd\n", |
28 | 30 | "\n", |
29 | 31 | "import linopy" |
30 | | - ], |
31 | | - "outputs": [], |
32 | | - "execution_count": null |
| 32 | + ] |
33 | 33 | }, |
34 | 34 | { |
35 | 35 | "cell_type": "markdown", |
|
47 | 47 | }, |
48 | 48 | { |
49 | 49 | "cell_type": "code", |
| 50 | + "execution_count": null, |
50 | 51 | "id": "model-setup", |
51 | 52 | "metadata": { |
52 | 53 | "ExecuteTime": { |
53 | | - "end_time": "2025-11-26T10:12:07.460556Z", |
54 | | - "start_time": "2025-11-26T10:12:07.442789Z" |
| 54 | + "end_time": "2025-12-03T14:04:49.840053Z", |
| 55 | + "start_time": "2025-12-03T14:04:49.669343Z" |
55 | 56 | } |
56 | 57 | }, |
| 58 | + "outputs": [], |
57 | 59 | "source": [ |
58 | 60 | "m = linopy.Model()\n", |
59 | 61 | "\n", |
60 | 62 | "time = pd.Index(range(5), name=\"time\")\n", |
61 | 63 | "\n", |
62 | 64 | "x = m.add_variables(lower=0, coords=[time], name=\"x\")\n", |
63 | 65 | "y = m.add_variables(lower=0, coords=[time], name=\"y\")" |
64 | | - ], |
65 | | - "outputs": [], |
66 | | - "execution_count": null |
| 66 | + ] |
67 | 67 | }, |
68 | 68 | { |
69 | 69 | "cell_type": "markdown", |
|
77 | 77 | }, |
78 | 78 | { |
79 | 79 | "cell_type": "code", |
| 80 | + "execution_count": null, |
80 | 81 | "id": "linear-constraint", |
81 | 82 | "metadata": { |
82 | 83 | "ExecuteTime": { |
83 | | - "end_time": "2025-11-26T10:12:07.487172Z", |
84 | | - "start_time": "2025-11-26T10:12:07.464747Z" |
| 84 | + "end_time": "2025-12-03T14:04:50.203648Z", |
| 85 | + "start_time": "2025-12-03T14:04:50.079589Z" |
85 | 86 | } |
86 | 87 | }, |
| 88 | + "outputs": [], |
87 | 89 | "source": [ |
88 | 90 | "budget = pd.Series([8, 9, 10, 11, 12], index=time)\n", |
89 | 91 | "\n", |
90 | 92 | "m.add_constraints(x + y <= budget, name=\"budget\")" |
91 | | - ], |
92 | | - "outputs": [], |
93 | | - "execution_count": null |
| 93 | + ] |
94 | 94 | }, |
95 | 95 | { |
96 | 96 | "cell_type": "markdown", |
|
102 | 102 | }, |
103 | 103 | { |
104 | 104 | "cell_type": "code", |
| 105 | + "execution_count": null, |
105 | 106 | "id": "quad-constraint", |
106 | 107 | "metadata": { |
107 | 108 | "ExecuteTime": { |
108 | | - "end_time": "2025-11-26T10:12:07.540835Z", |
109 | | - "start_time": "2025-11-26T10:12:07.506741Z" |
| 109 | + "end_time": "2025-12-03T14:04:50.639153Z", |
| 110 | + "start_time": "2025-12-03T14:04:50.544579Z" |
110 | 111 | } |
111 | 112 | }, |
| 113 | + "outputs": [], |
112 | 114 | "source": [ |
113 | 115 | "risk_limit = pd.Series([20, 25, 30, 35, 40], index=time)\n", |
114 | 116 | "\n", |
115 | 117 | "m.add_quadratic_constraints(x**2 + y**2, \"<=\", risk_limit, name=\"risk\")" |
116 | | - ], |
117 | | - "outputs": [], |
118 | | - "execution_count": null |
| 118 | + ] |
119 | 119 | }, |
120 | 120 | { |
121 | 121 | "cell_type": "markdown", |
|
129 | 129 | }, |
130 | 130 | { |
131 | 131 | "cell_type": "code", |
| 132 | + "execution_count": null, |
132 | 133 | "id": "objective", |
133 | 134 | "metadata": { |
134 | 135 | "ExecuteTime": { |
135 | | - "end_time": "2025-11-26T10:12:07.554745Z", |
136 | | - "start_time": "2025-11-26T10:12:07.545752Z" |
| 136 | + "end_time": "2025-12-03T14:04:51.366506Z", |
| 137 | + "start_time": "2025-12-03T14:04:51.276869Z" |
137 | 138 | } |
138 | 139 | }, |
| 140 | + "outputs": [], |
139 | 141 | "source": [ |
140 | 142 | "m.add_objective((x + 2 * y).sum(), sense=\"max\")\n", |
141 | 143 | "m" |
142 | | - ], |
143 | | - "outputs": [], |
144 | | - "execution_count": null |
| 144 | + ] |
145 | 145 | }, |
146 | 146 | { |
147 | 147 | "cell_type": "markdown", |
|
153 | 153 | }, |
154 | 154 | { |
155 | 155 | "cell_type": "code", |
| 156 | + "execution_count": null, |
156 | 157 | "id": "solve", |
157 | 158 | "metadata": { |
158 | 159 | "ExecuteTime": { |
159 | | - "end_time": "2025-11-26T10:12:07.741963Z", |
160 | | - "start_time": "2025-11-26T10:12:07.568872Z" |
| 160 | + "end_time": "2025-12-03T14:04:52.311740Z", |
| 161 | + "start_time": "2025-12-03T14:04:51.994541Z" |
161 | 162 | } |
162 | 163 | }, |
| 164 | + "outputs": [], |
163 | 165 | "source": [ |
164 | 166 | "m.solve(solver_name=\"gurobi\", QCPDual=1)" |
165 | | - ], |
166 | | - "outputs": [], |
167 | | - "execution_count": null |
| 167 | + ] |
168 | 168 | }, |
169 | 169 | { |
170 | 170 | "cell_type": "markdown", |
|
176 | 176 | }, |
177 | 177 | { |
178 | 178 | "cell_type": "code", |
| 179 | + "execution_count": null, |
179 | 180 | "id": "results", |
180 | 181 | "metadata": { |
181 | 182 | "ExecuteTime": { |
182 | | - "end_time": "2025-11-26T10:12:07.833812Z", |
183 | | - "start_time": "2025-11-26T10:12:07.752355Z" |
| 183 | + "end_time": "2025-12-03T14:04:57.706433Z", |
| 184 | + "start_time": "2025-12-03T14:04:56.543039Z" |
184 | 185 | } |
185 | 186 | }, |
| 187 | + "outputs": [], |
186 | 188 | "source": [ |
187 | 189 | "m.solution.to_dataframe().plot(kind=\"bar\", ylabel=\"Optimal Value\", rot=0);" |
188 | | - ], |
189 | | - "outputs": [], |
190 | | - "execution_count": null |
| 190 | + ] |
191 | 191 | }, |
192 | 192 | { |
193 | 193 | "cell_type": "markdown", |
|
201 | 201 | }, |
202 | 202 | { |
203 | 203 | "cell_type": "code", |
| 204 | + "execution_count": null, |
204 | 205 | "id": "qc-container", |
205 | 206 | "metadata": { |
206 | 207 | "ExecuteTime": { |
207 | | - "end_time": "2025-11-26T10:12:07.841509Z", |
208 | | - "start_time": "2025-11-26T10:12:07.837808Z" |
| 208 | + "end_time": "2025-12-03T14:04:58.261183Z", |
| 209 | + "start_time": "2025-12-03T14:04:58.251877Z" |
209 | 210 | } |
210 | 211 | }, |
| 212 | + "outputs": [], |
211 | 213 | "source": [ |
212 | 214 | "m.quadratic_constraints" |
213 | | - ], |
214 | | - "outputs": [], |
215 | | - "execution_count": null |
| 215 | + ] |
216 | 216 | }, |
217 | 217 | { |
218 | 218 | "cell_type": "code", |
| 219 | + "execution_count": null, |
219 | 220 | "id": "qc-single", |
220 | 221 | "metadata": { |
221 | 222 | "ExecuteTime": { |
222 | | - "end_time": "2025-11-26T10:12:07.854545Z", |
223 | | - "start_time": "2025-11-26T10:12:07.847875Z" |
| 223 | + "end_time": "2025-12-03T14:04:58.556810Z", |
| 224 | + "start_time": "2025-12-03T14:04:58.521816Z" |
224 | 225 | } |
225 | 226 | }, |
| 227 | + "outputs": [], |
226 | 228 | "source": [ |
227 | 229 | "m.quadratic_constraints[\"risk\"]" |
228 | | - ], |
229 | | - "outputs": [], |
230 | | - "execution_count": null |
| 230 | + ] |
231 | 231 | }, |
232 | 232 | { |
233 | 233 | "cell_type": "markdown", |
|
239 | 239 | }, |
240 | 240 | { |
241 | 241 | "cell_type": "code", |
| 242 | + "execution_count": null, |
242 | 243 | "id": "qc-duals", |
243 | 244 | "metadata": { |
244 | 245 | "ExecuteTime": { |
245 | | - "end_time": "2025-11-26T10:12:07.887976Z", |
246 | | - "start_time": "2025-11-26T10:12:07.876458Z" |
| 246 | + "end_time": "2025-12-03T14:04:58.728502Z", |
| 247 | + "start_time": "2025-12-03T14:04:58.714225Z" |
247 | 248 | } |
248 | 249 | }, |
| 250 | + "outputs": [], |
249 | 251 | "source": [ |
250 | 252 | "m.quadratic_constraints[\"risk\"].dual" |
251 | | - ], |
252 | | - "outputs": [], |
253 | | - "execution_count": null |
| 253 | + ] |
254 | 254 | }, |
255 | 255 | { |
256 | 256 | "cell_type": "markdown", |
|
264 | 264 | }, |
265 | 265 | { |
266 | 266 | "cell_type": "code", |
| 267 | + "execution_count": null, |
267 | 268 | "id": "bilinear", |
268 | 269 | "metadata": { |
269 | 270 | "ExecuteTime": { |
270 | | - "end_time": "2025-11-26T10:12:07.998397Z", |
271 | | - "start_time": "2025-11-26T10:12:07.922217Z" |
| 271 | + "end_time": "2025-12-03T14:04:59.651299Z", |
| 272 | + "start_time": "2025-12-03T14:04:59.297236Z" |
272 | 273 | } |
273 | 274 | }, |
| 275 | + "outputs": [], |
274 | 276 | "source": [ |
275 | 277 | "m2 = linopy.Model()\n", |
276 | 278 | "\n", |
|
287 | 289 | "m2.solve(solver_name=\"gurobi\")\n", |
288 | 290 | "\n", |
289 | 291 | "print(f\"x = {float(x.solution):.2f}, y = {float(y.solution):.2f}\")" |
290 | | - ], |
| 292 | + ] |
| 293 | + }, |
| 294 | + { |
| 295 | + "cell_type": "markdown", |
| 296 | + "id": "w5c0l9z5efi", |
| 297 | + "metadata": {}, |
| 298 | + "source": "## Mixed Linear and Quadratic Terms\n\nQuadratic constraints can combine both quadratic and linear terms in the same constraint. For example, $x^2 + 2x + y \\leq 10$:" |
| 299 | + }, |
| 300 | + { |
| 301 | + "cell_type": "code", |
| 302 | + "execution_count": null, |
| 303 | + "id": "gtgvi4i0mzl", |
| 304 | + "metadata": { |
| 305 | + "ExecuteTime": { |
| 306 | + "end_time": "2025-12-03T14:05:02.498083Z", |
| 307 | + "start_time": "2025-12-03T14:05:02.254357Z" |
| 308 | + } |
| 309 | + }, |
| 310 | + "outputs": [], |
| 311 | + "source": [ |
| 312 | + "m3 = linopy.Model()\n", |
| 313 | + "\n", |
| 314 | + "x = m3.add_variables(lower=0, name=\"x\")\n", |
| 315 | + "y = m3.add_variables(lower=0, name=\"y\")\n", |
| 316 | + "\n", |
| 317 | + "# Mixed constraint: x² + 2x + y <= 10\n", |
| 318 | + "m3.add_quadratic_constraints(x**2 + 2 * x + y, \"<=\", 10, name=\"mixed\")\n", |
| 319 | + "\n", |
| 320 | + "m3.add_objective(x + y, sense=\"max\")\n", |
| 321 | + "\n", |
| 322 | + "m3.solve(solver_name=\"gurobi\")\n", |
| 323 | + "\n", |
| 324 | + "print(f\"x = {float(x.solution):.2f}, y = {float(y.solution):.2f}\")" |
| 325 | + ] |
| 326 | + }, |
| 327 | + { |
| 328 | + "cell_type": "markdown", |
| 329 | + "id": "5gisroyuys4", |
| 330 | + "metadata": {}, |
| 331 | + "source": "## Equality Constraints\n\nQuadratic equality constraints use `\"==\"`. This example constrains a point to lie exactly on a circle:" |
| 332 | + }, |
| 333 | + { |
| 334 | + "cell_type": "code", |
| 335 | + "execution_count": null, |
| 336 | + "id": "x7tzlfma7fq", |
| 337 | + "metadata": { |
| 338 | + "ExecuteTime": { |
| 339 | + "end_time": "2025-12-03T14:05:05.330521Z", |
| 340 | + "start_time": "2025-12-03T14:05:05.202101Z" |
| 341 | + } |
| 342 | + }, |
291 | 343 | "outputs": [], |
292 | | - "execution_count": null |
| 344 | + "source": [ |
| 345 | + "m4 = linopy.Model()\n", |
| 346 | + "\n", |
| 347 | + "x = m4.add_variables(lower=-5, upper=5, name=\"x\")\n", |
| 348 | + "y = m4.add_variables(lower=-5, upper=5, name=\"y\")\n", |
| 349 | + "\n", |
| 350 | + "# Point must lie on circle of radius 2\n", |
| 351 | + "m4.add_quadratic_constraints(x**2 + y**2, \"==\", 4, name=\"circle\")\n", |
| 352 | + "\n", |
| 353 | + "# Maximize x + y (find point on circle furthest in direction (1,1))\n", |
| 354 | + "m4.add_objective(x + y, sense=\"max\")\n", |
| 355 | + "\n", |
| 356 | + "m4.solve(solver_name=\"gurobi\")\n", |
| 357 | + "\n", |
| 358 | + "print(f\"x = {float(x.solution):.4f}, y = {float(y.solution):.4f}\")\n", |
| 359 | + "print(f\"x² + y² = {float(x.solution) ** 2 + float(y.solution) ** 2:.4f}\")" |
| 360 | + ] |
| 361 | + }, |
| 362 | + { |
| 363 | + "cell_type": "markdown", |
| 364 | + "id": "ru3y88un3an", |
| 365 | + "metadata": {}, |
| 366 | + "source": "## Convexity Considerations\n\nQuadratic constraints have important convexity properties that affect which solvers can handle them:\n\n- **Convex** (most solvers): $x^T Q x + a^T x \\leq b$ where $Q$ is positive semidefinite (e.g., sum of squares like $x^2 + y^2$)\n- **Nonconvex** (requires specialized solvers like Gurobi):\n - $x^T Q x + a^T x \\geq b$ (greater-than with positive semidefinite Q)\n - $x^T Q x + a^T x = b$ (equality constraints)\n - Bilinear terms like $xy$\n\nConvex quadratic constraints define a convex feasible region (like the interior of an ellipse), while nonconvex constraints can create disconnected or non-convex regions. Solvers like Gurobi use spatial branch-and-bound to handle nonconvex cases." |
293 | 367 | } |
294 | 368 | ], |
295 | 369 | "metadata": { |
|
0 commit comments