本文參考Google OR-Tools官網文檔介紹OR-Tools的使用方法。
1 整數規劃
很多實際問題的變量不能是小數,比如指派多少人員、調度的航班數、分配的機器數等等,我們稱這種問題爲整數規劃,更特殊的,如果要求變量只能取0或1,則稱爲0-1規劃;還有些情況是部分決策變量是整數,其他可以是小數,則稱其爲混合整數規劃。雖然我們可以用線性函數來表示目標和約束,但是有了變量必須是整數的約束,可行域變得極度非凸(Nonconvex), 求解難度要比連續變量線性規劃大很多,連續變量線性規劃的常用算法是不能直接用於整數規劃的。關於這一點,後面我們可以給出實例。
解算整數規劃的算法中分支界定法及其衍生算法是最常用的,它的核心思想便是把整數規劃問題分解成求解一個個的線性規劃(LP)問題(每個LP問題是多項式時間可解),並且在求解的過程中實時追蹤原問題的上界(最優可行解)和下界(最優線性鬆弛解)。除此之外,啓發式算法或元啓發算法(例如遺傳算法)也是常用的手段,因爲當整數規劃的規模較大時,問題已經屬於NP-Hard問題了,這時候在合理的時間內找到一個相對最優解已經足夠了。
2 OR-Tools的整數規劃算法庫
在OR-Tools中用於解決整數規劃的工具是MP Solver和CP-SAT Solver,我們先認識MP Solver,至於CP-SAT Solver在下一篇文章中介紹,因爲兩者的適用情況是不一樣的。
OR-Tools實際上提供的是統一的求解器接口,內部連接的具體求解器我們可以自己配置,默認連接的是OR-Tools內置的CBC(Coin-or branch and cut)求解器,除此之外也支持下面幾種第三方求解器:
- SCIP
- GLPK
- Gurobi
不過如果要用第三方求解器的話就必須配置OR-Tools源碼並手動安裝,具體信息可參考官網的安裝教程。
另外一點是,MP Solver的算法屬於通用型算法,而像路徑優化、揹包問題和網絡流問題這些特定的問題領域,因爲場景在現實領域非常常見,有專門解決這類問題的特定算法,因此如果是對這些問題,就不建議使用MP Solver了,而是使用OR-Tools提供的特定接口。
3 演示
我們例舉下面這個例子:
其模型結構和線性規劃基本一致,除了多出兩個變量必須爲整數的約束。下面這張圖則更直觀地表現這個問題:
我們創建一個.Net Core控制應用,並下載OR-Tools。
首先創建Solver對象,這裏我們指明連接的求解器是CBC
// Create the linear solver with the CBC backend.
Solver solver = Solver.CreateSolver("SimpleMipProgram", "CBC_MIXED_INTEGER_PROGRAMMING");
定義變量和,注意這裏我們調的接口是MakeIntVar而不是線性規劃時調的MakeNumVar
// x and y are integer non-negative variables.
Variable x = solver.MakeIntVar(0.0, double.PositiveInfinity, "x");
Variable y = solver.MakeIntVar(0.0, double.PositiveInfinity, "y");
定義約束和目標
// x + 7 * y <= 17.5.
solver.Add(x + 7 * y <= 17.5);
// x <= 3.5.
solver.Add(x <= 3.5);
// Maximize x + 10 * y.
solver.Maximize(x + 10 * y);
計算得到解,規劃問題有時候未必有解,因此我們需要查看Solver對象的計算結果是否是OPTIMAL
Solver.ResultStatus resultStatus = solver.Solve();
// Check that the problem has an optimal solution.
if (resultStatus != Solver.ResultStatus.OPTIMAL)
{
Console.WriteLine("The problem does not have an optimal solution!");
return;
}
Console.WriteLine("Solution:");
Console.WriteLine("Objective value = " + solver.Objective().Value());
Console.WriteLine("x = " + x.SolutionValue());
Console.WriteLine("y = " + y.SolutionValue());
完整的程序和運行結果:
using System;
using Google.OrTools.LinearSolver;
namespace Demo3
{
class Program
{
static void Main(string[] args)
{
// Create the linear solver with the CBC backend.
Solver solver = Solver.CreateSolver("SimpleMipProgram", "CBC_MIXED_INTEGER_PROGRAMMING");
// x and y are integer non-negative variables.
Variable x = solver.MakeIntVar(0.0, double.PositiveInfinity, "x");
Variable y = solver.MakeIntVar(0.0, double.PositiveInfinity, "y");
Console.WriteLine("Number of variables = " + solver.NumVariables());
// x + 7 * y <= 17.5.
solver.Add(x + 7 * y <= 17.5);
// x <= 3.5.
solver.Add(x <= 3.5);
Console.WriteLine("Number of constraints = " + solver.NumConstraints());
// Maximize x + 10 * y.
solver.Maximize(x + 10 * y);
Solver.ResultStatus resultStatus = solver.Solve();
// Check that the problem has an optimal solution.
if (resultStatus != Solver.ResultStatus.OPTIMAL)
{
Console.WriteLine("The problem does not have an optimal solution!");
return;
}
Console.WriteLine("Solution:");
Console.WriteLine("Objective value = " + solver.Objective().Value());
Console.WriteLine("x = " + x.SolutionValue());
Console.WriteLine("y = " + y.SolutionValue());
Console.WriteLine("\nAdvanced usage:");
Console.WriteLine("Problem solved in " + solver.WallTime() + " milliseconds");
Console.WriteLine("Problem solved in " + solver.Iterations() + " iterations");
Console.WriteLine("Problem solved in " + solver.Nodes() + " branch-and-bound nodes");
}
}
}
我們可以看到解算整數規劃的代碼和之前介紹的線性規劃程序幾乎一致,那麼我們可以改成線性規劃來計算結果看看其結果的差異如何
改成線性規劃問題只需要該幾行代碼,首先把CBC求解器改成GLOP:
// Create the linear solver with the CBC backend.
//Solver solver = Solver.CreateSolver("SimpleMipProgram", "CBC_MIXED_INTEGER_PROGRAMMING");
Solver solver = Solver.CreateSolver("SimpleMipProgram", "GLOP_LINEAR_PROGRAMMING");
再把MakeIntVar改成MakeNumVar
//Variable x = solver.MakeIntVar(0.0, double.PositiveInfinity, "x");
//Variable y = solver.MakeIntVar(0.0, double.PositiveInfinity, "y");
Variable x = solver.MakeNumVar(0.0, double.PositiveInfinity, "x");
Variable y = solver.MakeNumVar(0.0, double.PositiveInfinity, "y");
其運算結果:
可以看到其結果和整數規劃下的結果相差是比較大的,因此這兩種規劃問題所採用的算法也不一致。下面這副圖直觀地表現出區別