Skip to content

Commit a22c9f6

Browse files
committed
Improve the quadratic-constraint.ipynb
1 parent 0af591f commit a22c9f6

1 file changed

Lines changed: 128 additions & 54 deletions

File tree

examples/quadratic-constraints.ipynb

Lines changed: 128 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@
1616
},
1717
{
1818
"cell_type": "code",
19+
"execution_count": null,
1920
"id": "imports",
2021
"metadata": {
2122
"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"
2425
}
2526
},
27+
"outputs": [],
2628
"source": [
2729
"import pandas as pd\n",
2830
"\n",
2931
"import linopy"
30-
],
31-
"outputs": [],
32-
"execution_count": null
32+
]
3333
},
3434
{
3535
"cell_type": "markdown",
@@ -47,23 +47,23 @@
4747
},
4848
{
4949
"cell_type": "code",
50+
"execution_count": null,
5051
"id": "model-setup",
5152
"metadata": {
5253
"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"
5556
}
5657
},
58+
"outputs": [],
5759
"source": [
5860
"m = linopy.Model()\n",
5961
"\n",
6062
"time = pd.Index(range(5), name=\"time\")\n",
6163
"\n",
6264
"x = m.add_variables(lower=0, coords=[time], name=\"x\")\n",
6365
"y = m.add_variables(lower=0, coords=[time], name=\"y\")"
64-
],
65-
"outputs": [],
66-
"execution_count": null
66+
]
6767
},
6868
{
6969
"cell_type": "markdown",
@@ -77,20 +77,20 @@
7777
},
7878
{
7979
"cell_type": "code",
80+
"execution_count": null,
8081
"id": "linear-constraint",
8182
"metadata": {
8283
"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"
8586
}
8687
},
88+
"outputs": [],
8789
"source": [
8890
"budget = pd.Series([8, 9, 10, 11, 12], index=time)\n",
8991
"\n",
9092
"m.add_constraints(x + y <= budget, name=\"budget\")"
91-
],
92-
"outputs": [],
93-
"execution_count": null
93+
]
9494
},
9595
{
9696
"cell_type": "markdown",
@@ -102,20 +102,20 @@
102102
},
103103
{
104104
"cell_type": "code",
105+
"execution_count": null,
105106
"id": "quad-constraint",
106107
"metadata": {
107108
"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"
110111
}
111112
},
113+
"outputs": [],
112114
"source": [
113115
"risk_limit = pd.Series([20, 25, 30, 35, 40], index=time)\n",
114116
"\n",
115117
"m.add_quadratic_constraints(x**2 + y**2, \"<=\", risk_limit, name=\"risk\")"
116-
],
117-
"outputs": [],
118-
"execution_count": null
118+
]
119119
},
120120
{
121121
"cell_type": "markdown",
@@ -129,19 +129,19 @@
129129
},
130130
{
131131
"cell_type": "code",
132+
"execution_count": null,
132133
"id": "objective",
133134
"metadata": {
134135
"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"
137138
}
138139
},
140+
"outputs": [],
139141
"source": [
140142
"m.add_objective((x + 2 * y).sum(), sense=\"max\")\n",
141143
"m"
142-
],
143-
"outputs": [],
144-
"execution_count": null
144+
]
145145
},
146146
{
147147
"cell_type": "markdown",
@@ -153,18 +153,18 @@
153153
},
154154
{
155155
"cell_type": "code",
156+
"execution_count": null,
156157
"id": "solve",
157158
"metadata": {
158159
"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"
161162
}
162163
},
164+
"outputs": [],
163165
"source": [
164166
"m.solve(solver_name=\"gurobi\", QCPDual=1)"
165-
],
166-
"outputs": [],
167-
"execution_count": null
167+
]
168168
},
169169
{
170170
"cell_type": "markdown",
@@ -176,18 +176,18 @@
176176
},
177177
{
178178
"cell_type": "code",
179+
"execution_count": null,
179180
"id": "results",
180181
"metadata": {
181182
"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"
184185
}
185186
},
187+
"outputs": [],
186188
"source": [
187189
"m.solution.to_dataframe().plot(kind=\"bar\", ylabel=\"Optimal Value\", rot=0);"
188-
],
189-
"outputs": [],
190-
"execution_count": null
190+
]
191191
},
192192
{
193193
"cell_type": "markdown",
@@ -201,33 +201,33 @@
201201
},
202202
{
203203
"cell_type": "code",
204+
"execution_count": null,
204205
"id": "qc-container",
205206
"metadata": {
206207
"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"
209210
}
210211
},
212+
"outputs": [],
211213
"source": [
212214
"m.quadratic_constraints"
213-
],
214-
"outputs": [],
215-
"execution_count": null
215+
]
216216
},
217217
{
218218
"cell_type": "code",
219+
"execution_count": null,
219220
"id": "qc-single",
220221
"metadata": {
221222
"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"
224225
}
225226
},
227+
"outputs": [],
226228
"source": [
227229
"m.quadratic_constraints[\"risk\"]"
228-
],
229-
"outputs": [],
230-
"execution_count": null
230+
]
231231
},
232232
{
233233
"cell_type": "markdown",
@@ -239,18 +239,18 @@
239239
},
240240
{
241241
"cell_type": "code",
242+
"execution_count": null,
242243
"id": "qc-duals",
243244
"metadata": {
244245
"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"
247248
}
248249
},
250+
"outputs": [],
249251
"source": [
250252
"m.quadratic_constraints[\"risk\"].dual"
251-
],
252-
"outputs": [],
253-
"execution_count": null
253+
]
254254
},
255255
{
256256
"cell_type": "markdown",
@@ -264,13 +264,15 @@
264264
},
265265
{
266266
"cell_type": "code",
267+
"execution_count": null,
267268
"id": "bilinear",
268269
"metadata": {
269270
"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"
272273
}
273274
},
275+
"outputs": [],
274276
"source": [
275277
"m2 = linopy.Model()\n",
276278
"\n",
@@ -287,9 +289,81 @@
287289
"m2.solve(solver_name=\"gurobi\")\n",
288290
"\n",
289291
"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+
},
291343
"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."
293367
}
294368
],
295369
"metadata": {

0 commit comments

Comments
 (0)