6 篇文章 16 订阅

OR-Tools官网

## 基本语法

#### 搜索所有最优解

# Get the optimal objective value
model.Maximize(objective)
solver.Solve(model)

# Set the objective to a fixed value
# use round() instead of int()
model.Proto().ClearField('objective')

# Search for all solutions
solver.SearchForAllSolutions(model, cp_model.VarArraySolutionPrinter([x, y, z]))

#### 非连续整形变量(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)

#### Implications(单向约束/半约束)

Implications方法是一种单向的约束操作:a => b (a,b均为布尔型变量) ，a为True则b也为True,反之则不成立。

# a => b (both booleans)

# a <=> b (better remove one of them)

# a or b or c => d

# a and b and c => d
or
model.AddBoolOr([a.Not(), b.Not(), c.Not(), d])

#### Iff, equivalence, boolean product

# (x and y) => p, rewrite as not(x and y) or p

# p => (x and y)
model.AddImplication(p, y)

#### If-Then-Else

if x>=5 then:
y=10-x
else:  y=0

b = model.NewBoolVar('b')

# Implement b == (x >= 5).

# First, b implies (y == 10 - x).
# Second, not(b) implies y == 0.
model.Add(y == 0).OnlyEnforceIf(b.Not())

#### Solution hint / Warm start(解决方案提示/热启动)

num_vals = 3
x = model.NewIntVar(0, num_vals - 1, 'x')
y = model.NewIntVar(0, num_vals - 1, 'y')
z = model.NewIntVar(0, num_vals - 1, 'z')

model.Maximize(x + 2 * y + 3 * z)

# Solution hinting: x <- 1, y <- 2
model.AddHint(y, 2)

### Storing Multi-index variables（存储多索引变量）

employee_works_day = {
(e, day): model.NewBoolVar(f"{e} works {day}")
for e in employees
for day in days
}

### Linear Constraints（线性约束）

#布尔数组work的和<=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|

#Adds constraint: target == v1 * v2



遗憾的是：没有一步到位的非线性表达式； 必须建立复杂的使用中间变量逐段地生成数学表达式。

### The AllDifferent Constraints（强制所有变量都不相同约束）

#Forces all vars in the array to take on different values


### protobuf

from google.protobuf import text_format
from ortools.sat.python import cp_model

model = cp_model.CpModel()
a = model.NewIntVar(0, 10, "a")
b = model.NewIntVar(0, 10, "b")
model.Maximize(a + b)

new_model = cp_model.CpModel()
text_format.Parse(str(model), new_model.Proto())

solver = cp_model.CpSolver()
status = solver.Solve(new_model)

print(solver.StatusName(status))
print(solver.ObjectiveValue())

### The Element Constraint（元素约束）

# Adds constraint: target == var_arr[index]
# Useful because index can be a variable
# The var_arr can also contain constants!



### 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
# 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



### Interval Variables (区间变量)

CP-SAT有一种特殊的区间变量，它可以用来表示时间间隔。

# Represents an interval, enforcing start - end == duration
# start, end, duration can be constants or variables!
# Note: there is no way to access start, end, duration of an interval by default
# Recommended: directly add them as fields of the interval object

model.NewIntervalVar(start, duration, end, name)



### NoOverlap Constraint (不重叠约束)

# 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

# Powerful constraint: enforces that all intervals in the array do not overlap with each other!
# It’s OK to have shared start/endpoints



### Parallelization（并行化）

solver = cp_model.CpSolver()
solver.parameters.num_search_workers = 8 #CPU内核数

### Optimization with CP-SAT (CP-SAT的优化目标)

