1. 程式人生 > >運籌系列12:約束規劃和python求解

運籌系列12:約束規劃和python求解

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