如何在OR-Tools中为Interval变量编写替代约束及相关疑问
OR-Tools中Interval变量的替代约束与激活状态判断
一、实现替代约束(替代不存在的AddAlternative方法)
OR-Tools的CP-SAT模块没有AddAlternative方法,要实现“一个区间变量由多个替代区间选其一”的逻辑,需要结合**可选区间变量(OptionalIntervalVar)**和布尔变量构建约束,核心逻辑如下:
- 将主区间或替代区间设为可选区间,用布尔变量控制激活状态;
- 确保同一时间仅一个区间处于激活状态;
- 绑定激活区间的
start/end/duration与主区间的对应变量(如需同步时间属性)。
修正后的示例代码
from ortools.sat.python import cp_model model = cp_model.CpModel() # 定义时间范围常量 time_min = 0 time_max = 100 # 主区间:设为可选,用布尔变量控制激活 main_start = model.NewIntVar(time_min, time_max, 'main_start') main_duration = model.NewIntVar(1, time_max, 'main_duration') main_end = model.NewIntVar(time_min, time_max, 'main_end') main_present = model.NewBoolVar('main_present') main_interval = model.NewOptionalIntervalVar(main_start, main_duration, main_end, main_present, 'main_interval') # 替代区间1:可选区间 alt1_start = model.NewIntVar(time_min, time_max, 'alt1_start') alt1_duration = model.NewIntVar(5, 20, 'alt1_duration') alt1_end = model.NewIntVar(time_min, time_max, 'alt1_end') alt1_present = model.NewBoolVar('alt1_present') alt1_interval = model.NewOptionalIntervalVar(alt1_start, alt1_duration, alt1_end, alt1_present, 'alt1_interval') # 替代区间2:可选区间 alt2_start = model.NewIntVar(time_min, time_max, 'alt2_start') alt2_duration = model.NewIntVar(10, 30, 'alt2_duration') alt2_end = model.NewIntVar(time_min, time_max, 'alt2_end') alt2_present = model.NewBoolVar('alt2_present') alt2_interval = model.NewOptionalIntervalVar(alt2_start, alt2_duration, alt2_end, alt2_present, 'alt2_interval') # 约束1:仅激活主区间或其中一个替代区间 model.AddBoolOr([main_present, alt1_present, alt2_present]) model.AddAtMostOne([main_present, alt1_present, alt2_present]) # 约束2:激活替代区间1时,同步主区间的时间属性 model.Add(main_start == alt1_start).OnlyEnforceIf(alt1_present) model.Add(main_duration == alt1_duration).OnlyEnforceIf(alt1_present) model.Add(main_end == alt1_end).OnlyEnforceIf(alt1_present) # 约束3:激活替代区间2时,同步主区间的时间属性 model.Add(main_start == alt2_start).OnlyEnforceIf(alt2_present) model.Add(main_duration == alt2_duration).OnlyEnforceIf(alt2_present) model.Add(main_end == alt2_end).OnlyEnforceIf(alt2_present) # 求解 solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: print(f"主区间激活状态: {solver.Value(main_present)}") print(f"替代区间1激活状态: {solver.Value(alt1_present)}") print(f"替代区间2激活状态: {solver.Value(alt2_present)}")
适配你的实际代码
你的代码中y是强制激活的可选区间,若要让y作为x的替代区间,需修改y的激活控制为布尔变量,再添加约束:
# 修改y的定义:将is_present改为布尔变量 y = [ model.NewOptionalIntervalVar( start=model.NewIntVar(row.dia_inicio, row.dia_fin, f"start_{idx}"), size=row.tiempo_total, end=model.NewIntVar(row.dia_inicio, row.dia_fin, f"end_{idx}"), is_present=model.NewBoolVar(f"y_present_{idx}"), # 改为布尔变量控制激活 name="pte_intv_{}".format(idx), ) for idx, row in pozo_time_equipment.iterrows() ] # 以x[0]为例,绑定对应的y区间作为替代 # 1. 将原x[0]转为可选区间(允许被替代) x0_start = x[0].StartExpr() x0_duration = x[0].DurationExpr() x0_end = x[0].EndExpr() x0_present = model.NewBoolVar("x0_present") x0_optional = model.NewOptionalIntervalVar(x0_start, x0_duration, x0_end, x0_present, "x0_optional") # 2. 选取对应的y区间(比如y[0]和y[1])作为替代 y0_present = y[0].IsPresentExpr() y1_present = y[1].IsPresentExpr() # 约束:x0、y0、y1只能激活一个 model.AddBoolOr([x0_present, y0_present, y1_present]) model.AddAtMostOne([x0_present, y0_present, y1_present]) # 约束:激活y0时同步x0的时间属性 model.Add(x0_start == y[0].StartExpr()).OnlyEnforceIf(y0_present) model.Add(x0_duration == y[0].DurationExpr()).OnlyEnforceIf(y0_present) model.Add(x0_end == y[0].EndExpr()).OnlyEnforceIf(y0_present) # 约束:激活y1时同步x0的时间属性 model.Add(x0_start == y[1].StartExpr()).OnlyEnforceIf(y1_present) model.Add(x0_duration == y[1].DurationExpr()).OnlyEnforceIf(y1_present) model.Add(x0_end == y[1].EndExpr()).OnlyEnforceIf(y1_present)
二、判断Interval变量的激活状态
- 普通IntervalVar(
NewIntervalVar创建):这类变量始终处于激活状态,不存在未激活情况,无需判断。 - 可选IntervalVar(
NewOptionalIntervalVar创建):通过其绑定的is_present布尔变量判断,求解后调用solver.Value(is_present_var),返回1表示激活,0表示未激活。
示例代码
# 获取y[0]对应的激活状态布尔变量 y0_present = y[0].IsPresentExpr() solver = cp_model.CpSolver() status = solver.Solve(model) if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: if solver.Value(y0_present) == 1: print(f"区间{y[0].Name()}已激活") print(f"开始时间: {solver.Value(y[0].StartExpr())}") print(f"结束时间: {solver.Value(y[0].EndExpr())}") else: print(f"区间{y[0].Name()}未激活")
内容的提问来源于stack exchange,提问作者Sebastian Jose
相关产品推荐
相关产品推荐

