OR-Tools CP-SAT 操作指南
最近笔者一直在从事整数规划方面的排程编程,主要使用的工具是Google的OR-tools,因此笔者在互联网上收集了很多有意思的技巧与知识想分享一下。首先这是OR-Tools的官网,里面有许许多多的例子,感兴趣的朋友可以自己去学习一下,笔者这里不再赘述了。
下面笔者先对Or-Tools的建模语言cp_model及其求解器CP-SAT Solver的使用技巧做一下简单介绍,随后对一些复杂的建模语法通过几个例子对大家详细讲解,以下素材均来自于互联网。这里的部分介绍引用自-派神-大大的博客线性规划之Google OR-Tools 简介与实战,笔者也会对部分约束进行解释。
CP-SAT 简介
在or-tools中内置了一些第三方的开源求解器(SCIP, GLPK) 及商用求解器( Gurobi , CPLEX),同时还有google自己的求解器Google's GLOP以及获得过多项金奖的CP-SAT。
下面笔者将对CP-SAT中使用频率较高的建模语法和使用技巧进行介绍,cp_model是一种很奇妙的建模语法,它主要使用整型变量对优化问题进行建模。
Or-tools 支持C#、python、java、c++建模。其实笔者都是使用C#作为建模语言的,但是此文为了普适性所以用python作为此文的建模与语言
求解器变量
非连续整形变量(Intvar)
# List of values
model.NewIntVarFromDomain(
cp_model.Domain.FromValues([1, 3, 4, 6]), 'x'
)
# List of intervals
model.NewIntVarFromDomain(
cp_model.Domain.FromIntervals([[1, 2], [4, 6]]), 'x'
)
# Exclude [-1, 1]
model.NewIntVarFromDomain(
cp_model.Domain.FromIntervals([[cp_model.INT_MIN, -2], [2, cp_model.INT_MAX]]), 'x'
)
# Constant
model.NewConstant(154)
model.NewIntVarFromDomain
从域区间创建整数变量。
域区间是由区间集合所指定的一组整数。 例如:
model.NewIntVarFromDomain(cp_model.Domain.FromIntervals([[1, 2], [4, 6]]), 'x')
Constant
Constant
表示一个特殊的IntVal,其上界等于其下界所以其IntVal所表示的值也是一个常量。
BoolVar
表示一个特殊的IntVal,其下界为0,上界为1。所以其变量的值只能为 0 或者 1 。
IntervalVar
CP-SAT有一种特殊的区间变量,它可以用来表示时间间隔。通常配合约束AddNoOverLap
与AddCumulative
使用表示区间不能重叠或者区间累计。有两种创建此变量的方式:
model.NewIntervalVar(self, start, size, end, name)
model.NewOptionalIntervalVar(self, start, size, end, is_present, name)
相同的是都需要start
、size
和end
参数表示区间的开始的整型变量、尺寸或者时间的长度和区间的结束的整型变量。
不同的是前者表示创建的区间变量在以后的约束建立中一定生效,而后者的方法签名中有个为is_present的参数表示这个区间变量是否生效。
求解器约束
Implications(离散数学里的蕴涵)
Implications方法是一种单向的约束操作:a = b (a,b均为布尔型变量) ,a为True则b也为True,反之则不成立。
其真值表为:
p | q | p-q |
---|---|---|
0 | 1 | 1 |
1 | 1 | 1 |
0 | 0 | 1 |
1 | 0 | 0 |
# a = b (both booleans)
model.AddImplication(a, b)
# a <= b (better remove one of them)
model.Add(a == b)
# a or b or c = d
model.AddImplication(a, d) # model.Add(d == 1).OnlyEnforceIf(a)
model.AddImplication(b, d)
model.AddImplication(c, d)
# a and b and c = d
model.Add(d == 1).OnlyEnforceIf([a, b, c])
or
model.AddBoolOr([a.Not(), b.Not(), c.Not(), d])
Linear Constraints(线性约束)
常用的线性约束有Add,AddLinearConstraint,AddLinearExpressionInDomain等几种:
#布尔数组work的和<=6
model.Add(sum(work[(i)] for i in range(10))<=6)
#布尔数组work的和=2 and <=6
model.AddLinearConstraint(sum(work[(i)] for i in range(10)), 2, 6)
#布尔数组work的和 in [0,2,3,5]
model.AddLinearExpressionInDomain(sum(work[(i)] for i in range(10)) ,[0,2,3,5])
Nonlinear Constraints(非线性约束)
常用的几种非线性约束如:绝对值约束、乘法约束、最大最小值约束
# Adds constraint: target == |var|
model.AddAbsEquality(target, var)
#Adds constraint: target == v1 * v2
model.AddMultiplicationEquality(target, [v1,v2])
#Adds constraint: target == Max(var_arr)
model.AddMaxEquality(target, var_arr)
#Adds constraint: target == Min(var_arr)
model.AddMinEquality(target, var_arr)
遗憾的是:没有一步到位的非线性表达式; 必须建立复杂的使用中间变量逐段地生成数学表达式。
The AllDifferent Constraints(强制所有变量都不相同约束)
#Forces all vars in the array to take on different values
model.AddAddAllDifferent(var_arr)
The Element Constraint(元素约束)
# Adds constraint: target == var_arr[index]
# Useful because index can be a variable
# The var_arr can also contain constants!
model.AddElement(index, var_arr, target)
The Inverse Constraint(逆约束)
# The arrays should have the same size 𝑛 (can’t use dicts)
# The vars in both arrays can only take values from 0 to 𝑛 − 1
# Adds the following constraints:
# If var_arr[i] == j, then inv_arr[j] == i
# If inv_arr[j] == i, then var_arr[i] == j
# Intuition: sets up a “perfect matching” between the two sets of variables
model.AddInverse(var_arr, inv_arr)
使不同的两个列表中的所有元素
NoOverlap Constraint (不重叠约束)
当笔者们创建了一组区间变量以后,有时候笔者们希望区间变量之间的时间间隔不能发生重叠,这时笔者们可以使用AddNoOverlap约束。AddNoOverlap会强制所有的时间间隔变量不发生重叠,不过它们可以使用相同的开始/结束的时间点。
# Note: there is no way to access start, end, duration of an interval by default
# Recommended: directly add them as fields of the interval, e.g.
# interval.start = start
model.AddNoOverlap(interval_arr)
# Powerful constraint: enforces that all intervals in the array do not overlap with each other!
# It’s OK to have shared start/endpoints
AllowedAssignments
Adds AllowedAssignments(variables, tuples_list).
AllowedAssignments
约束是对变量数组的约束,它要求当所有变量都被赋值时,其结果的数组内容等于 tuple_list
中的某一个元组的内容。
搜索最优解
单个最优化目标
使用model.Maximize()
或者model.Minimize()
设置模型的最优化目标。再通过 CpSolver 对象下的 solve(model) 方法求解模型即可开始求解,求解完成后方法会返回此次求解的状态:
Status | Description |
---|---|
OPTIMAL | An optimal feasible solution was found. |
FEASIBLE | A feasible solution was found, but we don't know if it's optimal. |
INFEASIBLE | The problem was proven infeasible. |
MODEL_INVALID | The given CpModelProto didn't pass the validation step. You can get a detailed error by calling ValidateCpModel(model_proto). |
UNKNOWN | The status of the model is unknown because no solution was found (or the problem was not proven INFEASIBLE) before something caused the solver to stop, such as a time limit, a memory limit, or a custom limit set by the user. |
求解完成后可以通过solver.ObjectiveValue()取得优化目标, 以及使用 solve.Value(IntVar) 方法取得模型中求解器变量的值。
求解所有可行解
求解所有可行解需要用到def SolveWithSolutionCallback(self, model, callback)
方法。而且需要用到一个额外的回调处理每一个解决方案中的解。具体参考or-tools文档
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, variables):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__variables = variables
self.__solution_count = 0
def on_solution_callback(self):
self.__solution_count += 1
for v in self.__variables:
print('%s=%i' % (v, self.Value(v)), end=' ')
print()
def solution_count(self):
return self.__solution_count
小技巧1 如何搜索所有的最优解
如果你在求解一个优化问题的时候设置了一个目标如:model.Maximize
或者model.Minimize
。因为最优化目标设置后只会得出一个解,所以你将不能使用SearchForAllSolutions方法来搜索跟最优解优化目标相同大小的解。但用一个小技巧,可以使用以下两个步骤来搜索所有优化目标达到最优的解:
# Get the optimal objective value
model.Maximize(objective)
solver.Solve(model)
# Set the objective to a fixed value
# use round() instead of int()
model.Add(objective == round(solver.ObjectiveValue()))
model.Proto().ClearField('objective')
# Search for all solutions
solver.SearchForAllSolutions(model, cp_model.VarArraySolutionPrinter([x, y, z]))
小技巧2 如何设置前后多个最优化目标
用求解目标得出解决方案后,再使用之前的求解方案的最优化目标的值作为下一个最优化目标的一个约束,并对模型AddHint提示上个解决方案的值再用求解目标求解。
from ortools.sat.python import cp_model
model = cp_model.CpModel()
solver = cp_model.CpSolver()
x = model.NewIntVar(0, 10, "x")
y = model.NewIntVar(0, 10, "y")
# Maximize x
model.Maximize(x)
solver.Solve(model)
print("x", solver.Value(x))
print("y", solver.Value(y))
print()
# Hint (speed up solving)
model.AddHint(x, solver.Value(x))
model.AddHint(y, solver.Value(y))
# Maximize y (and constraint prev objective)
model.Add(x == round(solver.ObjectiveValue())) # use <= or >= if not optimal
model.Maximize(y)
solver.Solve(model)
print("x", solver.Value(x))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了