3

I have spent a week working on this branch and bound code for the knapsack problem, and I have looked at numerous articles and books on the subject. However, when I am running my code I don't get the result I expect. Input is received from a text file, such as this:

12
4 1
1 1
3 2
2 3

where the first line is the capacity, and each subsequent line are value/weight pairs. The result I get from using this file is '8' instead of '10' (unless I am mistaken and all the items won't fit in the knapsack). Here is my code:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import Queue
from collections import namedtuple
Item = namedtuple("Item", ['index', 'value', 'weight', 'level', 'bound', 'contains'])

class Node:
    def __init__(self, level, value, weight, bound, contains):
         self.level = level
         self.value = value
         self.weight = weight
         self.bound = bound
         self.contains = contains

def upper_bound(u, k, n, v, w):
    if u.weight > k:
        return 0

    else:
        bound = u.value
        wt = u.weight
        j = u.level + 1

        while j < n and wt + w[j] <= k:
            bound += v[j]
            wt += w[j]
            j += 1

    # fill knapsack with fraction of a remaining item
            if j < n:
                bound += (k - wt) * (v[j] / w[j])

            return bound

def knapsack(items, capacity):
    item_count = len(items)
    v = [0]*item_count
    w = [0]*item_count

# sort items by value to weight ratio
    items = sorted(items, key=lambda k: k.value/k.weight, reverse = True)

    for i,item in enumerate(items, 0):
        v[i] = int(item.value)
        w[i] = int(item.weight)

    q = Queue.Queue()

    root = Node(0, 0, 0, 0.0, [])
    root.bound = upper_bound(root, capacity, item_count, v, w)
    q.put(root)

    value = 0
    taken = [0]*item_count
    best = set()

    while not q.empty():
        c = q.get()

        if c.bound > value:
            level = c.level+1

    # check 'left' node (if item is added to knapsack)
        left = Node(c.value + v[level], c.weight + w[level], level, 0.0, c.contains[:])
        left.contains.append(level)

        if left.weight <= capacity and left.value > value:
            value = left.value
            best |= set(left.contains)

        left.bound = upper_bound(left, capacity, item_count, v, w)

        if left.bound > value:
            q.put(left)

        # check 'right' node (if items is not added to knapsack)
        right = Node(c.value, c.weight, level, 0.0, c.contains[:])
        right.contains.append(level)
        right.bound = upper_bound(right, capacity, item_count, v, w)

        if right.bound > value:
            q.put(right)

    for b in best:
        taken[b] = 1

    value = sum([i*j for (i,j) in zip(v,taken)])

    return str(value)

Are my indices off? Am I not traversing the tree or calculating the bounds correctly?

wikenator
  • 310
  • 1
  • 4
  • 12
  • You might want to look at http://rosettacode.org/wiki/Knapsack_Problem/Python – Dietrich Mar 11 '14 at 00:01
  • I am working toward applying the Knapsack algorithm to data sets containing 10,000+ items. I successfully implemented the DP Knapsack on smaller sets, but at a certain point memory becomes an issue, which is why I switched over to the branch and bound method. – wikenator Mar 11 '14 at 01:02
  • So I replaced `bound += (k - wt) * (v[j] / w[j])` with `bound += v[j]`; apparently the former was causing the algorithm to stop prematurely. I also had some backwards variables when I was initializing my left and right nodes (fixed now). Now I am stuck on marking which items were taken or not. On larger sets, I either get a pattern of `[0,1,3,...]` or `[0,2,4,...]`. Is the location where the 'contains' array is updated/modified in the correct place? – wikenator Mar 11 '14 at 19:29

3 Answers3

2
def upper_bound(u, k, n, v, w):
        if u.weight > k:
            return 0
        else:
            bound = u.value
            wt = u.weight
            j = u.level 
            while j < n and wt + w[j] <= k:
                bound += v[j]
                wt += w[j]
                j += 1
            # fill knapsack with fraction of a remaining item
            if j < n:
                bound += (k - wt) * float(v[j])/ w[j]
            return bound


def knapsack(items, capacity):
        item_count = len(items)
        v = [0]*item_count
        w = [0]*item_count
        # sort items by value to weight ratio
        items = sorted(items, key=lambda k: float(k.value)/k.weight, reverse = True)
        for i,item in enumerate(items, 0):
            v[i] = int(item.value)
            w[i] = int(item.weight)
        q = Queue.Queue()
        root = Node(0, 0, 0, 0.0,[])
        root.bound = upper_bound(root, capacity, item_count, v, w)
        q.put(root)
        value = 0
        taken = [0]*item_count
        best = set()
        while not q.empty():
            c = q.get()
            if c.bound > value:
                level = c.level+1
            # check 'left' node (if item is added to knapsack)
            left = Node(level,c.value + v[level-1], c.weight + w[level-1], 0.0, c.contains[:])
            left.bound = upper_bound(left, capacity, item_count, v, w)
            left.contains.append(level)
            if left.weight <= capacity:
                if left.value > value:
                    value = left.value
                    best = set(left.contains)
                if left.bound > value:
                    q.put(left)
                # check 'right' node (if items is not added to knapsack)   
            right = Node(level,c.value, c.weight, 0.0, c.contains[:])
            right.bound = upper_bound(right, capacity, item_count, v, w)
            if right.weight <= capacity:
                if right.value > value:
                    value = right.value
                    best = set(right.contains)
                if right.bound > value:
                    q.put(right)
        for b in best:
            taken[b-1] = 1
        value = sum([i*j for (i,j) in zip(v,taken)])
        return str(value)
pbellot
  • 95
  • 1
  • 7
0

I think you only want to calculate the bound if you're not taking the item. If you take the item, that means that your bound is still attainable. If you don't, you have to readjust your expectations.

  • 3
    P.S. glad you're taking Coursera's discrete optimization class :) –  Mar 11 '14 at 16:46
