在docker容器中使用cplex-python37
技术背景
线性规划是常见的问题求解形式,可以直接跟实际问题进行对接,包括目标函数的建模和各种约束条件的限制等,最后对参数进行各种变更,以找到满足约束条件情况下可以达到的最优解。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/