運籌系列14:Assignment問題模型與python程式碼求解
阿新 • • 發佈:2018-11-24
1. 問題描述
分配問題可以簡單描述為:有數個人和數個任務,人做任務有不同的費用。每個人最多隻能做一項任務,每個任務只能由一個人做。如何將任務分配給人可以使總費用最小?
用數學語言表示為:
s.t.
來看一個例子,有4個人和4個任務,費用矩陣如下表:
人\任務 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 90 | 76 | 75 | 70 |
1 | 35 | 85 | 55 | 65 |
2 | 125 | 95 | 90 | 105 |
3 | 45 | 110 | 95 | 115 |
ortools有現成的模型LinearSumAssignment可以使用,下面是python程式碼:
from ortools.graph import pywrapgraph
cost = [[90, 76, 75, 70],[35, 85, 55, 65],[125, 95, 90, 105],[45, 110, 95, 115]]
rows = len(cost)
cols = len(cost[0])
assignment = pywrapgraph.LinearSumAssignment()
for worker in range(rows):
for task in range(cols):
if cost[worker][task]:
assignment.AddArcWithCost(worker, task, cost[worker][task])
solve_status = assignment.Solve()
if solve_status == assignment.OPTIMAL:
print('Total cost = ', assignment.OptimalCost())
print()
for i in range(0, assignment.NumNodes()):
print('Worker %d assigned to task %d. Cost = %d' % (i,assignment.RightMate(i),assignment.AssignmentCost(i)))
elif solve_status == assignment.INFEASIBLE:
print('No assignment is possible.')
elif solve_status == assignment.POSSIBLE_OVERFLOW:
print('Some input costs are too large and may cause an integer overflow.')
輸出結果為:
Total cost = 265
Worker 0 assigned to task 3. Cost = 70
Worker 1 assigned to task 2. Cost = 55
Worker 2 assigned to task 1. Cost = 95
Worker 3 assigned to task 0. Cost = 45
2. 問題變形1
假設我們有2個團隊(每個團隊中3個人)和4個任務,要求每個團隊分配2個任務使得總費用最小該怎麼建模?
這種情況下,我們可以將問題建模為最小費用流問題,如下圖:
最小費用流的求解方法見上一篇文章。
3. 問題變形2
有時候任務本身有一個size的屬性,比如每項工作需要一定的時間或費用,任務分配的時候不是限制每個人一個任務,而是要求task_size的和不能超過人能處理的size_max的限制。這裡可以使用前面文章提到的CP進行建模求解:
from __future__ import print_function
from ortools.sat.python import cp_model
import numpy as np
model = cp_model.CpModel()
start = time.time()
cost = [[90, 76, 75, 70, 50, 74, 12, 68],
[35, 85, 55, 65, 48, 101, 70, 83],
[125, 95, 90, 105, 59, 120, 36, 73],
[45, 110, 95, 115, 104, 83, 37, 71],
[60, 105, 80, 75, 59, 62, 93, 88],
[45, 65, 110, 95, 47, 31, 81, 34],
[38, 51, 107, 41, 69, 99, 115, 48],
[47, 85, 57, 71, 92, 77, 109, 36],
[39, 63, 97, 49, 118, 56, 92, 61],
[47, 101, 71, 60, 88, 109, 52, 90]]
sizes = [10, 7, 3, 12, 15, 4, 11, 5]
total_size_max = 15
num_workers = len(cost)
num_tasks = len(cost[1])
# Variables
x = []
for i in range(num_workers):
t = []
for j in range(num_tasks):
t.append(model.NewIntVar(0, 1, "x[%i,%i]" % (i, j)))
x.append(t)
x_array = [x[i][j] for i in range(num_workers) for j in range(num_tasks)]
# Constraints
# Each task is assigned to at least one worker.
[model.Add(sum(x[i][j] for i in range(num_workers)) >= 1)
for j in range(num_tasks)]
# Total size of tasks for each worker is at most total_size_max.
[model.Add(sum(sizes[j] * x[i][j] for j in range(num_tasks)) <= total_size_max)
for i in range(num_workers)]
model.Minimize(sum([np.dot(x_row, cost_row) for (x_row, cost_row) in zip(x, cost)]))
solver = cp_model.CpSolver()
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
print('Minimum cost = %i' % solver.ObjectiveValue())
print()
for i in range(num_workers):
for j in range(num_tasks):
if solver.Value(x[i][j]) == 1:
print('Worker ', i, ' assigned to task ', j, ' Cost = ', cost[i][j])
print()
end = time.time()
print("Time = ", round(end - start, 4), "seconds")
輸出為:
Minimum cost: 326
Worker 0 assigned to task 6 Cost = 12
Worker 1 assigned to task 0 Cost = 35
Worker 1 assigned to task 2 Cost = 55
Worker 2 assigned to task 4 Cost = 59
Worker 5 assigned to task 5 Cost = 31
Worker 5 assigned to task 7 Cost = 34
Worker 6 assigned to task 1 Cost = 51
Worker 8 assigned to task 3 Cost = 49
4. 使用MIP方法進行求解
一般來說,就求解速度來說LinearSumAssignment<minCostFlow<CP<MIP,而求解問題的範圍則是反過來的。這裡對上一節的問題使用傳統的MIP進行建模求解:
cost = [[90, 76, 75, 70, 50, 74, 12, 68],
[35, 85, 55, 65, 48, 101, 70, 83],
[125, 95, 90, 105, 59, 120, 36, 73],
[45, 110, 95, 115, 104, 83, 37, 71],
[60, 105, 80, 75, 59, 62, 93, 88],
[45, 65, 110, 95, 47, 31, 81, 34],
[38, 51, 107, 41, 69, 99, 115, 48],
[47, 85, 57, 71, 92, 77, 109, 36],
[39, 63, 97, 49, 118, 56, 92, 61],
[47, 101, 71, 60, 88, 109, 52, 90]]
task_sizes = [10, 7, 3, 12, 15, 4, 11, 5]
total_size_max = 15
from ortools.linear_solver import pywraplp
solver = pywraplp.Solver('SolveAssignmentProblem',pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
# Maximum total of task sizes for any worker
num_workers = len(cost)
num_tasks = len(cost[1])
# Variables
x = {}
for i in range(num_workers):
for j in range(num_tasks):
x[i, j] = solver.IntVar(0, 1, 'x[%i,%i]' % (i, j))
# Constraints
# The total size of the tasks each worker takes on is at most total_size_max.
for i in range(num_workers):
solver.Add(solver.Sum([task_sizes[j] * x[i, j] for j in range(num_tasks)]) <= total_size_max)
# Each task is assigned to at least one worker.
for j in range(num_tasks):
solver.Add(solver.Sum([x[i, j] for i in range(num_workers)]) >= 1)
solver.Minimize(solver.Sum([cost[i][j] * x[i,j] for i in range(num_workers) for j in range(num_tasks)]))
sol = solver.Solve()
print('Minimum cost = ', solver.Objective().Value())
print()
for i in range(num_workers):
for j in range(num_tasks):
if x[i, j].solution_value() > 0:
print('Worker', i,' assigned to task', j, ' Cost = ', cost[i][j])
結果為:
Minimum cost = 326.0
Worker 0 assigned to task 6 Cost = 12
Worker 1 assigned to task 0 Cost = 35
Worker 1 assigned to task 2 Cost = 55
Worker 4 assigned to task 4 Cost = 59
Worker 5 assigned to task 5 Cost = 31
Worker 5 assigned to task 7 Cost = 34
Worker 6 assigned to task 1 Cost = 51
Worker 8 assigned to task 3 Cost = 49