title: "Lecture 6: Multiple Knapsacks and Logic" author: "Junaid Hasan" date: "July 2, 2021"¶

Multiple Knapsacks¶

  • Last Friday we talked about the Knapsack problem.
  • Let us consider an extension to the Knapsack Problem:
  • We start with a collection of items with varying weights and values.
  • However, now we can pack them in N equal knapsacks (say 5 knapsacks).
  • Again, we want to pack so that the total volume is maximum.

Contd..¶

  • Suppose we have 5 knapsacks(bins) each of maximum allowed weight 100.

  • The weights are weights = {48, 30, 42, 36, 36, 48, 42, 42, 36, 24, 30, 30, 42, 36, 36}

  • The values are values = {10, 30, 25, 50, 35, 30, 15, 40, 30, 35, 45, 10, 20, 30, 25}

  • How to pack?

In [3]:
weights = {1:48, 2:30, 3:42, 4:36, 5:36, 6:48, 7:42, 8:42, 9:36, 10:24, 11:30, 12:30, 13:42, 14:36, 15:36}
values = {1:10, 2:30, 3:25, 4:50, 5:35, 6:30, 7:15, 8:40, 9:30, 10:35, 11:45, 12:10, 13:20, 14:30, 15:25}
items = range(1, len(weights)+1)
bin_capacities = {1:100, 2:100, 3:100, 4:100, 5:100}
bins = range(1, len(bin_capacities)+1)
sum(weights)
Out[3]:
120

Model: Multiple Knapsack¶

  • Let us consider data[i,j] as a binary variable which is 1 when item i is packed in bin j.
  • Constraints:
  • Each item goes in exactly one bin for each i $$\sum_{j} data[i,j] \leq 1$$
  • Bin capacity for each bin j $$\sum_{i} data[i,j]\cdot weights[i] \leq 100$$
  • Maximize value $$\sum_{i} data[i,j]\cdot values[i]$$
In [4]:
from pyscipopt import Model, quicksum
In [5]:
model = Model("Multiple Knapsack")
data = {}
for i in items:
    for j in bins:
        data[(i,j)] = model.addVar(vtype="B", name="Object %s " %i + " in bin %s" %j)
In [6]:
for i in items:
    model.addCons(quicksum(data[(i,j)] for j in bins) <= 1, "item %s" %i +" is in exactly one bin")

for j in bins:
    model.addCons(quicksum(data[(i,j)]*weights[i] for i in items) <= bin_capacities[j], "bin %s" %j +" must not exceed capacity")
In [7]:
model.setObjective(quicksum(data[(i,j)]*values[i] for i in items for j in bins), "maximize")
model.optimize()
In [8]:
for j in bins:
    print("\nBin %s" %j+"\t total weight %s" %sum([model.getVal(data[(i,j)])*weights[i] for i in items])+  "\t total price %s" %sum([model.getVal(data[(i,j)])*values[i] for i in items]))
    for i in items:
        if model.getVal(data[(i,j)]) != 0:
            print("Object %s " %i + "\t of weight %s" %weights[i] + "\t and price %s" %values[i])
Bin 1	 total weight 90.0	 total price 50.0
Object 6 	 of weight 48	 and price 30
Object 13 	 of weight 42	 and price 20

Bin 2	 total weight 84.0	 total price 65.0
Object 3 	 of weight 42	 and price 25
Object 8 	 of weight 42	 and price 40

Bin 3	 total weight 96.0	 total price 120.0
Object 4 	 of weight 36	 and price 50
Object 5 	 of weight 36	 and price 35
Object 10 	 of weight 24	 and price 35

Bin 4	 total weight 96.0	 total price 105.0
Object 2 	 of weight 30	 and price 30
Object 10 	 of weight 24	 and price 35
Object 11 	 of weight 30	 and price 45
Object 14 	 of weight 36	 and price 30

Bin 5	 total weight 72.0	 total price 55.0
Object 9 	 of weight 36	 and price 30
Object 15 	 of weight 36	 and price 25

Bin Packing¶

  • Let us ask a new question.
  • Suppose we have an infinite number of knapsacks(bins) of a common size say 100.
  • However we want to pack all items with the fewest number of bins.
  • An application of this problem is when a logistics agent wants to pack items in boxes of a fixed size and wants to use the fewest number of boxes.
  • Note that the value of the items is irrelevant now.
In [25]:
weights = {1:90, 2:90, 3:10, 4:10, 5:10, 6:90, 7:20, 8:80, 9:30, 10:70, 11:40, 12:60, 13:50, 14:50, 15:100}
# weights = {1:48, 2:30, 3:42, 4:36, 5:36, 6:48, 7:42, 8:42, 9:36, 10:24, 11:30, 12:30, 13:42, 14:36, 15:28}
values = {1:10, 2:30, 3:25, 4:50, 5:35, 6:30, 7:15, 8:40, 9:30, 10:35, 11:45, 12:10, 13:20, 14:30, 15:25}
items = range(1, len(weights)+1)
bin_capacities = 100
bins = range(1, len(items)+1)
In [26]:
from pyscipopt import Model, quicksum
model = Model("Bin Packing")
In [27]:
x = {}
for i in items:
    for j in bins:
        x[(i,j)] = model.addVar(vtype="B", name="Object %s in bin %s" %(i,j))

y = {}
for j in bins:
    y[j] = model.addVar(vtype="B", name="Bin %s " %j + " is used or not")

