I am interested in the following problem. The input is a set of $n$ integers, and a fixed integer $k$. The required output is a partitioning of the integers into $k$ subsets, such that the smallest sum of a subset is as large as possible. This is a variant of the NP-hard multiway number partitioning problem.
I found several references for Python solutions of a related problem - bin packing:
But it is a different problem. Also, I am not sure what is the most efficient way to approach this problem - should I search for solutions for this specific problem? Or should I just present it as a general integer programming or use a generic solver?
EDIT: Following the answer by Rob Pratt, I installed PuLP and solved the problem using an integer linear program. In case anyone is interested, here is the code.
def mms_partition(values:list, k:int)->int:
parts = range(k)
items = range(len(values))
min_value = pulp.LpVariable("min_value", cat=pulp.LpContinuous)
vars = [
[pulp.LpVariable(f"x_{item}_{part}", lowBound=0, upBound=1, cat=pulp.LpInteger)
for part in parts]
for item in items
] # vars[i][j] is 1 iff item i is in part j.
mms_problem = pulp.LpProblem("MMS_problem", pulp.LpMaximize)
mms_problem += min_value # Objective function: maximize min_value
for item in items: # Constraints: each item must be in exactly one part.
mms_problem += (pulp.lpSum([vars[item][part] for part in parts]) == 1)
for part in parts: # Constraint: the sum of each part must be at least min_value (by definition of min_value).
mms_problem += (min_value <= pulp.lpSum([vars[item][part]*values[item] for item in items]))
pulp.PULP_CBC_CMD(msg=False).solve(mms_problem)
return min_value.value()
Usually it works quite fast, but in some cases it is very slow. Here are some run-times on random instances, all with $k=3$:
3 items, 1-of-3 MMS: 0.013010892027523369 seconds.
[1, 8, 14]
4 items, 1-of-3 MMS: 0.07400063099339604 seconds.
[47, 49, 54, 64]
5 items, 1-of-3 MMS: 0.07231759402202442 seconds.
[20, 64, 71, 72, 94]
6 items, 1-of-3 MMS: 0.08935485797701403 seconds.
[45, 55, 61, 70, 86, 93]
7 items, 1-of-3 MMS: 0.11710083403158933 seconds.
[13, 41, 42, 55, 56, 88, 90]
8 items, 1-of-3 MMS: 0.1803806659881957 seconds.
[9, 31, 45, 49, 54, 55, 62, 62]
9 items, 1-of-3 MMS: 0.1663428150350228 seconds.
[3, 6, 23, 35, 38, 38, 46, 77, 84]
10 items, 1-of-3 MMS: 0.28711252100765705 seconds.
[29, 34, 43, 46, 54, 56, 70, 81, 92, 95]
11 items, 1-of-3 MMS: 0.03978497500065714 seconds.
[3, 9, 11, 26, 37, 41, 49, 76, 82, 83, 96]
12 items, 1-of-3 MMS: 0.6636879530269653 seconds.
[3, 8, 9, 12, 38, 47, 53, 65, 71, 73, 85, 94]
13 items, 1-of-3 MMS: 0.08022642397554591 seconds.
[31, 31, 32, 39, 40, 46, 49, 50, 55, 59, 61, 62, 90]
14 items, 1-of-3 MMS: 0.13822774798609316 seconds.
[22, 29, 35, 50, 51, 62, 66, 74, 78, 80, 82, 87, 93, 97]
15 items, 1-of-3 MMS: 0.06699130398919806 seconds.
[5, 6, 8, 9, 12, 15, 15, 18, 19, 54, 56, 59, 73, 73, 88]
16 items, 1-of-3 MMS: 7.507785552996211 seconds.
[18, 29, 30, 30, 44, 47, 57, 60, 66, 77, 77, 78, 79, 82, 83, 93]
17 items, 1-of-3 MMS: 0.09432925801957026 seconds.
[2, 3, 9, 14, 15, 25, 29, 32, 36, 39, 42, 62, 65, 68, 79, 81, 92]
18 items, 1-of-3 MMS: 0.14621741196606308 seconds.
[6, 6, 8, 10, 16, 26, 26, 38, 42, 48, 51, 60, 72, 79, 89, 94, 95, 98]
19 items, 1-of-3 MMS: 7.723582196980715 seconds.
[23, 36, 41, 44, 54, 56, 66, 67, 72, 75, 79, 86, 87, 88, 92, 93, 95, 96, 98]
20 items, 1-of-3 MMS: 132.41131251398474 seconds.
[10, 10, 11, 13, 24, 26, 26, 28, 29, 35, 51, 62, 66, 69, 79, 80, 82, 87, 88, 94]
EDIT: Here is the log of CBC solving the 20-item problem:
Welcome to the CBC MILP Solver
Version: 2.9.0
Build Date: Feb 12 2015
command line - /usr/local/lib/python3.8/dist-packages/pulp/apis/../solverdir/cbc/linux/64/cbc /tmp/3de7ce0a9b7f41edbfad00526ae9bffd-pulp.mps max branch printingOptions all solution /tmp/3de7ce0a9b7f41edbfad00526ae9bffd-pulp.sol (default strategy 1)
At line 2 NAME MODEL
At line 3 ROWS
At line 28 COLUMNS
At line 273 RHS
At line 297 BOUNDS
At line 359 ENDATA
Problem MODEL has 23 rows, 61 columns and 123 elements
Coin0008I MODEL read with 0 errors
Continuous objective value is 323.333 - 0.00 seconds
Cgl0004I processed model has 23 rows, 61 columns (60 integer (60 of which binary)) and 123 elements
Cbc0038I Initial state - 4 integers unsatisfied sum - 0.632799
Cbc0038I Pass 1: suminf. 0.00000 (0) obj. -309 iterations 3
Cbc0038I Solution found of -309
Cbc0038I Relaxing continuous gives -309
Cbc0038I Before mini branch and bound, 56 integers at bound fixed and 0 continuous
Cbc0038I Full problem 23 rows 61 columns, reduced to 2 rows 2 columns
Cbc0038I Mini branch and bound did not improve solution (0.00 seconds)
Cbc0038I Round again with cutoff of -310.433
Cbc0038I Pass 2: suminf. 0.03050 (2) obj. -310.433 iterations 1
Cbc0038I Pass 3: suminf. 0.62908 (2) obj. -310.433 iterations 1
Cbc0038I Pass 4: suminf. 0.82473 (2) obj. -310.433 iterations 8
Cbc0038I Solution found of -310.433
Cbc0038I Relaxing continuous gives -312
Cbc0038I Before mini branch and bound, 40 integers at bound fixed and 0 continuous
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 11 columns
Cbc0038I Mini branch and bound improved solution from -312 to -322 (0.00 seconds)
Cbc0038I Round again with cutoff of -322.267
Cbc0038I Pass 5: suminf. 0.30922 (3) obj. -322.267 iterations 1
Cbc0038I Pass 6: suminf. 0.37730 (3) obj. -322.267 iterations 1
Cbc0038I Pass 7: suminf. 0.63034 (4) obj. -322.267 iterations 10
Cbc0038I Pass 8: suminf. 0.09867 (4) obj. -322.267 iterations 4
Cbc0038I Pass 9: suminf. 0.68337 (4) obj. -322.267 iterations 7
Cbc0038I Pass 10: suminf. 0.30922 (3) obj. -322.267 iterations 4
Cbc0038I Pass 11: suminf. 0.37730 (3) obj. -322.267 iterations 5
Cbc0038I Pass 12: suminf. 1.21920 (4) obj. -322.267 iterations 7
Cbc0038I Pass 13: suminf. 0.25604 (4) obj. -322.267 iterations 6
Cbc0038I Pass 14: suminf. 0.77137 (4) obj. -322.267 iterations 7
Cbc0038I Pass 15: suminf. 0.35177 (3) obj. -322.267 iterations 6
Cbc0038I Pass 16: suminf. 0.41986 (3) obj. -322.267 iterations 5
Cbc0038I Pass 17: suminf. 0.83404 (4) obj. -322.267 iterations 16
Cbc0038I Pass 18: suminf. 0.54975 (4) obj. -322.267 iterations 6
Cbc0038I Pass 19: suminf. 1.27959 (4) obj. -322.267 iterations 9
Cbc0038I Pass 20: suminf. 0.56454 (3) obj. -322.267 iterations 5
Cbc0038I Pass 21: suminf. 0.63262 (3) obj. -322.267 iterations 8
Cbc0038I Pass 22: suminf. 1.31931 (4) obj. -322.267 iterations 15
Cbc0038I Pass 23: suminf. 1.00000 (3) obj. -322.267 iterations 8
Cbc0038I Pass 24: suminf. 1.00000 (3) obj. -322.267 iterations 9
Cbc0038I Pass 25: suminf. 1.00000 (3) obj. -322.267 iterations 6
Cbc0038I Pass 26: suminf. 1.37854 (4) obj. -322.267 iterations 12
Cbc0038I Pass 27: suminf. 0.56454 (3) obj. -322.267 iterations 8
Cbc0038I Pass 28: suminf. 0.63262 (3) obj. -322.267 iterations 10
Cbc0038I Pass 29: suminf. 1.02096 (4) obj. -322.267 iterations 12
Cbc0038I Pass 30: suminf. 0.98971 (4) obj. -322.267 iterations 6
Cbc0038I Pass 31: suminf. 1.42328 (4) obj. -322.267 iterations 13
Cbc0038I Pass 32: suminf. 1.42328 (4) obj. -322.267 iterations 5
Cbc0038I Pass 33: suminf. 1.01418 (4) obj. -322.267 iterations 11
Cbc0038I Pass 34: suminf. 1.01418 (4) obj. -322.267 iterations 3
Cbc0038I No solution found this major pass
Cbc0038I Before mini branch and bound, 15 integers at bound fixed and 0 continuous
Cbc0038I Full problem 23 rows 61 columns, reduced to 14 rows 40 columns
Cbc0038I Mini branch and bound did not improve solution (0.03 seconds)
Cbc0038I After 0.03 seconds - Feasibility pump exiting with objective of -322 - took 0.03 seconds
Cbc0012I Integer solution of -322 found by feasibility pump after 0 iterations and 0 nodes (0.03 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 10 columns
Cbc0031I 3 added rows had average density of 61
Cbc0013I At root node, 3 cuts changed objective from -323.33333 to -323.33333 in 100 passes
Cbc0014I Cut generator 0 (Probing) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.007 seconds - new frequency is -100
Cbc0014I Cut generator 1 (Gomory) - 352 row cuts average 60.9 elements, 0 column cuts (0 active) in 0.008 seconds - new frequency is -100
Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.003 seconds - new frequency is -100
Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.001 seconds - new frequency is -100
Cbc0014I Cut generator 4 (MixedIntegerRounding2) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.005 seconds - new frequency is -100
Cbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.001 seconds - new frequency is -100
Cbc0014I Cut generator 6 (TwoMirCuts) - 28 row cuts average 47.3 elements, 0 column cuts (0 active) in 0.003 seconds - new frequency is -100
Cbc0010I After 0 nodes, 1 on tree, -322 best solution, best possible -323.33333 (0.09 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 15 columns
Cbc0004I Integer solution of -323 found after 961 iterations and 81 nodes (0.13 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 13 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 13 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 5 rows 16 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 14 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 18 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 5 rows 21 columns
Cbc0010I After 1000 nodes, 8 on tree, -323 best solution, best possible -323.33333 (2.30 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 19 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 16 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 17 columns
Cbc0010I After 2000 nodes, 5 on tree, -323 best solution, best possible -323.33333 (4.62 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 13 columns
Cbc0038I Full problem 23 rows 61 columns, reduced to 5 rows 22 columns
Cbc0010I After 3000 nodes, 5 on tree, -323 best solution, best possible -323.33333 (7.92 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 3 rows 19 columns
Cbc0010I After 4000 nodes, 5 on tree, -323 best solution, best possible -323.33333 (9.92 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 17 columns
Cbc0010I After 5000 nodes, 6 on tree, -323 best solution, best possible -323.33333 (12.14 seconds)
Cbc0038I Full problem 23 rows 61 columns, reduced to 4 rows 17 columns
Cbc0001I Search completed - best objective -323, took 2028836 iterations and 1170256 nodes (13.24 seconds)
Cbc0032I Strong branching done 10846 times (21617 iterations), fathomed 206 nodes and fixed 620 variables
Cbc0041I Maximum depth 20, 257 variables fixed on reduced cost (complete fathoming 2647 times, 1164430 nodes taking 2019718 iterations)
Cuts at root node changed objective from -323.333 to -323.333
Probing was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.007 seconds)
Gomory was tried 100 times and created 352 cuts of which 0 were active after adding rounds of cuts (0.008 seconds)
Knapsack was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.003 seconds)
Clique was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.001 seconds)
MixedIntegerRounding2 was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.005 seconds)
FlowCover was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.001 seconds)
TwoMirCuts was tried 100 times and created 28 cuts of which 0 were active after adding rounds of cuts (0.003 seconds)
Result - Optimal solution found
Objective value: 323.00000000
Enumerated nodes: 1170256
Total iterations: 2028836
Time (CPU seconds): 13.24
Time (Wallclock seconds): 13.54
Option for printingOptions changed from normal to all
Total time (CPU seconds): 13.24 (Wallclock seconds): 13.54
EDIT: I re-implemented with XPRESS solver. Now I can solve instances with over 100 items in less than a second. I do not know what was the problem with CBC.