0
import functools
class solver():
    def __init__(self, Items, capacity):
        self.sortedItems = list(filter(lambda x: x.value > 0, Items))
        self.sortedItems = sorted(self.sortedItems, key=lambda    x:float(x.weight)/float(x.value))
    self.numItems = len(Items)
    self.capacity = capacity
    self.bestSolution = solution(0, self.capacity)

def isOptimisitcBetter(self, sol, newItemIdx):
    newItem = self.sortedItems[newItemIdx]
    rhs = (sol.value + (sol.capacity/newItem.weight)*newItem.value)
    return rhs > self.bestSolution.value

def explore(self, sol, itemIndex):
    if itemIndex < self.numItems:
        if self.isOptimisitcBetter(sol, itemIndex):
            self.exploreLeft(sol, itemIndex)
            self.exploreRight(sol, itemIndex)

def exploreLeft(self, sol, itemIndex):
    newItem = self.sortedItems[itemIndex]
    thisSol = sol.copy()
    if thisSol.addItem(newItem):
        if thisSol.value > self.bestSolution.value:
            self.bestSolution = thisSol
        self.explore(thisSol, itemIndex+1)

def exploreRight(self, sol, itemIndex):
    self.explore(sol, itemIndex+1)

def solveWrapper(self):
    self.explore(solution(0, self.capacity), 0)


class solution():
    def __init__(self, value, capacity, items=set()):
    self.value, self.capacity = value, capacity
    self.items = items.copy()

def copy(self):
    return solution(self.value,  self.capacity, self.items)

def addItem(self, newItem):
    remainingCap = self.capacity-newItem.weight
    if remainingCap < 0:
        return False
    self.items.add(newItem)
    self.capacity = remainingCap
    self.value+=newItem.value
    return True


solver = solver(items, capacity)
solver.solveWrapper()
bestSol = solver.bestSolution
Adel
  • 161
  • 6