如何解决将代理分配给具有固定开始时间和结束时间的任务的 CP/MILP 问题名称是什么? 关于3的小演示实施/可扩展性
我正在尝试解决将代理分配给任务的约束满足优化问题。但是,与基本分配问题不同的是,如果任务不重叠,可以将代理分配给许多任务。 每个任务都有固定的 start_time 和 end_time。 根据一些一元和二元约束将代理分配给任务。
变量 = 任务集
域 = 兼容代理集(对于每个变量)
约束 = 一元和二元
优化 fct = 一些线性函数
问题的一个例子:为我们知道到达和离开时间的卡车分配停车位(或车队)。
我很感兴趣,如果文献中有这些类型问题的确切名称。我认为这是某种分配问题。另外,如果您曾经遇到过这个问题,您将如何解决?
谢谢。
解决方法
我不知道您描述的特定变体的名称 - 也许其他人会知道。然而,这似乎非常适合 CP/MIP 求解器;我会选择 OR-Tools CP-SAT 求解器,它免费、灵活且通常运行良好。
这是一个使用 Python 的参考实现,假设每辆车都需要分配一个没有重叠的团队,并且目标是尽量减少使用的团队数量。 该框架允许直接建模允许/禁止的分配(查看 docs)
from ortools.sat.python import cp_model
model = cp_model.CpModel()
## Data
num_vehicles = 20
max_teams = 10
# Generate some (semi-)interesting data
interval_starts = [i % 9 for i in range(num_vehicles)]
interval_len = [ (num_vehicles - i) % 6 for i in range(num_vehicles)]
interval_ends = [ interval_starts[i] + interval_len[i] for i in range(num_vehicles)]
### variables
# t,v is true iff vehicle v is served by team t
team_assignments = {(t,v): model.NewBoolVar("team_assignments_%i_%i" % (t,v)) for t in range(max_teams) for v in range(num_vehicles)}
#intervals for vehicles. Each interval can be active or non active,according to team_assignments
vehicle_intervals = {(t,v): model.NewOptionalIntervalVar(interval_starts[v],interval_len[v],interval_ends[v],team_assignments[t,v],'vehicle_intervals_%i_%i' % (t,v))
for t in range(max_teams) for v in range(num_vehicles)}
team_in_use = [model.NewBoolVar('team_in_use_%i' % (t)) for t in range(max_teams)]
## constraints
# non overlap for each team
for t in range(max_teams):
model.AddNoOverlap([vehicle_intervals[t,v] for v in range(num_vehicles)])
# each vehicle must be served by exactly one team
for v in range(num_vehicles):
model.Add(sum(team_assignments[t,v] for t in range(max_teams)) == 1)
# what teams are in use?
for t in range(max_teams):
model.AddMaxEquality(team_in_use[t],[team_assignments[t,v] for v in range(num_vehicles)])
#symmetry breaking - use teams in-order
for t in range(max_teams-1):
model.AddImplication(team_in_use[t].Not(),team_in_use[t+1].Not())
# let's say that the goal is to minimize the number of teams required
model.Minimize(sum(team_in_use))
solver = cp_model.CpSolver()
# optional
# solver.parameters.log_search_progress = True
# solver.parameters.num_search_workers = 8
# solver.parameters.max_time_in_seconds = 5
result_status = solver.Solve(model)
if (result_status == cp_model.INFEASIBLE):
print('No feasible solution under constraints')
elif (result_status == cp_model.OPTIMAL):
print('Optimal result found,required teams=%i' % (solver.ObjectiveValue()))
elif (result_status == cp_model.FEASIBLE):
print('Feasible (non optimal) result found')
else:
print('No feasible solution found under constraints within time')
# Output:
#
# Optimal result found,required teams=7
编辑:
@sascha 提出了一种分析(预先知道的)时间窗口重叠的漂亮方法,这将使这个问题可以作为一个分配问题来解决。
因此,虽然上面的公式可能不是最佳方案(尽管可能是,这取决于求解器的工作方式),但我已尝试用建议的 max-clique 方法替换无重叠条件 - 完整代码如下。
我对中等规模的问题(100 辆和 300 辆汽车)做了一些实验,从经验来看,在较小的问题(~100 辆)上,这确实有所改善 - 获得最佳解决方案的时间平均约为 15%;但我找不到更大(~300)问题的显着改进。这可能是因为我的配方不是最佳的;因为 CP-SAT 求解器(也是一个很好的 IP 求解器)足够聪明;或者因为我错过了一些东西:)
代码:
(这与上面的代码基本相同,逻辑支持使用网络方法而不是从@sascha 的答案中复制的无重叠方法):
from timeit import default_timer as timer
from ortools.sat.python import cp_model
model = cp_model.CpModel()
run_start_time = timer()
## Data
num_vehicles = 300
max_teams = 300
USE_MAX_CLIQUES = True
# Generate some (semi-)interesting data
interval_starts = [i % 9 for i in range(num_vehicles)]
interval_len = [ (num_vehicles - i) % 6 for i in range(num_vehicles)]
interval_ends = [ interval_starts[i] + interval_len[i] for i in range(num_vehicles)]
if (USE_MAX_CLIQUES):
## Max-cliques analysis
# for the max-clique approach
time_windows = [(interval_starts[i],interval_ends[i]) for i in range(num_vehicles)]
def is_overlapping(a,b):
return (b[1] > a[0] and b[0] < a[1])
# raw conflicts
# -------------
binary_conflicts = []
for a,b in itertools.combinations(range(len(time_windows)),2):
if is_overlapping(time_windows[a],time_windows[b]):
binary_conflicts.append( (a,b) )
# conflict graph
# --------------
G = nx.Graph()
G.add_edges_from(binary_conflicts)
# maximal cliques
# ---------------
max_cliques = nx.chordal_graph_cliques(G)
##
### variables
# t,v is true iff point vehicle v is served by team t
team_assignments = {(t,v))
for t in range(max_teams) for v in range(num_vehicles)}
team_in_use = [model.NewBoolVar('team_in_use_%i' % (t)) for t in range(max_teams)]
## constraints
# non overlap for each team
if (USE_MAX_CLIQUES):
overlap_constraints = [list(l) for l in max_cliques]
for t in range(max_teams):
for l in overlap_constraints:
model.Add(sum(team_assignments[t,v] for v in l) <= 1)
else:
for t in range(max_teams):
model.AddNoOverlap([vehicle_intervals[t,v] for v in range(num_vehicles)])
# each vehicle must be served by exactly one team
for v in range(num_vehicles):
model.Add(sum(team_assignments[t,team_in_use[t+1].Not())
# let's say that the goal is to minimize the number of teams required
model.Minimize(sum(team_in_use))
solver = cp_model.CpSolver()
# optional
solver.parameters.log_search_progress = True
solver.parameters.num_search_workers = 8
solver.parameters.max_time_in_seconds = 120
result_status = solver.Solve(model)
if (result_status == cp_model.INFEASIBLE):
print('No feasible solution under constraints')
elif (result_status == cp_model.OPTIMAL):
print('Optimal result found,required teams=%i' % (solver.ObjectiveValue()))
elif (result_status == cp_model.FEASIBLE):
print('Feasible (non optimal) result found,required teams=%i' % (solver.ObjectiveValue()))
else:
print('No feasible solution found under constraints within time')
print('run time: %.2f sec ' % (timer() - run_start_time))
,
我会解释这为:矩形赋值-冲突问题 这可以说比多项式可解赋值问题困难得多(通常是NP-hard)。
另一个答案中显示的演示可能有效,而且 ortools 的 cp-sat 很棒,但是我认为没有充分理由在此处使用 基于离散时间的推理 strong> 就像已经完成的:间隔变量,边缘查找和合作。基于调度约束(+ 冲突分析/解释)。这些东西完全是矫枉过正,开销将是显而易见的。 我认为没有必要对时间进行推理,而只需对时间引起的冲突进行推理。
编辑:可以将这两种方法(链接+建议)标记为紧凑公式和扩展公式。扩展公式通常表现出更强的松弛和更好的(求解)结果只要可扩展性不是问题。随着数据量的增加,紧凑的方法可能会再次变得更加可行(因为调度传播器并不便宜,所以在这里很难猜测)。
我的建议:
- (1) 按照基本分配问题公式制定整数编程模型 + 调整使其矩形 -> 允许工人同时处理多项任务所有任务都解决了(一个和等式丢掉了)
- (2) 添加 integrity = 将变量标记为二进制 -> 因为问题不再满足 total unimodularity
- (3) 添加约束禁止冲突
- (4) 添加约束:剩余的东西(例如兼容性)
现在这一切都很简单,但我会针对 (3) 提出一项非天真的改进:
- 冲突可以解释为稳定的多胞体
- 您的冲突是由先验定义的时间窗口及其重叠引起的(按照我的解释;这是整个答案背后的核心假设)
- 这是一个区间图(因为有时间窗口)
- 所有区间图都是和弦
- Chordal 图允许在 poly-time 中枚举所有 max-cliques(意味着只有多项式很多)
- 所有极大团的集合(枚举)定义了稳定集多胞体的面
- 我们添加的那些(集合中每个元素的约束)作为约束!
- (此处使用的图上的稳定集多面体也将允许非常强大的半定松弛,但很难预见在哪些情况下这实际上会有所帮助,因为 SDP 更难使用:树中的热启动-搜索;可扩展性;...)
这将导致多尺寸整数规划问题,当使用好的 IP 求解器时应该会非常好(商业或如果需要开源:Cbc > GLPK)。
关于(3)的小演示
import itertools
import networkx as nx
# data: inclusive,exclusive
# --------------------------
time_windows = [
(2,7),(0,10),(6,12),(12,20),(8,(16,20)
]
# helper
# ------
def is_overlapping(a,b):
return (b[1] > a[0] and b[0] < a[1])
# raw conflicts
# -------------
binary_conflicts = []
for a,2):
if is_overlapping(time_windows[a],time_windows[b]):
binary_conflicts.append( (a,b) )
# conflict graph
# --------------
G = nx.Graph()
G.add_edges_from(binary_conflicts)
# maximal cliques
# ---------------
max_cliques = nx.chordal_graph_cliques(G)
print('naive constraints: raw binary conflicts')
for i in binary_conflicts:
print('sum({}) <= 1'.format(i))
print('improved constraints: clique-constraints')
for i in max_cliques:
print('sum({}) <= 1'.format(list(i)))
输出:
naive constraints: raw binary conflicts
sum((0,1)) <= 1
sum((0,2)) <= 1
sum((1,4)) <= 1
sum((2,4)) <= 1
sum((3,5)) <= 1
improved constraints: clique-constraints
sum([1,2,4]) <= 1
sum([0,1,2]) <= 1
sum([3,5]) <= 1
有趣的事实:
- 商业整数规划求解器,甚至 Cbc 甚至可能会尝试对团约束进行相同的推理在某种程度上尽管没有假设和弦,这是一个 NP 难题
- ortools 的 cp-sat 求解器也有一个代码路径(同样:一般的 NP-hard 情况)
- 在表达基于冲突的模型时应该触发(在一般的基于离散时间的调度模型上更难决定这种利用)
注意事项
实施/可扩展性
还有一些悬而未决的问题,例如:
- 在每个 worker 上复制 max-clique 约束与以某种方式合并它们
- 更高效/更聪明地发现冲突(排序)
- 它是否会根据数据进行缩放:图有多大/我们需要多少冲突和约束
但这些事情通常遵循实例统计(也就是“不要盲目决定”)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。