運籌系列12:約束規劃和python求解
阿新 • • 發佈:2018-11-24
1. 從算式謎問題說起
SEND + MORE = MONEY,每一個字母代表一個數字,如何求解?
這個問題顯然不是傳統意義上的整數規劃問題,並沒有需要進行優化的目標函式,因此使用約束規劃進行求解,問題建模如下:
from ortools.sat.python import cp_model
# Creates the model.
model = cp_model.CpModel()
kBase = 10
# Creates the variables.
s = model.NewIntVar(1, kBase - 1, 'S');
e = model.NewIntVar( 0, kBase - 1, 'E');
n = model.NewIntVar(0, kBase - 1, 'N');
d = model.NewIntVar(0, kBase - 1, 'D');
m = model.NewIntVar(1, kBase - 1, 'M');
o = model.NewIntVar(0, kBase - 1, 'O');
r = model.NewIntVar(0, kBase - 1, 'R');
y = model.NewIntVar(0, kBase - 1, 'Y');
letters = [s,e,n,d,m,o,r,y]
# Creates the constraints.
model.AddAllDifferent(letters)
model.Add(d + e + kBase * (n+r) + kBase * kBase * (e+o) + kBase * kBase * kBase * (s+m) ==
y + kBase * e + kBase * kBase * n + kBase * kBase * kBase * o + kBase * kBase * kBase * kBase * m)
# Creates a solver and solves the model.
solver = cp_model.CpSolver( )
注意其中有一個model.AddAllDifferent(letters),是要求所有變數都不相等的快速表達模式。
2. 輸出一個可行解
如果只需要輸出一個可行解,可以參照上一節的內容,將下列程式碼新增至模型之後:
status = solver.Solve(model)
print('status = %s' % solver.StatusName(status))
for v in letters:
print('%s=%i' % (v, solver.Value(v)), end=' ')
輸出為:
status = FEASIBLE
S=9 E=5 N=6 D=7 M=1 O=0 R=8 Y=2
3. 輸出所有解
若要輸出所有解,可以呼叫cp_model.CpSolver.SearchForAllSolutions(cp_model.CpModel, 回撥函式(變數list))。
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 OnSolutionCallback(self):
self.__solution_count += 1
for v in self.__variables:
print('%s=%i' % (v, self.Value(v)), end=' ')
print()
def SolutionCount(self):
return self.__solution_count
solution_printer = VarArraySolutionPrinter(letters)
status = solver.SearchForAllSolutions(model, solution_printer)
print('Status = %s' % solver.StatusName(status))
print('Number of solutions found: %i' % solution_printer.SolutionCount())
結果為:
S=9 E=5 N=6 D=7 M=1 O=0 R=8 Y=2
Status = FEASIBLE
Number of solutions found: 1
4. 設定結束條件
當問題規模比較大時,我們可以設定一些結束條件(解數量限制或者時間限制)。
時間限制的話,新增如下程式碼即可:
solver.parameters.max_time_in_seconds = 10.0
解數量限制的話,修改solver回撥函式,將__solution__limit修改為限制值即可,參考如下程式碼:
class VarArraySolutionPrinterWithLimit(cp_model.CpSolverSolutionCallback):
def __init__(self, variables, limit):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__variables = variables
self.__solution_count = 0
self.__solution_limit = limit
def OnSolutionCallback(self):
self.__solution_count += 1
for v in self.__variables:
print('%s=%i' % (v, self.Value(v)), end=' ')
print()
if self.__solution_count >= self.__solution_limit:
print('Stop search after %i solutions' % self.__solution_limit)
self.StopSearch()
def SolutionCount(self):
return self.__solution_count