Commit 280fc2e2 authored by Jirka Fink's avatar Jirka Fink
Browse files

Add 6th assignment on branch-and-bound

parent 8ad62126
import heapq
# Stores information about one item
class Item:
def __init__(self, index, weight, cost):
assert weight > 0 and cost > 0
self.index = index
self.weight = weight
self.cost = cost
self.density = cost / weight
# Stores data for one node in the branch-and_bound algorithm
class Branch:
# Use static methods init_from_instance and init_from_branch to create an instance
def __init__(self):
pass
# Create the initial node from a given instance
@staticmethod
def init_from_instance(instance):
self = Branch()
# All items that can be considered in this branch; items on which branch-and-bound decides to branch the computation are removed
self.items = [ Item(i, instance.weight[i], instance.cost[i]) for i in range(len(instance.weight)) ]
self.items = sorted(self.items, key=lambda item: item.density, reverse=True)
self.taken_items = [] # A list items already inserted inserted into the knapsack; i.e. items i for which branch-and-bound decided that x_i = 1
self.taken_cost = 0 # Cost of items in taken_items
self.capacity = instance.capacity # The remaining capacity; i.e. capacity minus weight of items in taken_items
self.update_optimal_integer_relaxed_solution()
return self
# Create a copy of a given branch
# Copy only data that does not need to be computed by optimal_integer_relaxed_solution()
@staticmethod
def init_from_branch(branch):
self = Branch()
self.items = branch.items.copy() # We really need to copy the list since the original branch may change the list later
self.taken_items = branch.taken_items.copy()
self.taken_cost = branch.taken_cost
self.capacity = branch.capacity
return self
# This function may be usefull to select new branch to be processed
def __lt__(self, other):
return self.upper_bound > other.upper_bound
# Solves the optimal integer-relaxed problem
# Since items has to be ordered by density, the optimal relaxed solution constains the first k items not exceeding the capacity and a part of the next item which cannot be included completely.
# Returns a tuple of
# * Total cost of the first k items (plus cost of earlier taken items)
# * The optimal value of the relaxed problem; i.e. the upper bound on the optimal integer solution
# * k
# * The portion of the k+1 -th item which needs to be taked to completely fill the knapsack
def optimal_integer_relaxed_solution(self):
assert all(self.items[i].density >= self.items[i+1].density for i in range(len(self.items)-1))
capacity = self.capacity
cost = self.taken_cost
for (index,item) in enumerate(self.items):
if capacity < item.weight:
part = capacity/item.weight
return (cost, cost+part*item.cost, index, part)
cost += item.cost
capacity -= item.weight
return (cost, cost, len(self.items), 0)
# Stores the return values of optimal_integer_relaxed_solution() into the branch
def update_optimal_integer_relaxed_solution(self):
self.best_known_cost, self.upper_bound, self.last_in_relaxation, self.relaxation_part = self.optimal_integer_relaxed_solution()
# Obtain indices of items of the optimal relaxed solution (excluding the partial item)
def get_solution_from_relaxation(self):
return [ item.index for item in self.taken_items + self.items[:self.last_in_relaxation] ]
# Perform the branching on the current node, update it and return a new node
def split_branch(self):
assert self.relaxation_part > 0
# TODO: Implement this function
# Essentially,
# * create a copy of the self branch,
# * the self.last_in_relaxation-th item has to be removed from both branches
# * the self.last_in_relaxation-th has to be added into the taken_items list of one of two branches
# * all other local variables has to be updated appropriatelly
# Find the optimal solution of a given instance of knapsack problem.
# Returns a list of indices of items in the optimal knapsack.
def solve_knapsack(instance):
branch = Branch.init_from_instance(instance)
best_known_cost = branch.best_known_cost
best_known_solution = branch.get_solution_from_relaxation()
queue = [ branch ]
#TODO: Implement this function
return best_known_solution
import sys
import random
import copy
from time import time
from prettytable import PrettyTable
from knapsack import solve_knapsack
class Instance:
def __init__(self, weight, cost, capacity):
self.items = len(weight) # The number of items
self.cost = cost # A list of costs of items
self.weight = weight # A list of weights of items
self.capacity = capacity # The capacity of a knapsack
# Solve the knapsack problem: Given weight and cost of items and capacity, find
# a subset of items with weights at the capacity and maximizing their total cost
# Returns the maximal cost of items fitting into the knapsack
def solve_knapsack(self):
max_cost = [-1] * (self.capacity+1) # Maximal possible cost reaching every total weight
max_cost[0] = 0
for w, p in zip(self.weight, self.cost): # Weight and cost of each item
for i in reversed(range(self.capacity-w+1)): # Try to insert the item j to all total weights
if max_cost[i+w] < max_cost[i] + p and max_cost[i] >= 0:
max_cost[i+w] = max_cost[i] + p # Adding item i increases the cost
return max(max_cost)
def verify_solution(self, solution):
if not all(0 <= i and i < self.items for i in solution):
return(False, "The solution has to be a list of indices of items.")
if len(set(solution)) < len(solution):
return(False, "Every item can be at most once inside the knapsack.")
weight = sum(self.weight[i] for i in solution)
if weight > self.capacity:
return(False, "The total weight of items is {} but the capacity is {}.".format(weight, self.capacity))
optimal_cost = self.solve_knapsack()
student_cost = sum(self.cost[i] for i in solution)
if student_cost < optimal_cost:
return (False, "Solution is not optimal")
elif student_cost > optimal_cost:
return (False, "Solution is better than the optimal one which should be impossible")
return (True, "Correct solution")
def run_tests(item_cnt, min_cost, max_cost, min_weight, max_weight, capacity, tests, seed):
rng = random.Random(seed)
for _ in range(tests):
cost = [ rng.randint(min_cost, max_cost) for _ in range(item_cnt) ]
weight = [ rng.randint(min_weight, max_weight) for _ in range(item_cnt) ]
instance = Instance(weight, cost, capacity)
solution = solve_knapsack(copy.deepcopy(instance))
status,msg = instance.verify_solution(solution)
if not status:
return (status, msg)
return (True, "Correct solutions")
def main():
tests = {
# (item_cnt, min_cost, max_cost, min_weight, max_weight, capacity, tests, seed, points)
"trivial": (10, 100, 200, 10, 50, 100, 1000, 2157, 2),
"small": (100, 100, 200, 10, 50, 200, 100, 6537, 2),
"many": (500, 100, 1000, 100, 1000, 10000, 3, 4326, 2),
"large": (200, 100, 1000, 500, 1000, 50000, 2, 1478, 2),
"heavy": (150, 1000000, 2000000, 10000, 20000, 50000, 1, 4579, 2),
}
if len(sys.argv) == 1:
results = PrettyTable(["Instance name", "Points", "Items", "Min weight", "Max weight", "Min cost", "Max cost", "Time limit [s]", "Your time [s]", "Evaluation"])
for name in tests:
print("Running test", name)
item_cnt, min_cost, max_cost, min_weight, max_weight, capacity, cnt, seed, points = tests[name]
start_time = time()
status, msg = run_tests(item_cnt, min_weight, max_weight, min_cost, max_cost, capacity, cnt, seed)
running_time = time() - start_time
print(msg)
print()
results.add_row([name, points, item_cnt, min_weight, max_weight, min_cost, max_cost, 10, running_time, msg])
print(results)
else:
name = sys.argv[1]
if name in tests:
item_cnt, min_cost, max_cost, min_weight, max_weight, capacity, tests, seed, points = tests[name]
status, msg = run_tests(item_cnt, min_cost, max_cost, min_weight, max_weight, capacity, tests, seed)
print(msg)
else:
print("Unknown test", name)
"""
To run all tests, run the command
$ python3 partition_tests.py
To run a test NAME, run the command
$ python3 partition_tests.py NAME
"""
import knapsack
if __name__ == "__main__":
main()
In Knapsack problem, we are given a set of items with weights and costs and capacity of a knapsack.
The problem is to find a subset of items with total weight not exceeding the capacity and having the maximal total cost.
This problem can be solved by many algorithm, e.g. pseudo-polynomial dynamic algorithm.
However, the purpose of this assignment is to implement branch-and-bound algorithm on a very simple problem.
Therefore, implement branch-and-bound algorithm which finds an optimal solution of Knapsack problem.
The template in out git already contains code solving the integer relaxed problem and a class to store data needed in branch-and-bound.
Your task is to implement the branching and bounding parts.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment