用CPLEX寫個數學模型就這么難?

一、前言

小編有個小伙伴,隔三差五就過來跟我說:這個模型CPLEX怎么寫呢?我說我不是給你講過好多次?他說CPLEX太復雜了,俺沒學過學不會呢。

Similarly,遇到這個問題的不止小編這個小伙伴。很多剛入行的小伙伴都表示CPLEX對初學者來說并不是很友好,就連學習資料都不知道去哪里看,不像Excel或者Word,百度一下出來好多資料。

其實吧,這玩意兒并沒有大家想的那么難,尤其是簡單使用CPLEX求解一個模型的話,用來用去都是那幾個函數而已。下面小編來給大家好好理一下,看完相信你也能用CPLEX跑一下論文上的模型啦。

當然啦,為了方便小編還是選擇大家熟悉的Java平臺,用Python也是可以的,處理數據可能還更方便。但是我們一般都是用Java寫的算法,因此就統一平臺啦。

我們今天以一個最經典的VRPTW arc-flow model為例,手把手給大家演示下,CPLEX其實并不是那么的難用。

image

二、模型集合定義

運行一個模型之前,首先要定義模型中用到的一些參數和集合,如果這些都沒有,是無從談起的。因此沒有的話第一步是要先生成這些數據哦。

2.1 讀取數據

首先,你需要在程序中定義相關的變量(通常的做法是寫一個instance的類,把算例的數據讀進來,放到成員變量上。)比如:

image

至于你怎么定義怎么寫都無所謂啦,反正你知道這些數據對應模型的哪些參數就可以啦。

2.2 定義集合

其實小編發現,大家之所以覺得寫模型難,還有一個原因就是自己建模的時候純粹瞎搞。很多集合啊,參數啊,范圍啊都沒有想清楚,到寫代碼的時候就各種凌亂了。。。

好了回到我們的正題,剛剛讀入了算例。接下來我們需要定義模型中需要用到的集合,這些集合是哪些集合呢?就是我指出來的這些:

image

然后你需要在程序中把這些集合給定義好了,然后把相應的數據填充進去,比如\mathscr{N}為所有節點的集合,\mathscr{V}為所有車輛集合,那么就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中,一個決策變量是一個對象來的,首先我們需要定義決策變量的數組,并分配數組的空間,比如x_{ijk}的:

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了一個數值變量的對象出來,我這里貼上官方的解釋好啦:

image

如果你有不同類型的變量,指定下第三個參數IloNumVarType就好啦:

image

模型中另一個決策變量s_{ik}類似,我就不寫啦。

3.2 CPLEX的表達式

首先來看一個概念:表達式是什么呢?吶,類似于我圈出來的這些:

image

開始的時候,一般需要new一條IloNumExpr類型的空表達式出來,然后慢慢去填充它:

IloNumExpr expr = this.cplex.numExpr();

創建空表達式可以通過numExpr()函數哦:

image

在CPLEX的JavaAPI中呢,涉及到CPLEX對象的一些表達式,是不能直接通過Java自帶的+-*/進行運算的。需要通過CPLEX提供sum()、diff()、prod()函數進行加、減、乘的操作。

那為什么沒有除呢?因為除是可以通過轉換變成乘的!比如a/b=c可以轉換成a = bc,沒毛病吧~

其中,sum()、diff()、prod()這些函數在CPLEX的庫中重載了很多版本,也就是說你sum(IloNumExpr, double)、sum(IloNumExpr, IloNumExpr)、sum(double, IloNumExpr)都是可以識別的,那么我就貼一個出來給大家看看就好啦:

image

sum()、diff()也是類似的,不過需要注意的是diff()時要注意區分是誰減去誰哦。現在表達式有了,我們來看看怎樣通過sum()、diff()、prod()這些函數,實現模型中的式子。以目標那個式子為例:

\sum_{k \in K}\sum_{i \in \mathscr{N}} \sum_{j \in \mathscr{N}} c_{ij}x_{ijk}

有三個求和符號,那么肯定得來三個循環啦:

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()分別用以添加最小化目標和最大化目標。根據自己的需要調用就好,當然這兩個函數也是有很多重載的版本,我就放一個最常用的給大家看看吧:

image

參數就是一個IloNumExpr類型的表達式,比如可以直接把上面的objExpr給add進來,是不是很簡單呢!

對于添加約束,CPLEX也提供了三個函數,我這里寫成一個表格方便大家查看:

method 作用
addGe(a, b) 添加約束a \ge b
addLe(a, b) 添加約束a \le b
addEq(a, b) 添加約束a = b

根據a,b類型的不同,這幾個函數同樣重載了很多版本,你寫addGe(IloNumExpr, double)、addGe(IloNumExpr, IloNumExpr)、addGe(double, IloNumExpr)都是可以的。我放一個官方的介紹吧:

image

現在,我們來看看一個example,演示下如何添加約束(3.5):

image

首先,從哪著手呢?從右邊開始:對于任意的h \in C,任意的k \in V,都要滿足坐標那個等式。兩個循環是沒跑了,然后在循環的最內層,把相關表達式給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安裝流程)

快點個贊關注我們。獲取更多精彩內容吧~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容