线性和分配(SciPy)和平衡成本
Linear sum assignment (SciPy) and balancing the costs
我在使用 scipy.optimize.linear_sum_assignment
将任务(成本)平均分配给工人时遇到困难,其中可以为每个工人分配多项任务。成本矩阵表示每个工作人员的每个任务的工作量。
我们希望最小化所有工人的总成本,同时平均分配每个工人的成本。
在这个例子中,我们有 3 个工人,分别是 a
、b
和 c
。每个工人总共可以分配 4 个任务,因此在成本矩阵中我们有代理 a_1
、a_2
等等。
linear_sum_assignment
确实给了我们总成本最小化的分配。简单来说,我们的示例使用成本矩阵,这样任何分配都会给我们相同的总成本。
然而,成本并未平均分配给 3 名工人。在我们的示例中,3 名工人的成本分别为 65
、163
和 192
。
是否可以尽可能降低成本,同时将每个工人的成本更平均地分配给 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
linear_sum_assignment
方法不支持约束或自定义 objective,所以我认为这是不可能的。
但是,您可以将问题表述为 mixed-integer 线性规划问题 (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="Continous")
min_val = pulp.LpVariable("min_val", cat="Continous")
# 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 传递给 MILP 求解器,如 CBC、SCIP、HiGHS 等
我在使用 scipy.optimize.linear_sum_assignment
将任务(成本)平均分配给工人时遇到困难,其中可以为每个工人分配多项任务。成本矩阵表示每个工作人员的每个任务的工作量。
我们希望最小化所有工人的总成本,同时平均分配每个工人的成本。
在这个例子中,我们有 3 个工人,分别是 a
、b
和 c
。每个工人总共可以分配 4 个任务,因此在成本矩阵中我们有代理 a_1
、a_2
等等。
linear_sum_assignment
确实给了我们总成本最小化的分配。简单来说,我们的示例使用成本矩阵,这样任何分配都会给我们相同的总成本。
然而,成本并未平均分配给 3 名工人。在我们的示例中,3 名工人的成本分别为 65
、163
和 192
。
是否可以尽可能降低成本,同时将每个工人的成本更平均地分配给 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
linear_sum_assignment
方法不支持约束或自定义 objective,所以我认为这是不可能的。
但是,您可以将问题表述为 mixed-integer 线性规划问题 (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="Continous")
min_val = pulp.LpVariable("min_val", cat="Continous")
# 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 传递给 MILP 求解器,如 CBC、SCIP、HiGHS 等