为何AddNoOverlap结合OnlyEnforceIf在离心机任务约束中失效?
我需要在约束满足问题中调度使用离心机的任务,要求任务要么完全重叠(起始时间相同),要么完全不重叠。
我尝试了以下代码实现该约束:
from ortools.sat.python import cp_model import collections model = cp_model.CpModel() # Named tuple to store information about created variables. task_type = collections.namedtuple("task_type", "start end interval") # Create tasks task1_start = model.NewIntVar(0, 10, 'task1_start') task1_end = model.NewIntVar(0, 10, 'task1_end') task1_interval = model.NewIntervalVar(task1_start, 5, task1_end, 'task1_interval') task1 = task_type(start=task1_start, end=task1_end, interval=task1_interval) task2_start = model.NewIntVar(0, 10, 'task2_start') task2_end = model.NewIntVar(0, 10, 'task2_end') task2_interval = model.NewIntervalVar(task2_start, 5, task2_end, 'task2_interval') task2 = task_type(start=task2_start, end=task2_end, interval=task2_interval) # Create centrifuge tasks centrifuge_tasks = [task1,task2] # Add overlap constraints for i, cent_a in enumerate(centrifuge_tasks): for j, cent_b in enumerate(centrifuge_tasks): if i < j: b = model.NewBoolVar(f"centrifuge_overlap_{cent_a.interval.name}_{cent_b.interval.name}") model.Add(cent_a.start == cent_b.start).OnlyEnforceIf(b) model.AddNoOverlap([cent_a.interval, cent_b.interval]).OnlyEnforceIf(~b) # Solve the model solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print("Task 1 start:", solver.Value(task1_start)) print("Task 2 start:", solver.Value(task2_start)) else: print("No solution found")
执行后返回:
No solution found
改用以下代码后得到可行解:
from ortools.sat.python import cp_model import collections model = cp_model.CpModel() # Named tuple to store information about created variables. task_type = collections.namedtuple("task_type", "start end interval") # Create tasks task1_start = model.NewIntVar(0, 10, 'task1_start') task1_end = model.NewIntVar(0, 10, 'task1_end') task1_interval = model.NewIntervalVar(task1_start, 5, task1_end, 'task1_interval') task1 = task_type(start=task1_start, end=task1_end, interval=task1_interval) task2_start = model.NewIntVar(0, 10, 'task2_start') task2_end = model.NewIntVar(0, 10, 'task2_end') task2_interval = model.NewIntervalVar(task2_start, 5, task2_end, 'task2_interval') task2 = task_type(start=task2_start, end=task2_end, interval=task2_interval) # Create centrifuge tasks centrifuge_tasks = [task1,task2] # Add overlap constraints for i, cent_a in enumerate(centrifuge_tasks): for j, cent_b in enumerate(centrifuge_tasks): if i < j: b = model.NewBoolVar(f"centrifuge_overlap_{cent_a.interval.name}_{cent_b.interval.name}") b_order = model.NewBoolVar(f"centrifuge_order_{cent_a.interval.name}_{cent_b.interval.name}") model.Add(cent_a.start == cent_b.start).OnlyEnforceIf(b) model.Add(cent_a.end <= cent_b.start).OnlyEnforceIf(~b, b_order) model.Add(cent_b.end <= cent_a.start).OnlyEnforceIf(~b, ~b_order) # Solve the model solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.FEASIBLE or status == cp_model.OPTIMAL: print("Task 1 start:", solver.Value(task1_start)) print("Task 2 start:", solver.Value(task2_start)) else: print("No solution found")
执行后返回:
Task 1 start: 0 Task 2 start: 0
请问为何使用AddNoOverlap结合OnlyEnforceIf的方式无法生效?
问题原因
核心问题是OR-Tools CP-SAT求解器的全局约束不支持OnlyEnforceIf条件化启用:
AddNoOverlap属于全局约束,这类约束是针对整个变量集合(这里是两个区间变量)的逻辑约束,求解器的内部实现并没有设计为支持通过OnlyEnforceIf来条件性地开启或关闭。当你尝试用OnlyEnforceIf(~b)修饰AddNoOverlap时,求解器并不会正确解析这个条件逻辑,导致约束没有按照预期生效,甚至可能引入隐性矛盾,最终找不到可行解。而第二种方法中,你将“不重叠”的逻辑拆分为两个互斥的线性约束:
cent_a.end <= cent_b.start(A在B之前结束)或cent_b.end <= cent_a.start(B在A之前结束),并用布尔变量b_order来选择其中一种情况。这类普通的线性约束(Add创建的约束)是支持OnlyEnforceIf修饰的,因此能正确建模“不重叠”的条件逻辑,结合b变量控制的“完全重叠”逻辑,就能覆盖所有合法的调度情况,从而找到可行解。
补充说明
如果需要用全局约束实现类似的条件化逻辑,通常需要通过复制变量或引入额外的布尔变量来激活/禁用区间,比如:当~b为真时,强制其中一个区间的长度为0(但这不符合你的任务必须执行的需求),或者使用其他方式间接建模。但对于你的需求,第二种方法的显式顺序约束已经是最直接且有效的实现方式。
内容的提问来源于stack exchange,提问作者Austin

