jmyrberg/mknapsack

Extra Knapsack For Solve_Multiple_Knapsack

Theelx opened this issue · 2 comments

Theelx commented

Hello, and thanks so much for this amazing library! I spent many hours googling and looking through StackOverflow for efficient ways to solve the Multiple Knapsack problem with approximate solutions, and yours is by far the fastest and easiest to use.

I have some code where I'm trying to distribute work among three people (no more, no less). All work pieces must be distributed to someone. Each weight's value represents the amount of work one person must do (1000 represents ~1000 seconds of work, though the units are irrelevant), and the targets are the amount of work each person should do, with the same units as the weights. The issue is that solve_multiple_knapsack somehow seems to be making a fourth knapsack, which is weird because I'm only providing it three capacities. A Minimal Reproducible Example is as follows:

from mknapsack import solve_multiple_knapsack
import numpy as np

weights = np.array([1000, 3402, 4232, 3363, 8690, 8664, 5043, 8711, 2792, 7470, 1231, 3243, 5808, 2736, 5532, 855, 6760, 6861, 786, 2513, 1261, 6102, 2133, 1640])
profits = np.array([1 for _ in range(len(weights))])
capacities = np.array([50414, 25207, 25207])
res = solve_multiple_knapsack(profits, weights, capacities)
print(res)
print(np.sum(sizes[res == 0]), np.sum(sizes[res == 1]), np.sum(sizes[res == 2]), np.sum(sizes[res == 3]))
# Output:
# [1 2 1 3 3 1 2 0 2 2 1 1 3 1 1 1 1 3 1 1 1 2 1 1]
# 8711 42586 24809 24722

# Expected output:
# [1 2 1 3 3 1 2 1 2 2 1 1 3 1 1 1 1 3 1 1 1 2 1 1]
# 59217 24809 24722
# or
# Exception! Input values cannot be split without exceeding the capacity of a knapsack! Pass force=True to return an extra knapsack with the extra values.

I'm guessing this is because there's some assumption in the code that the result values are never allowed to go over the knapsacks' capacities. While this makes sense with the normal formulation of the problem, in my case one person can take slightly more work than the capacity as long as all the weights are assigned to someone.

Basically, I'm wondering if:

  1. I'm using the correct solver for my case. If possible, I'd appreciate a recommendation for a better solver if I'm using the wrong one.
  2. It makes sense for the result to implicitly contain an extra knapsack without warning the user or documenting the behavior in the README. It seems especially liable to issues if the user uses the results in a pipeline, where they don't see the results immediately, and where there may or may not be an extra knapsack. A better design may be to put the extra knapsack at the end position instead of position 0.

Thanks in advance!

Hi, and thanks for using the library!

There are only three knapsacks in the results, as 0 means that the item could not be assigned into a knapsack. This is also stated in the documentation of the function.

In your expected output, the capacity of knapsack 1 would be exceeded, as 59217 > 50414, which is not a feasible solution. If you need a "softer formulation" of the problem, you could e.g. first solve the stricter version, and then assign the leftover items into knapsacks greedily one-by-one while aiming to minimize exceeding the capacity.

Please let me know if this solved your problem or if you have any further questions.

Theelx commented

This is great, thank you so much!