Model¶

  • x[i,j] is a binary variable which records if object i is placed in bin j.
  • y[j] is a binary variable which records if bin j is used.
  • Contraints:
  • Each item is packed in exactly one bin, for all i $$\sum_{j} x[i,j] = 1$$
  • Bin capacity for each j $$\sum_{i} x[i,j]\cdot weights[i] \leq 100 \cdot y[j]$$
  • The multiplication by y[j] makes sure that the capcity is 0 if not used.
  • Minimize $$\sum_{j} y[j]$$.
In [28]:
for i in items:
    model.addCons(quicksum(x[(i,j)] for j in bins) == 1, "item %s" %i + "in only one bin")

for j in bins:
    model.addCons(quicksum(x[(i,j)]*weights[i] for i in items) <= bin_capacities*y[j], "bin %s capacity" %j)
In [29]:
model.setObjective(quicksum(y[j] for j in bins), "minimize")
In [30]:
model.optimize()
In [31]:
for j in bins:
    if model.getVal(y[j]) !=0:
        print("\nBin %s" %j+"\t total weight %s" %sum([model.getVal(x[(i,j)])*weights[i] for i in items]))
        for i in items:
            if model.getVal(x[(i,j)]) != 0:
                print("Object %s " %i + "\t of weight %s" %weights[i])

print("\nNumber of bins: %s" %round(model.getObjVal()))
Bin 1	 total weight 100.0
Object 1 	 of weight 90
Object 3 	 of weight 10

Bin 2	 total weight 100.0
Object 2 	 of weight 90
Object 4 	 of weight 10

Bin 3	 total weight 100.0
Object 5 	 of weight 10
Object 6 	 of weight 90

Bin 4	 total weight 100.0
Object 7 	 of weight 20
Object 8 	 of weight 80

Bin 5	 total weight 100.0
Object 9 	 of weight 30
Object 10 	 of weight 70

Bin 6	 total weight 100.0
Object 11 	 of weight 40
Object 12 	 of weight 60

Bin 7	 total weight 100.0
Object 13 	 of weight 50
Object 14 	 of weight 50

Bin 8	 total weight 100.0
Object 15 	 of weight 100

Number of bins: 8
In [24]:
model.getSolvingTime()
Out[24]:
0.513675

LPs and Logic¶

  • Let us change course and discuss how to put logical constraints in LPs.

  • Suppose we have two binary variables and we require that $x_1$ or $x_2$ is 1. How?

  • We can do this by $$x_1 + x_2 \geq 1.$$

  • If we want exclusive or, then?

  • $$ x_1 + x_2 = 1$$

More logical operations¶

  • If we want $x_1$ to imply $x_2$
  • If $x_1 =1$, then $x_2 = 1$, how to enforce this with linear constraints?
  • Answer: $$x_2 \geq x_1.$$

AND operator¶

  • If we want $b = 1$ if $x_1 = 1$ and $x_2 = 1$, and $b = 0$ otherwise. Then?
  • $$\begin{aligned}b &\geq x_1 + x_2 -1 \\ b &\leq x_1 \\ b &\leq x_2 \\ b &\in \{0,1 \}\end{aligned}$$
  • When both are 1, the first condition forces b to be 1 and
  • When either is 0, it forces b to be less than them making b equal to 0.

OR Operator¶

  • If we want $b = 1$ if $x_1 = 1$ or $x_2 = 1$, then
  • $$\begin{aligned}b &\leq x_1 + x_2\\ b &\geq x_1 \\ b &\geq x_2 \\ b &\in \{0,1\}\end{aligned}$$
  • When both are 0, the first statement makes b 0 as well and
  • When either is 1, then the greater than equal makes b equal to 1 as well.

XOR Operator¶

  • If we want $b = 1$ if $x_1 = 1$ xor $x_2 = 1$. This means that $b$ must be 0 when both are 1, and $b$ is 1 only when exactly on of $x_1, x_2$ is 1.
  • $$\begin{aligned}b &\leq x_1 +x_2 \\ b &\geq x_1 - x_2 \\ b &\geq x_2 - x_1 \\ b &\leq 2-x_1-x_2 \end{aligned}$$
  • The first condition ensures b is 0, when both are 0.
  • The last condition ensures b is 0 when both are 1.
  • The remaining two ensure b is 1, when exactly one of them is 1.

More complex contructions¶

  • If we want either $2x_1+x_2 \geq 5$ or $2x_3 -x_4 \leq 2$ or both.
  • Then how to do?
  • We add a binary variable $y$ and
  • a large value $M$. How large $M$ is depends on the largest possible value taken by $2x_3 - x_4$ and $2x_1 +x_2$.
  • We make $M$ larger than the largest these can take so that they are always less than $M$.
  • $$\begin{aligned}2x_1 + x_2 &\geq 5 - M \cdot y \\ 2x_3 -x_4 &\leq 2 + M\cdot(1-y) \end{aligned}$$

contd..¶

  • $$\begin{aligned}2x_1 + x_2 &\geq 5 - M \cdot y \\ 2x_3 -x_4 &\leq 2 + M\cdot(1-y) \end{aligned}$$
  • If $y = 0$ then this means we only check if $2x_1 + x_2 \geq 5$ because the second is always true.
  • If $y=1$, then we only check the second condition as the first is always true.

Indicator variable¶

  • Suppose $x$ is an integer variable (say $x \geq 0$).
  • We want a binary variable $y$ such that if $ x >0$ then $y = 1$, else $y = 0$.
  • Again we choose a postive $M$ such that $x < M$ always holds then
  • $$\begin{aligned}x \leq M \cdot y \\ y \leq M \cdot x\end{aligned}$$
  • Why?
In [ ]: