一、前言
小編有個小伙伴,隔三差五就過來跟我說:這個模型CPLEX怎么寫呢?我說我不是給你講過好多次?他說CPLEX太復雜了,俺沒學過學不會呢。
Similarly,遇到這個問題的不止小編這個小伙伴。很多剛入行的小伙伴都表示CPLEX對初學者來說并不是很友好,就連學習資料都不知道去哪里看,不像Excel或者Word,百度一下出來好多資料。
其實吧,這玩意兒并沒有大家想的那么難,尤其是簡單使用CPLEX求解一個模型的話,用來用去都是那幾個函數而已。下面小編來給大家好好理一下,看完相信你也能用CPLEX跑一下論文上的模型啦。
當然啦,為了方便小編還是選擇大家熟悉的Java平臺,用Python也是可以的,處理數據可能還更方便。但是我們一般都是用Java寫的算法,因此就統一平臺啦。
我們今天以一個最經典的VRPTW arc-flow model為例,手把手給大家演示下,CPLEX其實并不是那么的難用。
二、模型集合定義
運行一個模型之前,首先要定義模型中用到的一些參數和集合,如果這些都沒有,是無從談起的。因此沒有的話第一步是要先生成這些數據哦。
2.1 讀取數據
首先,你需要在程序中定義相關的變量(通常的做法是寫一個instance的類,把算例的數據讀進來,放到成員變量上。)比如:
至于你怎么定義怎么寫都無所謂啦,反正你知道這些數據對應模型的哪些參數就可以啦。
2.2 定義集合
其實小編發現,大家之所以覺得寫模型難,還有一個原因就是自己建模的時候純粹瞎搞。很多集合啊,參數啊,范圍啊都沒有想清楚,到寫代碼的時候就各種凌亂了。。。
好了回到我們的正題,剛剛讀入了算例。接下來我們需要定義模型中需要用到的集合,這些集合是哪些集合呢?就是我指出來的這些:
然后你需要在程序中把這些集合給定義好了,然后把相應的數據填充進去,比如為所有節點的集合,
為所有車輛集合,那么就for一下填充就好啦:
for(i = 0; i < inst.nbCust + 2; ++i){
this.N.add(i);
}
for(i = 0; i < inst.nbVeh; ++i){
this.K.add(i);
}
當然了,在程序中不用定義這些集合也能實現我們的模型,這樣做只是為了讓程序更清晰,不至于到后面雜亂無章,debug起來也無從下手。
三、CPLEX建模
做完數據的定義,基本上就成功50%了。就像追女孩紙一樣,當你喜歡她的時候就成功了50%,當她再喜歡你的時候,就100%成功了。現在我們就來完成剩下的50%。
在CPLEX中,你只需要知道以下三點,就能輕松駕馭一個數學模型啦:
- 決策變量定義
- 添加優化目標
- 添加約束
想想也是哦,一個數學模型無非就是由決策變量、優化目標和約束組成嘛。下面我們來一個一個講解。
不過,在此之前,我們先new一個CPLEX的對象出來,并設置一些參數:
this.cplex = new IloCplex();
this.cplex.setParam(IloCplex.Param.Simplex.Tolerances.Optimality, 1e-9);
this.cplex.setParam(IloCplex.Param.MIP.Tolerances.MIPGap, 1e-9);
this.cplex.setParam(IloCplex.DoubleParam.TimeLimit, 3600);
this.cplex.setOut(null);
第一第二句是求解精度相關的設置。倒數第二句表示設置求解時間為3600s,比較常用。最后一句是告訴CPLEX不要輸出那些亂七八糟的東西,太煩啦!
3.1 決策變量的定義
首先是模型中有哪些變量,通通得定義出來。在CPLEX的Java API中,一個決策變量是一個對象來的,首先我們需要定義決策變量的數組,并分配數組的空間,比如的:
this.x = new IloNumVar[n+1][n+1][v];
IloNumVar這個表示它是一個num也就是數值類型的變量,就是可以為浮點數也可以為整數。這里我們只分配了數組空間,接下來 還需要為里面的每個引用分配一個對象(分配了房子,再給它發媳婦!):
//分配內存
//x 0-1變量 [n+1][n+1][v];
for(int i = 0; i < n+1; ++i){
for(int j = 0; j < n+1; ++j){
for(int k = 0; k < v; ++k){
x[i][j][k] = cplex.numVar(0, 1, IloNumVarType.Int, "x["+i+"]["+j+"]["+k+"]");
}
}
}
其中cplex.numVar()這個函數呢就為我們new了一個數值變量的對象出來,我這里貼上官方的解釋好啦:
如果你有不同類型的變量,指定下第三個參數IloNumVarType就好啦:
模型中另一個決策變量類似,我就不寫啦。
3.2 CPLEX的表達式
首先來看一個概念:表達式是什么呢?吶,類似于我圈出來的這些:
開始的時候,一般需要new一條IloNumExpr類型的空表達式出來,然后慢慢去填充它:
IloNumExpr expr = this.cplex.numExpr();
創建空表達式可以通過numExpr()函數哦:
在CPLEX的JavaAPI中呢,涉及到CPLEX對象的一些表達式,是不能直接通過Java自帶的+-*/進行運算的。需要通過CPLEX提供sum()、diff()、prod()函數進行加、減、乘的操作。
那為什么沒有除呢?因為除是可以通過轉換變成乘的!比如可以轉換成
,沒毛病吧~
其中,sum()、diff()、prod()這些函數在CPLEX的庫中重載了很多版本,也就是說你sum(IloNumExpr, double)、sum(IloNumExpr, IloNumExpr)、sum(double, IloNumExpr)都是可以識別的,那么我就貼一個出來給大家看看就好啦:
sum()、diff()也是類似的,不過需要注意的是diff()時要注意區分是誰減去誰哦。現在表達式有了,我們來看看怎樣通過sum()、diff()、prod()這些函數,實現模型中的式子。以目標那個式子為例:
有三個求和符號,那么肯定得來三個循環啦:
IloNumExpr objExpr = this.cplex.numExpr();
for(k : this.K){
for(i : this.N){
for(j : this.N){
IloNumExpr subExpr = this.cplex.prod(c[i][j], this.x[i][j][k]);
obj = this.cplex.sum(obj, subExpr);
}
}
}
可能這一句obj = this.cplex.sum(obj, subExpr);
大家有點暈,其實很簡單,就是obj和subExpr相加,得到一個新的表達式,再賦值給obj。那么這樣就能實現累加的效果了,大部分的求和表達式都可以寫成這種形式哦。
3.3 添加目標和約束
好了,知道了表達式,添加目標和約束就變得非常簡單啦。首先是目標的添加,CPLEX中提供了兩個函數:addMinimize()和addMaximize()分別用以添加最小化目標和最大化目標。根據自己的需要調用就好,當然這兩個函數也是有很多重載的版本,我就放一個最常用的給大家看看吧:
參數就是一個IloNumExpr類型的表達式,比如可以直接把上面的objExpr給add進來,是不是很簡單呢!
對于添加約束,CPLEX也提供了三個函數,我這里寫成一個表格方便大家查看:
method | 作用 |
---|---|
addGe(a, b) | 添加約束 |
addLe(a, b) | 添加約束 |
addEq(a, b) | 添加約束 |
根據a,b類型的不同,這幾個函數同樣重載了很多版本,你寫addGe(IloNumExpr, double)、addGe(IloNumExpr, IloNumExpr)、addGe(double, IloNumExpr)
都是可以的。我放一個官方的介紹吧:
現在,我們來看看一個example,演示下如何添加約束(3.5):
首先,從哪著手呢?從右邊開始:對于任意的,任意的
,都要滿足坐標那個等式。兩個循環是沒跑了,然后在循環的最內層,把相關表達式給addEq就好了:
for(h : this.C){
for(k : this.V){
//這里要開始寫表達式啦
IloNumExpr subExpr1 = this.cplex.numExpr();
IloNumExpr subExpr2 = this.cplex.numExpr();
for(i : this.N){
subExpr1 = this.cplex.sum(subExpr1, this.x[i][h][k]);
subExpr2 = this.cplex.sum(subExpr1, this.x[h][j][k]);
}
cplex.addEq(subExpr1, subExpr2);
}
}
怎樣,是不是很簡單呢?當然了,這個easy是建立在一個清晰明了的模型基礎之上的,如果你一開始的模型就設置得亂七八糟,這個過程寫起來是很痛苦的。畢竟你要邊寫代碼邊修正模型,很可能寫著寫著就變成了一坨。。。
四、CPLEX求解
上面的模型建立完成以后,就可以調用solve()函數進行求解了,如果返回true,那么就找到了可行解(是的吧?我也不太清楚,可以去查查)。否則就是不可行解。
求解完成以后,獲取一個變量的值可以采用CPLEX的getValue()函數,參數是你new出來的決策變量。
總的來說,CPLEX已經為我們封裝好了很多東西,大部分只需要動動手指就可以直接使用了。少部分可能需要查查庫什么的,但是基本的時候已經非常簡單了。最后,貼上兩篇文章,大家可能會比較感興趣,小編也建議大家結合起來看,效果會更好哦:
CPLEX出現'q1' is not convex?
干貨|十分鐘快速掌握CPLEX求解VRPTW數學模型(附JAVA代碼及CPLEX安裝流程)
快點個贊關注我們。獲取更多精彩內容吧~