代碼黑科技的分享區node
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1、前言
小編有個小夥伴,隔三差五就過來跟我說:這個模型CPLEX怎麼寫呢?我說我不是給你講過好屢次?他說CPLEX太複雜了,俺沒學過學不會呢。其實對於不少剛入行的小夥伴來講,CPLEX算不上友好,就連學習資料都不知道去哪裏看,不像Excel或者Word,百度一下出來好多資料。web
其實吧,這玩意兒並無你們想的那麼難,尤爲是簡單使用CPLEX求解一個模型的話,用來用去都是那幾個函數而已。下面小編來給你們好好理一下,看完相信你也能用CPLEX跑一下論文上的模型啦。算法
固然啦,爲了方便小編仍是選擇你們熟悉的Java平臺,用Python也是能夠的,處理數據可能還更方便。可是咱們通常都是用Java寫的算法,所以就統一平臺啦。咱們今天以一個最經典的VRPTW arc-flow model爲例,手把手給你們演示下,CPLEX其實並非那麼的難用。編程
![](http://static.javashuo.com/static/loading.gif)
2、模型集合定義
運行一個模型以前,首先要定義模型中用到的一些參數和集合,若是這些都沒有,是無從談起的。所以沒有的話第一步是要先生成這些數據哦。數組
2.1 讀取數據
首先,你須要在程序中定義相關的變量(一般的作法是寫一個instance的類,把算例的數據讀進來,放到成員變量上。)好比:微信
![](http://static.javashuo.com/static/loading.gif)
至於你怎麼定義怎麼寫都無所謂啦,反正你知道這些數據對應模型的哪些參數就能夠啦。app
2.2 定義集合
其實小編髮現,你們之因此以爲寫模型難,還有一個緣由就是本身建模的時候純粹瞎搞。不少集合啊,參數啊,範圍啊都沒有想清楚,到寫代碼的時候就各類凌亂了。。。編輯器
好了回到咱們的正題,剛剛讀入了算例。接下來咱們須要定義模型中須要用到的集合,這些集合是哪些集合呢?就是我指出來的這些:svg
![](http://static.javashuo.com/static/loading.gif)
而後你須要在程序中把這些集合給定義好了,而後把相應的數據填充進去,好比 爲全部節點的集合, 爲全部車輛集合,那麼就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起來也無從下手。
3、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了一個數值變量的對象出來,我這裏貼上官方的解釋好啦:
![](http://static.javashuo.com/static/loading.gif)
若是你有不一樣類型的變量,指定下第三個參數IloNumVarType就好啦:
![](http://static.javashuo.com/static/loading.gif)
模型中另外一個決策變量 相似,我就不寫啦。
3.2 CPLEX的表達式
首先來看一個概念:表達式是什麼呢?吶,相似於我圈出來的這些:
![](http://static.javashuo.com/static/loading.gif)
開始的時候,通常須要new一條IloNumExpr類型的空表達式出來,而後慢慢去填充它:
IloNumExpr expr = this.cplex.numExpr();
建立空表達式能夠經過numExpr()函數哦:
![](http://static.javashuo.com/static/loading.gif)
在CPLEX的JavaAPI中呢,涉及到CPLEX對象的一些表達式,是不能直接經過Java自帶的+-*/進行運算的。須要經過CPLEX提供sum()、diff()、prod()函數進行加、減、乘的操做。
那爲何沒有除呢?由於除是能夠經過轉換變成乘的!好比 能夠轉換成 ,沒毛病吧~
其中,sum()、diff()、prod()這些函數在CPLEX的庫中重載了不少版本,也就是說你sum(IloNumExpr, double)、sum(IloNumExpr, IloNumExpr)、sum(double, IloNumExpr)都是能夠識別的,那麼我就貼一個出來給你們看看就好啦:
![](http://static.javashuo.com/static/loading.gif)
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()分別用以添加最小化目標和最大化目標。根據本身的須要調用就好,固然這兩個函數也是有不少重載的版本,我就放一個最經常使用的給你們看看吧:
![](http://static.javashuo.com/static/loading.gif)
參數就是一個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)
都是能夠的。我放一個官方的介紹吧:
![](http://static.javashuo.com/static/loading.gif)
如今,咱們來看看一個example,演示下如何添加約束(3.5):
![](http://static.javashuo.com/static/loading.gif)
首先,從哪着手呢?從右邊開始:對於任意的 ,任意的 ,都要知足左邊那個等式。兩個循環是沒跑了,而後在循環的最內層,把相關表達式給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是創建在一個清晰明瞭的模型基礎之上的,若是你一開始的模型就設置得亂七八糟,這個過程寫起來是很痛苦的。畢竟你要邊寫代碼邊修正模型,極可能寫着寫着就變成了一坨。。。
4、CPLEX求解
上面的模型創建完成之後,就能夠調用solve()函數進行求解了,若是返回true,那麼就找到了可行解(是的吧?我也不太清楚,能夠去查查)。不然就是不可行解。
求解完成之後,獲取一個變量的值能夠採用CPLEX的getValue()函數,參數是你new出來的決策變量。
不過求解獲得結果之後,是須要最好手動或者寫個函數驗算下,確保獲得的解知足了全部約束。以及獲得的目標值也是正確的。
總的來講,CPLEX已經爲咱們封裝好了不少東西,大部分只須要動動手指就能夠直接使用了。少部分可能須要查查庫什麼的,可是基本的時候已經很是簡單了。最後,貼上兩篇文章,你們可能會比較感興趣,小編也建議你們結合起來看,效果會更好哦:
乾貨|十分鐘快速掌握CPLEX求解VRPTW數學模型(附JAVA代碼及CPLEX安裝流程)
快點個贊關注咱們。獲取更多精彩內容吧~你們幫忙點個在看,讓更多小夥伴知道吧~
推薦閱讀:
乾貨 | 學習算法,你須要掌握這些編程基礎(包含JAVA和C++)
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
本文分享自微信公衆號 - 程序猿聲(ProgramDream)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。