技術背景
線性規划是常見的問題求解形式,可以直接跟實際問題進行對接,包括目標函數的建模和各種約束條件的限制等,最后對參數進行各種變更,以找到滿足約束條件情況下可以達到的最優解。Cplex是一個由IBM主推的線性規划求解器,可以通過調用cplex的接口,直接對規定形式的線性規划的配置文件.lp
文件進行求解。這里我們介紹一下,基於docker來調用cplex的python接口,對線性規划問題進行求解。
基於Docker部署Cplex環境
由於cplex依賴於python3.7版本,而我們本地使用的python版本是python3.8,因此我們考慮使用docker容器來制作一個python37+cplex的容器鏡像,用於計算線性規划的問題。關於docker容器的使用,在另外3篇博客(博客1,博客2,博客3)。首先我們在dockerhub上面找一個python37的鏡像:
這里我們習慣性的選擇星星最高的那個,然后下載到本地:
[dechin-root cplex]# docker pull rackspacedot/python37
Using default tag: latest
latest: Pulling from rackspacedot/python37
Digest: sha256:5ae238bd5d6b06af739ac1b2666111955966d563cb6aea8b366fb446425eb299
Status: Downloaded newer image for rackspacedot/python37:latest
docker.io/rackspacedot/python37:latest
下載完成后,可以在本地的鏡像倉庫中看到這個新的鏡像:
[dechin-root cplex]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
rackspacedot/python37 latest ab7083b6c7c4 3 months ago 1.02GB
下載完成后我們可以進入這個鏡像,用pip
安裝一個最新的cplex。其實cplex的安裝還是非常簡單的,只是對於python的版本有要求而已。
[dechin-root cplex]# docker run -it rackspacedot/python37 /bin/bash
root@c766ed62d149:/# python3 -m pip install cplex
Collecting cplex
Downloading cplex-20.1.0.1-cp37-cp37m-manylinux1_x86_64.whl (30.9 MB)
|████████████████████████████████| 30.9 MB 347 kB/s
Installing collected packages: cplex
Successfully installed cplex-20.1.0.1
安裝完成后,我們可以進入python3的命令行界面,測試一下cplex的安裝情況:
root@c766ed62d149:/# python3
Python 3.7.9 (default, Nov 18 2020, 14:29:12)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cplex
>>> exit()
這里如果沒有報錯,就表示安裝成功了。那么最后,我們需要把剛才對容器鏡像的修改永久的保留下來,我們先用ps
查看剛才的修改被保存到哪里:
[dechin-root cplex]# docker ps -n 2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c766ed62d149 rackspacedot/python37 "/bin/bash" 2 minutes ago Exited (0) 6 seconds ago xenodochial_ardinghelli
af037db88540 cplex "/bin/bash" 48 minutes ago Up 48 minutes magical_cori
在過去的2條記錄中我們發現對容器鏡像的修改被保存到c766
開頭的容器中,這時我們可以直接對這個編號的容器進行提交保存:
[dechin-root cplex]# docker commit c766 cplex-py37
sha256:34e2729697010b1320c2f7dbfd1fc45004e9ffae6a1d26ffb8748b5627cb2224
如果出現以上的反饋,就表示我們成功的把剛才下載cplex的這一修改永久的保存進cplex-py37
這個新容器中,這樣就可以在本地的容器倉庫里面看到這個新的容器:
[dechin-root cplex]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cplex-py37 latest 34e272969701 About a minute ago 1.15GB
到這里,我們使用docker部署的cplex求解器的環境就已經完成了,下一步我們用真實的線性規划的問題來進行測試。
線性規划問題求解
上面的章節主要是為了展示基於docker的cplex環境部署,用同樣的方法我們此前已經制作好了一個名為cplex
的容器鏡像,這里我們直接用來測試。容器的拉起方法,要綁定本地存放有線性規划問題定義的文件所在的目錄:
[dechin-root cplex]# docker run -it -v /home/dechin/projects/2021-quantum/cplex/:/home/ cplex /bin/bash
線性規划問題定義
Cplex可以識別lp
格式的文件,這里我們展示一個測試用例來說明這個線性規划的問題是如何定義的:
[dechin-root cplex]# cat test.lp
Maximize
obj: 2 x1 + 3 x2 + 4 x3
Subject To
c1: 3 x1 + 4 x2 + 5 x3 <= 8
Bounds
0 <= x1 <= 1
0 <= x2 <= 1
0 <= x3 <= 1
Binary
x1 x2 x3
End
在這個問題中,我們的目標是優化這樣的一個函數:
就是找這么一個函數的最大值,這些參數\(x_1,x_2,x_3\)都是二元變量,即\(x\in\{0,1\}\),而且需要滿足給定的約束條件:
問題解析與代碼求解
其實這是一個典型的單背包問題的案例:給定一個承重量為8的背包,需要裝3個物品\(\{x_1,x_2,x_3\}\)中的某幾個拿去賣。這三個物品的重量分別是\(\{3,4,5\}\),因此我們沒辦法將所有的物品一次性裝到包里面,因為這會超過背包的承重量。而這3個物品的收益分別是\(\{2,3,4\}\),對於這個問題來說,就是要最大化這個收益。比如說,我們只裝\(x_1,x_2\)兩個物品,也就是\(x_1=1,x_2=1,x_3=0\),那么總重量是7,並沒有超過背包的承重量,而總的收益是5。這是一組可行解,但不一定是最優解,接下來我們看看cplex是否有可能找到這個問題的最優解。
root@af037db88540:/home# python3
Python 3.7.9 (default, Nov 18 2020, 14:29:12)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cplex
>>> lp = cplex.Cplex() # 初始化對象
>>> lp.read('test.lp') # 讀取線性規划文件
>>> lp.solve() # 求解
Version identifier: 12.10.0.0 | 2019-11-27 | 843d4de
CPXPARAM_Read_DataCheck 1
Found incumbent of value 0.000000 after 0.00 sec. (0.00 ticks)
Tried aggregator 1 time.
MIP Presolve eliminated 1 rows and 3 columns.
MIP Presolve modified 3 coefficients.
All rows and columns eliminated.
Presolve time = 0.00 sec. (0.00 ticks)
Root node processing (before b&c):
Real time = 0.00 sec. (0.00 ticks)
Parallel b&c, 8 threads:
Real time = 0.00 sec. (0.00 ticks)
Sync time (average) = 0.00 sec.
Wait time (average) = 0.00 sec.
------------
Total (root+branch&cut) = 0.00 sec. (0.00 ticks)
>>> lp.solution.get_objective_value() # 獲取求解的目標函數值
6.0
>>> lp.solution.get_values() # 獲取最終的參數值
[1.0, 0.0, 1.0]
這個示例中我們將每一步的含義都直接注釋在代碼中,我們直接調用cplex的接口,寫好lp
文件,就可以很輕松的進行求解了。得到的最終的解是\(\{1,0,1\}\),也就是總重量為8,未超過承重量,而總收益為6,高於我們剛才手工找到的可行解的收益值。同時這也是這個問題的唯一最優解,這一點其實我們可以手工驗證。
總結概要
在這篇文章中我們介紹了如何使用docker去搭建一個cplex線性規划求解器的編程環境,制作完docker容器,我們也展示了如何寫一個線性規划問題定義的文件,並使用cplex對給定一個背包問題的線性規划(實際上是一個二元規划問題)文件進行求解。
版權聲明
本文首發鏈接為:https://www.cnblogs.com/dechinphy/p/cplex.html
作者ID:DechinPhy
更多原著文章請參考:https://www.cnblogs.com/dechinphy/