본문 바로가기
  • ̀⁽ᵕ̈⁾ ́
M.S./OR-Tools

[OR-Tools 스터디] Assignment with Allowed Groups

by j2j2y 2023. 2. 23.

13명의 노동자가 있으며 이 수는 0~11로 표현한다. 허용되는 그룹들은 다음 작업자 쌍의 조합이다.


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]]


허용된 그룹은 그룹1, 2, 3 각각으로부터 한 쌍씩 세 쌍의 조합일 수 있다.
예를들어, [2,3], [6,7], [10,11]의 조합은 [2,3,6,7,10,11]로부터 나온 허용된 그룹이다.
세개의 집합 각각이 총 5개의 element들을 포함하고 있기 때문에, 허용된 그룹의 전체 수는 5 * 5* 5 = 125이다.

노동자들의 그룹은 허용된 그룹들 중 하나에라도 속한다면 문제의 해결책이 될 수 있다.
다르게 말하면, 실현 가능한 집합은 constraint 중 하나가 만족되는 점으로 구성된다.
non-convex problem의 예이다.


대조적으로 이전에 기술되었던 MIP 예제는 convex problem이다 : 어떤 점이 실현가능하도록 하기 위해서는, 모든 constraint들이 반드시 만족되어야한다.

이와 같은 non-convex 문제의 경우, CP-SAT solver가 일반적으로 MIP solver보다 빠르다.
따르는 섹션은 CP-SAT solver와 MIP solver를 사용하여 문제에 대한 해결책을 보여주고, 두 solver들에 대한 해답 시간을 비교한다.

CP-SAT solution

 

# CP-SAT solution

from ortools.sat.python import cp_model

def CPSAT():
    #define the data
    costs = [
        [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(costs)
    num_tasks = len(costs[0])

    #create the allowed groups
    #CP-SAT solver에 대해 노동자들의 허용된 그룹을 정의하기 위해, 그룹에 속해있는 노동자들을 나타내기 위한 binary 배열을 만들어야 한다.
    #예로, group1(노동자 0-3)에 대해 binary vector [0, 0, 1, 1]을 노동자 2,3을 포함한 그룹으로 정의한다.

    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
    ]
    #CP-SAT는 반복문 내에서 그 벡터들의 모든 125개 조합들을 만들어낼 필요가 없다.
    #CP-SAT solver는 그에 대해 AllowedAssignments라는 메소드를 제공해주는데, 이는 우리가 노동자들의 세 집합(0-3, 4-7, 8-11) 각각에 대해 허용된 그룹으로 개별적으로 constraint들을 정의할 수 있도록 해준다.

    model = cp_model.CpModel()

    #create the variables
    x = {}
    for worker in range(num_workers):
        for task in range(num_tasks):
            x[worker, task] = model.NewBoolVar(f'x[{worker},{task}]')

    #add the constraints
    #each worker is assigned to at most one task (각 작업자는 최대 하나의 일을 부여받음)
    for worker in range(num_workers):
        model.AddAtMostOne(x[worker, task] for task in range(num_tasks))
    #each task is assigned to exactly one worker (각 일들은 한명의 작업자에게 부여됨)
    for task in range(num_tasks):
        model.AddExactlyOne(x[worker, task] for worker in range(num_workers))

    #create the objective
    objective_terms = []
    for worker in range(num_workers):
        for task in range(num_tasks):
            objective_terms.append(costs[worker][task] * x[worker, task])
    model.Minimize(sum(objective_terms))

    #invoke the solver
    solver = cp_model.CpSolver()
    status = solver.Solve(model)

    #display the results
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print(f'Total cost = {solver.ObjectiveValue()}\n')
        for worker in range(num_workers):
            for task in range(num_tasks):
                if solver.BooleanValue(x[worker, task]):
                    print(f'Worker {worker} assigned to task {task}.' +
                          f'Cost = {costs[worker][task]}')
    else:
        print('No solution found.')
CPSAT()

 

 

MIP solution

#MIP Solution
from ortools.linear_solver import pywraplp

def MIP():
    #define data
    costs = [
        [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(costs)
    num_tasks = len(costs[0])

    #create the allowed groups
    group1 = [  # Subgroups of workers 0 - 3
        [2, 3],
        [1, 3],
        [1, 2],
        [0, 1],
        [0, 2],
    ]

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

    group3 = [  # Subgroups of workers 8 - 11
        [10, 11],
        [9, 11],
        [9, 10],
        [8, 10],
        [8, 11],
    ]

    #declare the solver
    #create the mip solver with the SCIP backend
    solver = pywraplp.Solver.CreateSolver('SCIP')
    if not solver:
        return

    #create the variables
    # x[worker, task] is an array of 0-1 variables, which will be 1
    #if the worker is assigned to the task.
    x = {}
    for worker in range(num_workers):
        for task in range(num_tasks):
            x[worker, task] = solver.BoolVar(f'x[{worker}, {task}]')

    #add the constraints
    #total size of the tasks each worker takes on is at most total_size_max
    for worker in range(num_workers):
        solver.Add(
            solver.Sum([x[worker, task] for task in range(num_tasks)]) <= 1
        )

    #each task is assigned to exactly one worker.
    for task in range(num_tasks):
        solver.Add(
            solver.Sum([x[worker, task] for worker in range(num_workers)]) == 1
        )

    #create the objective
    objective_terms = []
    for worker in range(num_workers):
        for task in range(num_tasks):
            objective_terms.append(costs[worker][task] * x[worker, task])
    solver.Minimize(solver.Sum(objective_terms))

    #invoke the solver
    status = solver.Solve()

    if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
        print(f'Total cost = {solver.Objective().Value()}\n')
        for worker in range(num_workers):
            for task in range(num_tasks):
                if x[worker, task].solution_value() > 0.5:
                    print(f'Worker {worker} assigned to task {task}.' +
                          f'Cost : {costs[worker][task]}')
    else:
        print('No solution found.')

MIP()

 

 

결과적으로 CP-SAT이 MIP solver보다 훨씬 빠름

 

Reference:

https://developers.google.com/optimization/assignment/assignment_groups

 

허용된 그룹이 있는 할당  |  OR-Tools  |  Google Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 허용된 그룹이 있는 할당 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이

developers.google.com

 

'M.S. > OR-Tools' 카테고리의 다른 글

[OR-Tools 스터디] Packing  (0) 2023.02.23
[OR-Tools 스터디] Linear Sum Assignment Solver  (0) 2023.02.23
[OR-Tools 스터디] Assignment  (0) 2023.02.22
[OR-Tools] Cryptarithmetic Puzzles  (0) 2023.02.21
[OR-Tools] Solving a CP Problem  (0) 2023.02.15

댓글