Google OR-Tools : 指定與分配 - 4. Assignment with Allowed Groups - MIP solver


任務0 任務1 任務2 任務3 任務4 任務5
0號工人 90 7675705074
1號工人3585556548101
2號工人125959010559120
3號工人451109511510483
4號工人6010580755962
5號工人4565110954731
6號工人3851107416999
7號工人478557719277
8號工人3963974911856
9號工人47101716088109
10號工人1739103646192
11號工人1014583599227

這次的分配問題,有以下條件
1) 將工人分成3組,每組取2人來分配任務
2) 每個任務只能分配給一個工人
3) 每個工人只能做一件任務
4) 花費最少

在該範例中,有12個工人,編號為0-11。
每4個工人分成一個group(1~3)
每個group裡任意2人組成一對
再從三個group裡各選一對
(ex, [2,3], [4,7], [8,10] -> [2,3,4,7,8,10] )
所以最後總共有5*5*5種可能
  group1 =  [[2, 3],       # Subgroups of workers 0 - 3
             
[1, 3],
             
[1, 2],
             
[0, 1],
             
[0, 2]]

  group2
=  [[6, 7],       # Subgroups of workers 4 - 7
             
[5, 7],
             
[5, 6],
             
[4, 5],
             
[4, 7]]

  group3
=  [[10, 11],     # Subgroups of workers 8 - 11
             
[9, 11],
             
[9, 10],
             
[8, 10],
             
[8, 11]]
這次選用CP-SAT來解

# Step 1: Import the linear solver, or MIP, or cp_model

from __future__ import print_function
from ortools.sat.python import cp_model
import time
import numpy as np

# Step 2: Add the solution printer

此步驟不需

# Step 3: Declare the linear solver, or MIP, or cp_model

  model = cp_model.CpModel()
  start
= time.time()

# Step 4: Create a database and requests

  cost = [[90, 76, 75, 70, 50, 74],
         
[35, 85, 55, 65, 48, 101],
         
[125, 95, 90, 105, 59, 120],
         
[45, 110, 95, 115, 104, 83],
         
[60, 105, 80, 75, 59, 62],
         
[45, 65, 110, 95, 47, 31],
         
[38, 51, 107, 41, 69, 99],
         
[47, 85, 57, 71, 92, 77],
         
[39, 63, 97, 49, 118, 56],
         
[47, 101, 71, 60, 88, 109],
         
[17, 39, 103, 64, 61, 92],
         
[101, 45, 83, 59, 92, 27]]
  num_workers
= len(cost)
  num_tasks
= len(cost[1])
  group1
=  [[0, 0, 1, 1],      # Workers 2, 3
             
[0, 1, 0, 1],      # Workers 1, 3
             
[0, 1, 1, 0],      # Workers 1, 2
             
[1, 1, 0, 0],      # Workers 0, 1
             
[1, 0, 1, 0]]      # Workers 0, 2

  group2
=  [[0, 0, 1, 1],      # Workers 6, 7
             
[0, 1, 0, 1],      # Workers 5, 7
             
[0, 1, 1, 0],      # Workers 5, 6
             
[1, 1, 0, 0],      # Workers 4, 5
             
[1, 0, 0, 1]]      # Workers 4, 7

  group3
=  [[0, 0, 1, 1],      # Workers 10, 11
             
[0, 1, 0, 1],      # Workers 9, 11
             
[0, 1, 1, 0],      # Workers 9, 10
             
[1, 0, 1, 0],      # Workers 8, 10
             
[1, 0, 0, 1]]      # Workers 8, 11
ex. 在group1的第一個list中 [0,0,1,1] 表示選擇worker2,3工作
     被選中工作的為1,沒被選到的為0

