-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathbnb_blank.txt
More file actions
551 lines (334 loc) · 19 KB
/
Copy pathbnb_blank.txt
File metadata and controls
551 lines (334 loc) · 19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# Branch & Bound and Knapsack Lab
**Objectives**
- Preform the branch and bound algorithm
- Apply branch and bound to the knapsack problem
- Understand the geometry of the branch and bound algorithm
**Brief description:** In this lab, we will try solving an example of a knapsack problem with the branch-and-bound algorithm. We will also see how adding a cutting plane helps in reducing the computation time and effort of the algorithm. Lastly, we will explore the geometry of the branch and bound algorithm.
```python
# imports -- don't forget to run this cell
import pandas as pd
import gilp
from gilp.visualize import feasible_integer_pts
from ortools.linear_solver import pywraplp as OR
```
## Part 1: Branch and Bound Algorithm
Recall that the branch and bound algorithm (in addition to the simplex method) allows us to solve integer programs. Before applying the branch and bound algorithm to the knapsack problem, we will begin by reviewing some core ideas. Furthermore, we will identify a helpful property that will make branch and bound terminate quicker later in the lab!
**Q1:** What are the different ways a node can be fathomed during the branch and bound algorithm? Describe each.
**A:**
**Q2:** Suppose you have a maximization integer program and you solve its linear program relaxation. What does the LP-relaxation optimal value tell you about the IP optimal value? What if it is a minimization problem?
**A:**
**Q3:** Assume you have a maximization integer program with all integral coefficents in the objective function. Now, suppose you are running the branch and bound algorithm and come across a node with an optimal value of 44.5. The current incumbent is 44. Can you fathom this node? Why or why not?
**A:**
**Q4:** If the optimal solution to the LP relaxation of the original program is integer, then you have found an optimal solution to your integer program. Explain why this is true.
**A:**
**Q5:** If the LP is infeasible, then the IP is infeasible. Explain why this is true.
**A:**
The next questions ask about the following branch and bound tree. If the solution was not integral, the fractional $x_i$ that was used to branch is given. If the solution was integral, it is denoted *INT*. In the current iteration of branch and bound, you are looking at the node with the *****.
<img src="images-lab/part1_bnb_tree.png" width="500"/>
**Q6:** Can you determine if the integer program this branch and bound tree is for is a minimization or maximixation problem? If so, which is it?
**A:**
Hint: For **Q7-8**, you can assume integral coefficents in the objective function.
**Q7:** Is the current node (marked $z^*$) fathomed? Why or why not? If not, what additional constraints should be imposed for each of the next two nodes?
**A:**
**Q8:** Consider the nodes under the current node (where $z = 16.3$). What do you know about the optimal value of these nodes? Why?
**A:**
## Part 2: The Knapsack Problem
In this lab, you will solve an integer program by branch and bound. The integer program to be solved will be a knapsack problem.
**Knapsack Problem:** We are given a collection of $n$ items, where each item $i = 1,\dots,n$ has a weight $w_i$ and a value $v_i$. In addition, there is a given capacity $W$, and the aim is to select a maximum value subset of items that has a total weight at most $W$. Note that each item can be brought at most once.
$$\begin{align*}
\max \quad & \sum_{i=1}^n v_ix_i\\
\text{s.t.} \quad & \sum_{i=1}^n w_ix_i \leq W \\
& 0 \leq x_i \leq 1, \text{integer}, i = 1,\dots,n
\end{align*}$$
Consider the following data which we import from a CSV file:
```python
data = pd.read_csv('data/knapsack_data_1.csv', index_col=0)
data
```
and $W = 18$.
**Q9:** Are there any items we can remove from our input to simplify this problem? Why? If so, replace `index` with the item number that can be removed in the code below. Hint: how many of each item could we possibly take?
**A:**
```python
# TODO: replace index
# data = data.drop(#index)
```
**Q10:** If we remove item 7 from the knapsack, it does not change the optimal solution to the integer program. Explain why.
**A:**
**Q11:** Consider removing items $i$ such that $w_i > W$ from a knapsack input. How does the LP relaxation's optimal value change?
**A:**
In **Q10-11**, you should have found that removing these items removes feasible solutions from the linear program but does not change the integer program. This is desirable as the gap between the optimal IP and LP values can become smaller. By adding this step, branch and bound may terminate sooner.
Recall that a branch and bound node can be fathomed if its bound is no better than the value of the best feasible integer solution found thus far. Hence, it helps to have a good feasible integer solution as quickly as possible (so that we stop needless work). To do this, we can first try to construct a good feasible integer solution by a reasonable heuristic algorithm before starting to run the branch and bound procedure.
In designing a heuristic for the knapsack problem, it is helpful to think about the value per unit weight for each item. We compute this value in the table below.
```python
data['value per unit weight'] = (data['value'] / data['weight']).round(2)
data
```
**Q12:** Design a reasonable heuristic for the knapsack problem. Note a heuristic aims to find a decent solution to the problem (but is not necessarily optimal).
**A:**
**Q13:** Run your heuristic on the data above to compute a good feasible integer solution. Your heuristic should generate a feasible solution with a value of 64 or better. If it does not, try a different heuristic (or talk to your TA!)
**A:**
We will now use the branch and bound algorithm to solve this knapsack problem! First, let us define a mathematical model for the linear relaxation of the knapsack problem.
**Q14:** Complete the model below.
```python
def Knapsack(table, capacity, integer = False):
"""Model for solving the Knapsack problem.
Args:
table (pd.DataFrame): A table indexd by items with a column for value and weight
capcity (int): An integer-capacity for the knapsack
integer (bool): True if the variables should be integer. False otherwise.
"""
ITEMS = list(table.index) # set of items
v = table.to_dict()['value'] # value for each item
w = table.to_dict()['weight'] # weight for each item
W = capacity # capacity of the knapsack
# define model
m = OR.Solver('knapsack', OR.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
# decision variables
x = {}
for i in ITEMS:
if integer:
x[i] = m.IntVar(0, 1, 'x_%d' % (i))
else:
x[i] = m.NumVar(0, 1, 'x_%d' % (i))
# define objective function here
m.Maximize(sum(v[i]*x[i] for i in ITEMS))
# TODO: Add a constraint that enforces that weight must not exceed capacity
# recall that we add constraints to the model using m.Add()
return (m, x) # return the model and the decision variables
```
```python
# You do not need to do anything with this cell but make sure you run it!
def solve(m):
"""Used to solve a model m."""
m.Solve()
print('Objective =', m.Objective().Value())
print('iterations :', m.iterations())
print('branch-and-bound nodes :',m.nodes())
return ({var.name() : var.solution_value() for var in m.variables()})
```
We can now create a linear relaxation of our knapsack problem. Now, `m` represents our model and `x` represents our decision variables.
```python
m, x = Knapsack(data, 18)
```
We can use the next line to solve the model and output the solution
```python
solve(m)
```
**Q15:** How does this optimal value compare to the value you found using the heuristic integer solution?
**A:**
**Q16:** Should this node be fathomed? If not, what variable should be branched on and what additional constraints should be imposed for each of the next two nodes?
**A:**
After constructing the linear relaxation model using `Knapsack(data1, 18)` we can add additional constraints. For example, we can add the constraint $x_2 \leq 0$ and solve it as follows:
```python
m, x = Knapsack(data, 18)
m.Add(x[2] <= 0)
solve(m)
```
**NOTE:** The line `m, x = Knapsack(data1, 18)` resets the model `m` to the LP relaxation. All constraints from branching have to be added each time.
**Q17:** Use the following cell to compute the optimal value for the other node you found in **Q16**.
```python
# TODO: Answer Q17
```
**Q18:** What was the optimal value? Can this node be fathomed? Why? (Hint: In **Q13**, you found a feasible integer solution with value 64.)
**A:**
If we continue running the branch and bound algorithm, we will eventually reach the branch and bound tree below where the $z^*$ indictes the current node we are looking at.
<img src="images-lab/part2_bnb_tree.png" width="700"/>
**Q19:** The node with $z = 64.857$ was fathomed. Why are we allowed to fathom this node? (Hint: think back to **Q3**)
**A:**
**Q20:** Finish running branch and bound to find the optimal integer solution. Use a separate cell for each node you solve and indicate if the node was fathomed with a comment. (Hint: Don't forget to include the constraints further up in the branch and bound tree.)
```python
# Template
m, x = Knapsack(data, 18)
# Add constraints here
solve(m)
# fathomed?
```
```python
```
```python
```
**A:**
**Q21:** How many nodes did you have to explore while running the branch and bound algorithm?
**A:**
In the next section, we will think about additional constraints we can add to make running branch and bound quicker.
## Part 3: Cutting Planes
In general, a cutting plane is an additional constraint we can add to an integer program's linear relaxation that removes feasible linear solutions but does not remove any integer feasible solutions. This is very useful when solving integer programs! Recall many of the problems we have learned in class have something we call the "integrality property". This is useful because it allows us to ignore the integrality constraint since we are garunteed to reach an integral solution. By cleverly adding cutting planes, we strive to remove feasible linear solutions (without removing any integer feasible solutions) such that the optimal solution to the linear relaxation is integral!
Conisder an integer program whose linear program releaxation is
$$\begin{align*}
\max \quad & 2x_1+x_2\\
\text{s.t.} \quad & x_1 + x_2 \leq 3 \\
& 2x_1 \leq 5 \\
& -x_1 + 2x_2 \leq 2 \\
& x_1, x_2 \geq 0
\end{align*}$$
We can define this linear program and then visualize its feasible region. The integer points have been highlighted.
```python
lp = gilp.LP([[1,1],[2,0],[-1,2]],
[3,5,2],
[2,1])
fig = gilp.lp_visual(lp)
fig.set_axis_limits([3.5,2])
fig.add_trace(feasible_integer_pts(lp, fig))
fig
```
**Q22:** List every feasible solution to the integer program.
**A:**
**Q23:** Is the constraint $x_2 \leq 1$ a cutting plane? Why? (Hint: Would any feasible integer points become infeasible? What about feasible linear points?)
**A:**
Let's add this cutting plane to the LP relaxation!
```python
lp = gilp.LP([[1,1],[2,0],[-1,2],[0,1]],
[3,5,2,1],
[2,1])
fig = gilp.lp_visual(lp)
fig.set_axis_limits([3.5,2])
fig.add_trace(feasible_integer_pts(lp, fig))
fig
```
**Q24:** Is the constraint $x_1 \leq 3$ a cutting plane? Why?
**A:**
**Q25:** Can you provide another cutting plane? If so, what is it?
**A:**
Let's look at the feasible region after adding the cutting plane from **Q23** and one of the possible answers from **Q25**. Notice the optimal solution to the LP relaxation is now integral!
```python
lp = gilp.LP([[1,1],[2,0],[-1,2],[0,1],[1,0]],
[3,5,2,1,2],
[2,1])
fig = gilp.lp_visual(lp)
fig.set_axis_limits([3.5,2])
fig.add_trace(feasible_integer_pts(lp, fig))
fig
```
Let's try applying what we know about cutting planes to the knapsack problem! Again, recall our input was $W = 18$ and:
```python
data
```
**Q26:** Look at items 1, 2, and 3. How many of these items can we take simultaneously? Can you write a new constraint to capture this? If so, please provide it.
**A:**
**Q27:** Is the constraint you found in **Q26** a cutting plane? If so, provide a feasible solution to the linear program relaxation that is no longer feasible (i.e. a point the constraint *cuts off*).
**A:**
**Q28:** Provide another cutting plane involving items 4,5 and 6 for this integer program. Explain how you derived it.
**A:**
**Q29:** Add the cutting planes from **Q26** and **Q28** to the model and solve it. You should get a solution in which we take items 1 and 4 and $\frac{1}{6}$ of item 5 with an objective value of 66.
```python
m, x = Knapsack(data, 18)
# TODO: Add cutting planes here
solve(m)
```
Let's take a moment to pause and reflect on what we are doing. Recall from **Q9-11** that we dropped item 7 becuase its weight was greater than the capcity of the knapsack. Essentially we added the constraint $x_7 \leq 0$. This constraint was a cutting plane! It eliminated some linear feasible solutions but no integer ones. By adding these two new cutting planes, we can get branch and bound to terminate earlier yet again! So far, we have generated cutting planes by inspection. However, there are more algorithmic ways to identify them (which we will ignore for now).
If we continue running the branch and bound algorithm, we will eventually reach the branch and bound tree below where the $z^*$ indictes the current node we are looking at.
<img src="images-lab/part3_bnb_tree.png" width="700"/>
**NOTE:** Do not forget about the feasible integer solution our heuristic gave us with value 64.
**Q30** Finish running branch and bound to find the optimal integer solution. Use a separate cell for each node you solve and indicate if the node was fathomed with a comment. Hint: Don't forget the cutting plane constraints should be included in every node of the branch and bound tree.
```python
# Template
m, x = Knapsack(data, 18)
# Add constraints here
solve(m)
# fathomed?
```
```python
```
```python
```
**A:**
**Q31:** Did you find the same optimal solution? How many nodes did you explore? How did this compare to the number you explored previously?
**A:**
## Part 4: Geometry of Branch and Bound
Previously, we used the `gilp` package to viusualize the simplex algorithm but it also has the functionality to visualize branch and bound. We will give a quick overview of the tool. Similar to `lp_visual` and `simplex_visual`, the function `bnb_visual` takes an `LP` and returns a visualization. It is assumed that every decision variable is constrained to be integer. Unlike previous visualizations, `bnb_visual` returns a series of figures for each node of the branch and bound tree. Let's look at a small 2D example:
$$\begin{align*}
\max \quad & 5x_1+ 8x_2\\
\text{s.t.} \quad & x_1 + x_2 \leq 6 \\
& 5x_1 + 9x_2 \leq 45 \\
& x_1, x_2 \geq 0, \quad \text{integral}
\end{align*}$$
```python
nodes = gilp.bnb_visual(gilp.examples.STANDARD_2D_IP)
```
```python
nodes[0].show()
```
Run the cells above to generate a figure for each node and view the first node. At first, you will see the LP relaxation on the left and the root of the branch and bound tree on the right. The simplex path and isoprofit slider are also present.
**Q32:** Recall the root of a branch and bound tree is the unaltered LP relaxation. What is the optimal solution? (Hint: Use the objective slider and hover over extreme points).
**A:**
**Q33:** Assume that we always choose the variable with the minimum index to branch on if there are multiple options. Write down (in full) each of the LPs we get after branching off the root node.
**A:**
**Q34:** Draw the feasible region to each of the LPs from **Q33** on the same picture.
**A:**
Run the following cell to see if the picture you drew in **Q34** was correct.
```python
nodes[1].show()
```
The outline of the original LP relaxation is still shown on the left. Now that we have eliminated some of the fractional feasible solutions, we now have 2 feasible regions to consider. The darker one is the feasible region associated with the current node which is also shaded darker in the branch and bound tree. The unexplored nodes in the branch and bound tree are not shaded in.
**Q35:** Which feasible solutions to the LP relaxation are removed by this branch?
**A:**
**Q36:** At the current (dark) node, what constraints will we add? How many feasible regions will the original LP relaxation be broken into?
**A:**
```python
nodes[2].show()
```
**Q37:** What is the optimal solution at the current (dark) node? Do we have to further explore this branch? Explain.
**A:**
**Q38:** Recall shaded nodes have been explored and the node shaded darker (and feasible region shaded darker) correspond to the current node and its feasible region. Nodes not shaded have not been explored. How many nodes have not yet been explored?
**A:**
**Q39:** How many nodes have a degree of one in the branch and bound tree? (That is, they are only connected to one edge). These nodes are called leaf nodes. What is the relationship between the leaf nodes and the remaining feasible region?
**A:**
```python
# Show the next two iterations of the branch and bound algorithm
nodes[3].show()
nodes[4].show()
```
**Q40:** At the current (dark) node, we added the constraint $x_1 \leq 1$. Why were the fractional solutions $1 < x_1 < 2$ not eliminated for $x_2 <= 3$?
**A:**
```python
# Show the next three iterations of the branch and bound algorithm
nodes[5].show()
nodes[6].show()
nodes[7].show()
```
**Q41:** What constraints are enforced at the current (dark) node? Why are there no feasible solutions at this node?
**A:**
```python
nodes[8].show()
```
**Q42:** Are we done? If so, what nodes are fathomed and what is the optimal solution? Explain.
**A:**
Let's look at branch and bound visualization for an integer program with 3 decision variables!
```python
nodes = gilp.bnb_visual(gilp.examples.VARIED_BRANCHING_3D_IP)
```
```python
# Look at the first 3 iterations
nodes[0].show()
nodes[1].show()
nodes[2].show()
```
Let's fast-forward to the final iteration of the branch and bound algorithm.
```python
nodes[-1].show()
```
**Q43:** Consider the feasible region that looks like a rectangular box with one corner point at the origin. What node does it correspond to in the tree? What is the optimal solution at that node?
**A:**
**Q44:** How many branch and bound nodes did we explore? What was the optimal solution? How many branch and bound nodes would we have explored if we knew the value of the optimal solution before starting branch and bound?
**A:**
## Bonus: Branch and Bound for Knapsack
Consider the following example:
| item | value | weight
|------|-------|-------
| 1 | 2 | 1
| 2 | 9 | 3
| 3 | 6 | 2
The linear program formulation will be:
$$\begin{align*}
\max \quad & 2x_1+9x_2+6x_3\\
\text{s.t.} \quad & 1x_1+3x_2+2x_3 \leq 10 \\
& x_1, x_2, x_3 \geq 0, \quad \text{integer}
\end{align*}$$
In gilp, we can define this lp as follows:
```python
lp = gilp.LP([[1,3,2]],
[10],
[2,9,6])
for fig in gilp.bnb_visual(lp):
fig.show()
```