建立ortools的Dockerfile
阿新 • • 發佈:2021-03-30
# 技術背景
基於已有的Docker容器映象,去建立一個本地的映象,有兩種方法:一種是在之前的[部落格](https://www.cnblogs.com/dechinphy/p/cplex.html#)中提到過的,使用`docker commit`的方案,也就是先進去基礎系統映象內部完成所需的修改,然後commit到一個新的容器內部;還有另外一種也非常常用的方法,就是寫一個Dockerfile,在本文中會作簡單介紹。
另外我們在上一篇[部落格](https://www.cnblogs.com/dechinphy/p/cplex.html#)中介紹瞭如何部署與使用IBM主導的Cplex線性規劃求解器的一些基本使用方法。在本文中我們會介紹另外一套由Google主導的開源線性規劃求解器ortools的部署與基本使用方法。
# Dockerfile的建立
對於簡單的場景而言,尤其是別人已經把基礎容器映象做的比較完善的情況下,使得我們減少了大量的工作量。比如這裡我們直接使用一個別人做好的python3.7的基礎映象,而獲得該映象的方法在上一篇[部落格](https://www.cnblogs.com/dechinphy/p/cplex.html#)中也作了介紹。那麼我們在dockerfile裡面只需要安裝好我們所需要的ortools的python包即可:
```bash
[dechin-root ortools]# cat Dockerfile
FROM rackspacedot/python37
RUN python3 -m pip install pip --upgrade \
&& python3 -m pip install ortools
```
這裡`FROM`後面所跟隨的基礎映象是必須在本地所具備的,可以在`docker images`裡面看到的才行。最好也在本地通過執行`docker run your_iamge`來測試一下這個容器映象是否正常工作,因為有些容器映象必須要跟隨版本號才能正常使用。在上述dockerfile中我們先對`pip`管理工具做了一個升級,然後才安裝`ortools`工具包。有一個需要注意的點是,我們也可以選擇使用多次的RUN來製作一個dockerfile,但是這會導致添加了多層的映象,因此最好我們是可以用命令拼接的方式一次性完成所有的任務,這樣只會增加一層映象(截圖來自於參考連結2):
![](https://img2020.cnblogs.com/blog/2277440/202103/2277440-20210329190523824-1981099693.png)
按照上述流程編寫好dockerfile之後,我們就可以使用`docker build`來構建一個本地的容器映象:
```bash
[dechin-root ortools]# docker build -t dechin/ortools:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM rackspacedot/python37
---> ab7083b6c7c4
Step 2/2 : RUN python3 -m pip install pip --upgrade && python3 -m pip install ortools
---> Running in ff6b1971bdc9
Requirement already satisfied: pip in /usr/local/lib/python3.7/site-packages (20.3.1)
Collecting pip
Downloading pip-21.0.1-py3-none-any.whl (1.5 MB)
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 20.3.1
Uninstalling pip-20.3.1:
Successfully uninstalled pip-20.3.1
Successfully installed pip-21.0.1
Collecting ortools
Downloading ortools-8.2.8710-cp37-cp37m-manylinux1_x86_64.whl (14.2 MB)
Collecting protobuf>=3.14.0
Downloading protobuf-3.15.6-cp37-cp37m-manylinux1_x86_64.whl (1.0 MB)
Collecting absl-py>=0.11
Downloading absl_py-0.12.0-py3-none-any.whl (129 kB)
Requirement already satisfied: six in /usr/local/lib/python3.7/site-packages (from absl-py>=0.11->ortools) (1.15.0)
Installing collected packages: protobuf, absl-py, ortools
Successfully installed absl-py-0.12.0 ortools-8.2.8710 protobuf-3.15.6
Removing intermediate container ff6b1971bdc9
---> b9ff988385a5
Successfully built b9ff988385a5
Successfully tagged dechin/ortools:v1
```
我們可以看到2條dockerfile的指令的執行結果都在螢幕上輸出,顯示是成功安裝了的。在執行完build之後,我們可以在本地的images倉庫裡面看到這個新的容器映象:
```bash
[dechin-root ortools]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dechin/ortools v1 b9ff988385a5 35 seconds ago 1.09GB
```
我們也可以測試一下這個容器映象的功能是否正常:
```bash
[dechin-root ortools]# docker run -it dechin/ortools:v1 /bin/bash
root@198255eacb30:/# 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 ortools
>>>
```
通過執行一個簡單的python指令我們可以看到`ortools`這個工具已經被成功的部署在容器映象內,在下一個章節中我們會介紹如何使用`ortools`來解決一個實際問題。當然,上述測試方案也有一個更加簡單的方法,使用`-c`標籤來執行程式碼:
```bash
[dechin-root ortools]# docker run dechin/ortools:v1 python3 -c "import ortools;print('hello')"
hello
```
這裡再補充介紹一下在docker中如何刪除一個容器映象的方法,那就是使用`rmi`和`rm`指令。這兩個指令也容易區分,如果是在`docker images`指令下找到的容器映象,那就用`rmi`來進行刪除,如果是在`docker ps`裡面看到的容器,那就用`rm`來刪除,以下是兩個示例:
```bash
[dechin-root ortools]# docker rmi cplex-py37
Error response from daemon: conflict: unable to remove repository reference "cplex-py37" (must force) - container 7ce9dbee3e55 is using its referenced image 34e272969701
[dechin-root ortools]# docker rmi -f cplex-py37
Untagged: cplex-py37:latest
Deleted: sha256:34e2729697010b1320c2f7dbfd1fc45004e9ffae6a1d26ffb8748b5627cb2224
```
上面這個用例是表示我們在`docker images`中有一個名為`cplex-py37`的容器映象,其實也是在上一篇[部落格](https://www.cnblogs.com/dechinphy/p/cplex.html#)中製作的產物。假如我們需要刪除這個映象,就用刪除的`rmi`指令。當我們第一次嘗試刪除的時候,我們收到一個提示,關於一些衝突的提示。假如我們認定了這個衝突並不影響我們的操作,那麼我們可以強制刪除,也就是加上`-f`指令。
另外也展示一下`rm`指令的使用場景。假如我們使用`docker ps -n 5`檢視過去執行的最近5條指令,並且需要刪除第一條映象id為`2df3`的容器:
```bash
[dechin-root ortools]# docker ps -n 5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2df3fcc803e0 34e272969701 "/bin/bash" 20 hours ago Exited (0) 20 hours ago bold_colden
c766ed62d149 rackspacedot/python37 "/bin/bash" 20 hours ago Exited (0) 20 hours ago xenodochial_ardinghelli
af037db88540 cplex "/bin/bash" 21 hours ago Exited (0) 12 minutes ago magical_cori
e8c49c211039 cplex "/bin/bash" 21 hours ago Exited (0) 21 hours ago gracious_babbage
1e053a1b6330 cplex "/bin/bash" 21 hours ago Exited (0) 21 hours ago suspicious_ride
[dechin-root ortools]# docker rm 2df3
2df3
[dechin-root ortools]# docker ps -n 5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c766ed62d149 rackspacedot/python37 "/bin/bash" 20 hours ago Exited (0) 20 hours ago xenodochial_ardinghelli
af037db88540 cplex "/bin/bash" 21 hours ago Exited (0) 12 minutes ago magical_cori
e8c49c211039 cplex "/bin/bash" 21 hours ago Exited (0) 21 hours ago gracious_babbage
1e053a1b6330 cplex "/bin/bash" 21 hours ago Exited (0) 21 hours ago suspicious_ride
de7f22ac259b cplex "python3 -m pip inst…" 21 hours ago Exited (0) 21 hours ago hardcore_meitner
```
可以看到用`rm`刪除之後就不會在最新的結果查詢中出現這個容器,這也方便我們釋放不需要的容器資源給本地環境。
# ortools案例
這裡我們還是使用上一篇[部落格](https://www.cnblogs.com/dechinphy/p/cplex.html#)中所提到的`單揹包問題(Knapsack Problem)`來進行測試。相關問題的定義如下:
![](https://img2020.cnblogs.com/blog/2277440/202103/2277440-20210329193208718-1923918071.png)
當然在ortools的案例中我們不需要寫lp檔案,只是借用這個lp檔案來展示一下我們的約束條件和目標函式。這個問題的含義也在上一篇部落格中介紹過了,這裡我們直接截圖引用:
![](https://img2020.cnblogs.com/blog/2277440/202103/2277440-20210329193331977-1295939402.png)
## ortools求解器的使用
在瞭解清楚問題的背景之後,現在我們就可以開始寫測試程式碼了,首先我們也是從進入docker容器開始,然後出於方便我們直接在python指令中執行相關的測試(這裡的測試程式碼我們參考了官方文件,也就是本文的參考連結1):
```python
[dechin-root ortools]# docker run -it dechin/ortools:v1 /bin/bash
root@3882b83959c8:/# 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.
>>> from ortools.linear_solver import pywraplp
>>> solver = pywraplp.Solver.CreateSolver('SCIP') # 這裡使用了第三方後端SCIP
>>> x1 = solver.IntVar(0.0, 1.0, 'x1')
>>> x2 = solver.IntVar(0.0, 1.0, 'x2')
>>> x3 = solver.IntVar(0.0, 1.0, 'x3')
>>> print ('Number of variables = ', solver.NumVariables()) # 引數數量
Number of variables = 3
>>> solver.Add(3 * x1 + 4 * x2 + 5 * x3 <= 8)
>
>>> solver.Maximize(2 * x1 + 3 * x2 + 4 * x3)
>>> status = solver.Solve()
>>> print('Number of constraints =', solver.NumConstraints()) # 約束條件數量
Number of constraints = 1
>>> print('Objective value =', solver.Objective().Value()) # 最終解的函式值
Objective value = 6.0
>>> print('x1 =', x1.solution_value())
x1 = 1.0
>>> print('x2 =', x2.solution_value())
x2 = 0.0
>>> print('x3 =', x3.solution_value())
x3 = 1.0
>>> print (status == pywraplp.Solver.OPTIMAL) # 是否最優解?
True
```
在這個案例中我們使用了一個第三方的求解器後端來進行計算,叫`SCIP`。我們得到的最終解已經達到了最優解,這個我們在上一篇部落格中也分析過了。到這裡為止,我們就成功的使用ortools提供的框架求解了一個實際的揹包問題。
# 總結概要
在本地構建基於Docker的程式設計環境是一個相容性和可用性非常強的解決方案,這裡我們介紹了一個使用Dockerfile來構建Docker容器映象的簡單例項。同時也用谷歌所主導的開源線性規劃求解器ortools來測試這個容器化的程式設計環境解決方案,最終我們用ortools成功的求解了一個單揹包問題,並且跟前面一篇部落格中所介紹的IBM主導的cplex一樣都得到了問題的最優解。
# 版權宣告
本文首發連結為:https://www.cnblogs.com/dechinphy/p/ortools.html
作者ID:DechinPhy
更多原著文章請參考:https://www.cnblogs.com/dechinphy/
# 參考連結
1. https://developers.google.cn/optimization/mip/integer_opt?hl=zh-cn
2. https://www.runoob.com/docker/docker-dockerfile.html
3. https://www.cnblogs.com/dechinphy/p/cpl