[英]What is the CP/MILP problem name for the assignment of agents to tasks with fixed start time and end time?
我正在嘗試解決將代理分配給任務的約束滿足優化問題。 然而,與基本的分配問題不同的是,如果任務不重疊,則可以將代理分配給許多任務。 每個任務都有固定的開始時間和結束時間。 代理根據一些一元和二元約束分配給任務。
變量 = 任務集
域 = 一組兼容的代理(對於每個變量)
約束=一元&二元
優化 fct = some liniar function
問題示例:為我們知道到達和離開時間的卡車分配停車位(或車隊)。
我對文獻中是否有此類問題的准確名稱感興趣。 我認為這是某種分配問題。 另外,如果您曾經遇到過這個問題,您是如何解決的?
謝謝你。
我會將其解釋為:具有沖突的矩形賦值問題可以說比多項式可解決的賦值問題更難(一般來說是NP 難)。
另一個答案中顯示的演示可能有效並且 ortools 的 cp-sat 很棒,但我沒有看到一個很好的理由在這里使用基於離散時間的推理就像它已經完成的那樣: interval-variables , edge-finding 和 co。 基於調度約束(+沖突分析/解釋)。 這些東西完全是矯枉過正,開銷會很明顯。 我認為沒有必要對時間進行推理,而只是對時間引發的沖突進行推理。
編輯:可以 label 這兩種方法(鏈接+建議)作為compact formulation和extended formulation 。 只要可擴展性不是問題,擴展公式通常會顯示出更強的松弛和更好的(求解)結果。 對於更大的數據,緊湊的方法可能會再次變得更加可行(這里很難猜測,因為調度傳播器並不便宜)。
我會建議:
現在這一切都很簡單,但我會針對 (3) 提出一個非天真的改進:
這將導致多尺寸整數規划問題,這在使用良好的 IP 求解器時應該非常好(商業廣告或如果需要開源: Cbc > GLPK )。
import itertools
import networkx as nx
# data: inclusive, exclusive
# --------------------------
time_windows = [
(2, 7),
(0, 10),
(6, 12),
(12, 20),
(8, 12),
(16, 20)
]
# helper
# ------
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)
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)))
Output:
naive constraints: raw binary conflicts
sum((0, 1)) <= 1
sum((0, 2)) <= 1
sum((1, 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
有趣的事實:
還有一些懸而未決的問題,例如:
但這些事情通常遵循實例統計(又名“不要盲目決定”)。
我不知道您所描述的特定變體的名稱 - 也許其他人會。 然而,這似乎確實適合 CP/MIP 求解器; 我會使用 OR-Tools CP-SAT 求解器 go,它免費、靈活且通常運行良好。
下面是 Python 的參考實現,假設每輛車都需要分配一個團隊且沒有重疊,並且目標是盡量減少使用中的團隊數量。 該框架允許直接 model 允許/禁止分配(查看文檔)
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 提出了一種分析(提前已知)時間 window 重疊的漂亮方法,這將使它可以作為分配問題解決。
因此,雖然上面的公式可能不是最佳的(盡管它可能是,這取決於求解器的工作方式),但我嘗試用建議的 max-clique 方法替換無重疊條件 - 下面的完整代碼。
我對中等規模的問題(100 輛和 300 輛汽車)進行了一些實驗,從經驗來看,在較小的問題(~100 輛)上,這確實有所改善——在最佳解決方案的時間上平均提高了 15%; 但我找不到對較大(~300)問題的重大改進。 這可能是因為我的公式不是最優的; 因為 CP-SAT 求解器(也是一個很好的 IP 求解器)足夠聰明; 或者因為我錯過了什么:)
代碼:
(這基本上與上面的代碼相同,其邏輯支持使用 .network 方法而不是從@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): 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
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, 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 = 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))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.