如何在OR-Tools CP-SAT排班中实现连续休息日约束?
解决飞行员每14天至少2天连续休息日的CP-SAT约束问题
你的核心问题是OR-Tools CP-SAT不支持直接对BoolVar做乘法运算,所以之前的sum(...) >=1写法行不通。需要通过辅助变量+逻辑约束的方式转化需求,以下是具体实现步骤:
步骤1:简化休息日变量(可选但推荐)
因为d=0和d=4都代表休息日,先为每个飞行员每天创建一个统一的“是否休息”BoolVar,减少后续约束复杂度:
rest = {} for p in p_n: for x in x_n: rest[(p, x)] = model.NewBoolVar(f"rest_p{p}_x{x}") # 约束:rest为1 当且仅当 当天选了d=0或d=4的班次 model.AddBoolOr([duties[(p, x, 0)], duties[(p, x, 4)]]).OnlyEnforceIf(rest[(p, x)]) model.AddBoolAnd([duties[(p, x, 0)].Not(), duties[(p, x, 4)].Not()]).OnlyEnforceIf(rest[(p, x)].Not())
步骤2:实现连续休息日约束
针对每个飞行员,遍历所有14天的滑动窗口(若需求是固定14天周期而非滑动,可调整窗口起始步长为14),确保每个窗口内至少存在一组连续两天都休息的情况:
for p in p_n: # 遍历所有14天窗口的起始日期(假设x_n是连续的日期索引列表) total_days = len(x_n) for x_start in range(total_days - 13): consecutive_rest_candidates = [] # 遍历窗口内的每一组连续两天 for x in range(x_start, x_start + 13): # 创建临时变量,标记这两天是否都休息 is_consec_rest = model.NewBoolVar(f"consec_rest_p{p}_x{x}_x{x+1}") # 约束:临时变量为1 当且仅当 两天都休息 model.Add(rest[(p, x)] + rest[(p, x+1)] == 2).OnlyEnforceIf(is_consec_rest) model.Add(rest[(p, x)] + rest[(p, x+1)] <= 1).OnlyEnforceIf(is_consec_rest.Not()) consecutive_rest_candidates.append(is_consec_rest) # 约束:窗口内至少有一组连续休息 model.Add(sum(consecutive_rest_candidates) >= 1)
替代实现方式(更简洁)
也可以不用临时变量,直接通过AddBoolOr约束窗口内至少存在一组连续两天都休息:
for p in p_n: total_days = len(x_n) for x_start in range(total_days - 13): # 收集所有“连续两天都休息”的逻辑条件 literals = [] for x in range(x_start, x_start + 13): # 用两个BoolVar的AND构造Literal both_rest = model.NewBoolVar(f"both_rest_p{p}_x{x}_x{x+1}") model.AddBoolAnd([rest[(p, x)], rest[(p, x+1)]]).OnlyEnforceIf(both_rest) literals.append(both_rest) # 约束至少一个条件成立 model.AddBoolOr(literals)
关键说明
OR-Tools CP-SAT的约束系统基于线性不等式和逻辑关系,不支持BoolVar的乘法运算。所有非线性的逻辑需求都需要通过辅助变量或逻辑子句转化为求解器可识别的形式,这是CP-SAT建模的核心技巧之一。
内容的提问来源于stack exchange,提问作者LaurieHarbord
相关产品推荐
相关产品推荐