# Step 5-1: Create the 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)]
設一個list為x,裡面包含12個(num_workers) list t
讓每個工人針對每一項任務形成一個0 or 1 的變數(x[i, j])
0表示沒工作,1表示有工作
ex. 每個list t如下
[x[0,0](0..1), x[0,1](0..1), x[0,2](0..1), x[0,3](0..1), x[0,4](0..1), x[0,5](0..1), x[0,6](0..1), x[0,7](0..1)], 

[x[1,0](0..1), x[1,1](0..1), x[1,2](0..1), x[1,3](0..1), x[1,4](0..1), x[1,5](0..1)]...

# Step 6-2: Define the 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)]
也可以寫成下列:
    # Each task is assigned to exactly one worker.
   
for j in range(num_tasks):
        model
.Add(sum(x[i][j] for i in range(num_workers)) == 1)
x[0,0],x[1,0],x[2,0],x[3,0],x[4,0]只能有一個為1,其他都是0
x[0,1],x[1,1],x[2,1],x[3,1],x[4,1]只能有一個為1,其他都是0
之後都類似

  # Each worker is assigned to at most one task.
 
[model.Add(sum(x[i][j] for j in range(num_tasks)) <= 1)
 
for i in range(num_workers)]
x[0,0],x[0,1],x[0,2],x[0,3]最多只能有一個為1,其他都是0
x[1,0],x[1,1],x[1,2],x[1,3]最多只能有一個為1,其他都是0
之後都類似

# Step 5-2: Create the variables

這邊要創造另外一個變數 list work 用來顯示每個工人是否有做哪項任務
  # Create variables for each worker, indicating whether they work on some task.
  work
= []
 
for i in range(num_workers):
    work
.append(model.NewIntVar(0, 1, "work[%i]" % i))
變量work [i]是0-1變量,指示工作狀態或每個工人。
也就是說,如果將工作者i分配給任務,則work [i]等於1,否則為0。

# Step 6-2: Define the constraints

  for i in range(num_workers):
   
for j in range(num_tasks):
      model
.Add(work[i] == sum(x[i][j] for j in range(num_tasks)))
並且讓work[i]等於x[i][j] (兩者同時為0 or 1)
PS. 
work[i]用於讓之後的結果會在指定的group中
x[i][j]用於讓之後的結果會符合前面的約束

  # Define the allowed groups of worders
  model
.AddAllowedAssignments([work[0], work[1], work[2], work[3]], group1)
  model
.AddAllowedAssignments([work[4], work[5], work[6], work[7]], group2)
  model
.AddAllowedAssignments([work[8], work[9], work[10], work[11]], group3)
AddAllowedAssignments(variables, tuples_list)是對"變量數組(array of variable)"的約束,它要求在分配答案時,"結果數組(resulting array)"會是tuple_list中的其中一個list
(ex. 表示work [0-3]這四個變數的工作狀態必須符合group 1的其中一組如[0,1,1,0]表示,不可以變成像[0,1,1,1],一定要在group中選出剛好只有兩位工人工作)

# Step 7: Define the objective

  model.Minimize(sum([np.dot(x_row, cost_row) for (x_row, cost_row) in zip(x, cost)]))
將x, cost兩條一維list相乘
dot()在一維數組時,是把兩條list的每一個元素與另外一條list的元素相乘之後,再把相乘得到的數全部相加)

# Step 8: Create a solver and invoke solve

  solver = cp_model.CpSolver()
  status
= solver.Solve(model)

# Step 9: Call a printer and display the results 

  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 = 239

Worker  0  assigned to task  4   Cost =  50
Worker  1  assigned to task  2   Cost =  55
Worker  5  assigned to task  5   Cost =  31
Worker  6  assigned to task  3   Cost =  41
Worker  10  assigned to task  0   Cost =  17
Worker  11  assigned to task  1   Cost =  45

Time =  0.0113 seconds


留言

這個網誌中的熱門文章

Excel上如何建立3個Y軸的圖表

ABG動脈血基礎判讀 好簡單啊~~~

CVVH實用篇,FF和clearance快速計算與應用