【问题标题】:Linear sum assignment (SciPy) and balancing the costs线性总和分配(SciPy)和平衡成本
【发布时间】:2022-07-23 03:29:51
【问题描述】:

我在使用 scipy.optimize.linear_sum_assignment 将任务(成本)平均分配给工人时遇到了困难,其中每个工人都可以分配多个任务。成本矩阵表示每个工作人员的每个任务的工作量。

我们希望最小化所有工人的总成本,同时平均分配每个工人的成本。

在此示例中,我们有 3 个工作人员,分别名为 abc。每个工作人员总共可以分配 4 个任务,因此在成本矩阵中,我们有代理 a_1a_2 等等。

linear_sum_assignment 确实为我们提供了总成本最小化的任务。简单来说,我们的示例使用了一个成本矩阵,这样任何分配都会给我们相同的总成本。

但是,成本并没有平均分配给 3 名工人。在我们的示例中,3 个工人的成本分别为 65163192

是否有可能尽可能降低成本,同时将每个工人的成本更平均地分配给 3 名工人?

from scipy.optimize import linear_sum_assignment
import numpy as np

worker_capacities = [
    "a_1", "a_2", "a_3", "a_4",
    "b_1", "b_2", "b_3", "b_4",
    "c_1", "c_2", "c_3", "c_4",
]
n_tasks = len(worker_capacities)
c = np.array([
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
 [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
])

_, assignments = linear_sum_assignment(c)
print("Costs for worker a:", sum(c[i][j] for i, j in enumerate(assignments[0:4])))
print("Costs for worker b:", sum(c[i+4][j] for i, j in enumerate(assignments[4:8])))
print("Costs for worker c:", sum(c[i+8][j] for i, j in enumerate(assignments[8:12])))

给出输出

Costs for worker a: 65
Costs for worker b: 163
Costs for worker c: 192

【问题讨论】:

    标签: python numpy optimization scipy assignment-problem


    【解决方案1】:

    linear_sum_assignment 方法不支持约束或自定义目标,所以我认为这是不可能的。

    但是,您可以将问题表述为混合整数线性规划问题 (MILP),并通过 PuLP1 解决它。为了平均分配每个工人的总成本,您可以最小化每个工人的最大和最小总成本之间的差异。这是一个可能的公式:

    Sets:
    
    - workers = ["a", "b", "c"]
    - tasks   = [1, 2, ..., 12]
    
    Variables:
    
    - x[w,t] = 1 iff worker w is assigned to task t, 0 otherwise
    - min_val
    - max_val
    
    Model:
    
    min  max_val - min_val
    
    s.t. 
    
    # each worker is assigned to exactly n_tasks_per_worker tasks
    sum(x[w,t] for t in tasks) == n_tasks_per_worker for all w in workers
    
    # each task can only be assigned once
    sum(x[w,t] for w in workers) == 1 for all t in tasks
    
    # evenly distribute the tasks
    sum(x[w,t] for t in tasks) <= max_val for all w in workers
    sum(x[w,t] for t in tasks) >= min_val for all w in workers
    

    代码很简单:

    import pulp
    import numpy as np
    
    workers = ["a", "b", "c"]
    n_workers = len(workers)
    n_tasks_per_worker = 4
    n_tasks = n_workers * n_tasks_per_worker
    
    c = np.array([[27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
                  [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26],
                  [27, 42, 65, 33, 67, 45, 60, 76,  6,  6, 43, 26]])
    
    # create the model
    mdl = pulp.LpProblem("even_assignment")
    
    # decision variables
    x = {}
    for w in workers:
        for t in range(n_tasks):
            x[w, t] = pulp.LpVariable(f"x[{w}, {t}]", cat="Binary")
    
    max_val = pulp.LpVariable("max_val", cat="Continuous")
    min_val = pulp.LpVariable("min_val", cat="Continuous")
    
    # objective: minimize the difference between the maximum and the minimum
    #            costs per worker
    mdl.setObjective(max_val - min_val)
    
    # constraint: each worker gets assigned exactly n_tasks_per_worker
    for w in workers:
        mdl.addConstraint(sum(x[w, task] for task in range(n_tasks)) == n_tasks_per_worker)
    
    # constraint: each task can only be assigned once
    for task in range(n_tasks):
        mdl.addConstraint(sum(x[w, task] for w in workers) == 1)
    
    # constraint: evenly distribute the tasks
    for i_w, w in enumerate(workers):
        assignment_cost = sum(x[w,task]*c[i_w,task] for task in range(n_tasks))
        mdl.addConstraint(assignment_cost <= max_val)
        mdl.addConstraint(assignment_cost >= min_val)
    
    # solve the problem
    mdl.solve()
    
    # Output
    for i_w, w in enumerate(workers):
        worker_cost = sum(x[w, t].varValue*c[i_w, t] for t in range(n_tasks))
        print(f"costs for worker {w}: {worker_cost:.2f}")
    

    这给了我

    costs for worker a: 165.00
    costs for worker b: 167.00
    costs for worker c: 164.00
    

    [1] 确切地说,PuLP 不是求解器,它只是一个建模框架,可以将 MILP 传递给 CBC、SCIP、HiGHS 等 MILP 求解器。

    【讨论】:

    • @creanion 不错。谢谢!已编辑。
    猜你喜欢
    • 1970-01-01
    • 2014-03-23
    • 1970-01-01
    • 2018-07-18
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    相关资源
    最近更新 更多