model.Minimize(5*a+2*b-c)
# or
model.Maximize(sum(x[i] for i in range(10))



## OR-Tools实战

### 破译密码

from ortools.sat.python import cp_model

#初始化模型,并创建变量
model = cp_model.CpModel()
S = model.NewIntVar(1,9, 'S')
E = model.NewIntVar(0,9, 'E')
N = model.NewIntVar(0,9, 'N')
D = model.NewIntVar(0,9, 'D')
M = model.NewIntVar(1,9, 'M')
O = model.NewIntVar(0,9, 'O')
R = model.NewIntVar(0,9, 'R')
Y = model.NewIntVar(0,9, 'Y')

#添加约束
model.Add(   1000*S + 100*E + 10*N + D
+ 1000*M + 100*O + 10*R + E
== 10000*M + 1000*O + 100*N + 10*E + Y)

model.AddAllDifferent([S,E,N,D,M,O,R,Y])

#求解并打印结果
solver = cp_model.CpSolver()
if solver.Solve(model) == cp_model.OPTIMAL:
print([f'{v}={solver.Value(v)}' for v in [S,E,N,D,M,O,R,Y]])

### Magic Sequence（魔术序列）

from ortools.sat.python import cp_model
model = cp_model.CpModel()

S={}
n=4
for i in range(n+1):
S[i] = model.NewIntVar(0,n+1,f's_{i}')  # 创建初始化魔术序列s

eq={}
for i in range(n+1):
for j in range(n+1):
eq[i,j] = model.NewBoolVar(f's_{i}=={j}') # 创建布尔型二位矩阵
model.Add(S[i] == j).OnlyEnforceIf(eq[i,j]) # 只要eq[i,j]等于1,则 S[i] == j
model.Add(S[i]!=j).OnlyEnforceIf(eq[i,j].Not()) # 只要eq[i,j]等于0,则 S[i] != j

for i in range(n+1):
model.Add(S[i]==sum(eq[j,i] for j in range(n+1)))

solver = cp_model.CpSolver()
status = solver.Solve(model)
print("n :",n)
print([f's_{i}={solver.Value(S[i])}' for i in range(n+1)])

球队比赛安排计划，要求是这样的：任意一个球队在完成连续两个主场的比赛后，下一场必须去客场，同理任意一个球队在完成连续两个客场的比赛后，下一场必须去主场。也就是说每个球队主客场连续比赛的次数不能超过2次。下面我们来看一个代码片段：

a = model.NewBoolVar('home day1')
b = model.NewBoolVar('home day2')
c = model.NewBoolVar('break')

model.AddBoolOr([a, b.Not(), c.Not()])

model.AddBoolOr([a.Not(), b.Not(), c]) 

model.AddBoolOr([a, b, c])

model.AddBoolOr([a.Not(), b.Not(), c])
# model.AddBoolOr([a, b.Not(), c.Not()])

model.AddBoolOr([a.Not(), b, c.Not()])

model.AddBoolOr([a, b.Not(), c.Not()])

这里我们发现为了要让 AddBoolOr([a, b.Not(), c.Not()]) 的结果为True, 真值表中必须排除[0, 1, 1] 这个组合。因为[0, 1, 1] 会使AddBoolOr([a, b.Not(), c.Not()]) 的结果为False,这是不允许的。

# model.AddBoolOr([a.Not(), b.Not(), c])
model.AddBoolOr([a, b.Not(), c.Not()])

model.AddBoolOr([a.Not(), b.Not(), c])
model.AddBoolOr([a, b.Not(), c.Not()])

#### 使用OnlyEnforceIf来代替AddBoolOr

model.Add(a == b).OnlyEnforceIf(c)
model.Add(a != b).OnlyEnforceIf(c.Not())

OnlyEnforceIf可以表示为：当且仅当，上述两句OnlyEnforceIf表达的含义是: 当且仅当c = True时，则a和b值必须相同，同时当且仅当c = False时，则a和b值必须不相同。执行上述两句代码后所产生的真值表为：

model.Add(a == b).OnlyEnforceIf(c)
# model.Add(a != b).OnlyEnforceIf(c.Not())

## 火车路线分配问题

1.每列火车每天必须至少被分配一条路线（不得出现火车闲置的情况）。

2.必须将每列火车分配到至少一条路线（最多两条路线），并且必须覆盖所有路线。

3.分配给路线的火车最终里程不得超过24800（即前一天的累积里程+分配的路线里程<= 24800）。

4.如果一天中将火车分配了两条路线，则这两条路线的运行时间不得重叠。

5.前一天行驶里程高的火车应该分配到短途，前一天行驶里程低的火车应该分配到长途(这是一个软约束，应尽量满足)。

### 初始化数据

import datetime
from itertools import combinations
from datetimerange import DateTimeRange
from ortools.sat.python import cp_model

# 路线里程
route_km = {
'R11': 700,
'R32': 600,
'R16': 600,
'R41': 10,
'R42': 10,
'R43': 10,
'R44': 10,
'R45': 10}

#火车累积行驶里程
train_cum_km = {
'T32': 24_300,
'T11': 24_200,
'T38': 24_200,
'T28': 600,
'T15': 200,
'T24': 100}

#路线时刻表
route_times = {
'R11': ('05:00', '00:00'),
'R32': ('06:00', '00:50'),
'R16': ('05:20', '23:40'),
'R41': ('11:15', '12:30'),
'R42': ('11:45', '13:00'),
'R43': ('12:15', '13:30'),
'R44': ('12:45', '14:00'),
'R45': ('13:20', '14:35')}

trains = list(train_cum_km.keys())
routes = list(route_km.keys())
num_trains = len(trains)
num_routes = len(routes)

### 定义函数

from datetimerange import DateTimeRange

#检查时间范围是否重叠
def check_overlap(t1_st, t1_ed, t2_st, t2_ed):
t1_st_dt=datetime.datetime.strptime(t1_st, '%H:%M')
t1_ed_dt=datetime.datetime.strptime(t1_ed, '%H:%M')

t2_st_dt=datetime.datetime.strptime(t2_st, '%H:%M')
t2_ed_dt=datetime.datetime.strptime(t2_ed, '%H:%M')

if t1_ed_dt <= t1_st_dt: # 跨天则修改t1_ed_dt的日期
t1_ed_dt += datetime.timedelta(days = 1)

if t2_ed_dt <= t2_st_dt: # 跨天则修改t2_ed_dt的日期
t2_ed_dt += datetime.timedelta(days = 1)

t1_range = DateTimeRange(t1_st_dt.strftime('%Y-%m-%d %H:%M:%S'),
t1_ed_dt.strftime('%Y-%m-%d %H:%M:%S'))
t2_range = DateTimeRange(t2_st_dt.strftime('%Y-%m-%d %H:%M:%S'),
t2_ed_dt.strftime('%Y-%m-%d %H:%M:%S'))

return t1_range.is_intersection(t2_range)

### 创建约束变量

model = cp_model.CpModel()
solver = cp_model.CpSolver()

#创建约束变量
assignments = {(t, r): model.NewBoolVar('assignment_%s%s' % (t, r)) for t in trains for r in routes}

## 约束1: 每条路线只能分配给1列火车

for r in routes:
model.Add(sum(assignments[(t, r)] for t in trains) == 1)

## 约束2: 必须将每列火车分配到至少一条路线（最多两条路线）

for t in trains:
#model.Add(sum(assignments[(t, r)] for r in routes) >= 1)
#model.Add(sum(assignments[(t, r)] for r in routes) <= 2)
model.AddLinearConstraint(sum(assignments[(t, r)] for r in routes),1,2)

## 约束3:

### 分配给路线的火车最终里程不得超过24800（即前一天的累积里程+分配的路线里程<= 24800）

day_end_km = { t: model.NewIntVar(0, 24_800, 'train_%s_day_end_km' % t) for t in trains }
for t in trains:
model.Add(sum(assignments[t, r]*route_km[r] for r in routes) + train_cum_km[t] == day_end_km[t])

## 约束4：分配给火车的任意两条路线时间上不能重叠

for (r1, r2) in combinations(routes, 2):
if check_overlap(route_times[r1][0], route_times[r1][1], route_times[r2][0], route_times[r2][1]):
for train in trains:
model.AddBoolOr([assignments[train, r1].Not(), assignments[train, r2].Not()])

## 约束5: 软约束

#### 前一天行驶里程高的火车应该分配给短途路线，前一天行驶里程低的火车应该分配给长途路线

score = {(t,r): route_km[r] + train_cum_km[t] for t in trains for r in routes }
model.Minimize(sum(assignments[t,r]*score[t,r] for t in trains for r in routes))

## 求解

status = solver.Solve(model)
assert status in [cp_model.FEASIBLE, cp_model.OPTIMAL]

for t in trains:
t_routes = [r for r in routes if solver.Value(assignments[t, r])]
print(f'Train {t} 分配路线: {t_routes}',','
f'总累积行驶里程: '
f'{solver.Value(day_end_km[t])}')

## 参考文档

https://www.xiang.dev/cp-sat/#

• 35
点赞
• 63
收藏
觉得还不错? 一键收藏
• 打赏
• 24
评论
12-03 1384
12-03 989
03-22 1733
12-27 1万+
04-26 877
12-14 1061
05-12
06-13 4234

-派神-

¥1 ¥2 ¥4 ¥6 ¥10 ¥20

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、付费专栏及课程。