目录
- 前言
- 关于手册翻译项目
- 关于 Zimpl 语言
- Zimpl 语言简介
- Zimpl 语言的优势
- Zimpl 语言的使用
- 总结
前言
早在半年多前,我在查阅资料的时候偶然找到了 zimpl 这样一种轻量化的数学优化建模语言。我当时就觉得 zimpl 对于当下正在学习运筹优化方法的大学生来说应当是一种十分实用的工具。有下面这几方面原因:
- 相交于常用的 CPLEX、LINGO / LINDO 一类的商业软件,Zimpl 数学优化建模语言及其所属的 SCIP Optimization Suite 都是免费开源的。学生不需要费心去找破解版资源,也就不需要绞尽脑汁地和爷爷辈的老弱病残 IDE 斗智斗勇[1];
- Zimpl 语言的语法十分简单易用,不必费心就能很快地学会。相交于功能更加完善的求解器 API (如 gurobipy),zimpl 没有额外的学习成本。这是因为你不需要为此学习一门额外的编程语言;
- 初学运筹优化方法的时候,我们往往需要一种轻捷的手段,用以测试各种运筹模型的建模可行性。比如,如何知道你的数学公式有没有错误?如何确认求解器是否接受你所定义的约束形式?对此,zimpl 的语法与运筹模型的数学公式表述有着很强的关联性,例如 \(\forall \rightarrow \mathtt{for all}\) 这样的语法,都能有助于对运筹优化建模方法理解和学习。
但与此同时,中文互联网上对于 zimpl 的介绍少之又少,于是我便决定要提供一份 Zimpl 语言手册的中文译本。时隔半年,我的这份 37 页的《Zimpl 用户手册》中文翻译终于是和读者们见面了。我在这里写一篇博客,推广一下我自己的这个翻译项目,顺便跟读者们介绍一下 zimpl 这样一种数学优化建模语言。
[!note]
请访问 gitee.com/BOXonline_1396529/zimpl-doc.zh_CN 以获取最新的翻译版本。
关于手册翻译项目
关于翻译项目的许多详细说明,可以在项目的 README.md 文件中找到。我这里只是简单地说一下,翻译本手册的过程中我使用了 AI 翻译 + 人工校订的模式,对许多翻译内容都进行了细致的推敲斟酌。包括但不限于:
- 技术术语:查阅了有关的书籍,根据国内读者的用词习惯进行了翻译和勘误。在此基础之上,我统一了每个词汇在全文中的对应译文,在每个术语第一次出现时,用括号标记术语原文。
- 语言通顺性:为了读者们能够更快更好地阅读手册,我改善了中文的遣词造句。在保持与英语原文一致的同时,确保中文内容读起来通顺流畅。
- 脚注内容:对于本文手册内容从英文转换到中文的过程中可能产生歧义的各种内容,我添加了额外的脚注 (形如“译者注:xxx...”),确保读者能够正确理解。
本项目同时在 Gitee 和 GitHub 平台更新。目前由于工作流限制,Gitee 平台的更新速度会比 GitHub 更快一些。因此,对于国内用户,这里建议您优先从 Gitee 平台获取文件。
关于 Zimpl 语言
Zimpl 语言简介
Zimpl 的全称叫做 Zuse Institue Mathematical Programming Language,即“祖萨研究院数学规划建模设计语言”,是德国 ZIB 研究所设计的一种用于数学优化领域的轻量级建模语言。按照官方手册中的解释:“Zimpl 是一种轻量化的特定领域语言 (little language),用于将问题的数学模型描述转译为线性或 (混合) 整数规划程序,并保存为 (希望是) 能被 LP 或 MIP 的求解器求解的 .lp 或 .mps 文件格式。”
[!note]
关于求解器与数学优化建模语言的概念,请参见我的往期博客文章 一篇文章给你讲清楚运筹优化到底怎么学!基于 SCIP Optimization Suite 的运筹优化入坑教程。
读者们可以将 Zimpl 语言看作是 AMPL 的一个开源平替。虽然不像 Zimpl 那么完善,但仍具有多方面的优势。
Zimpl 语言的优势
跨平台性能
不同于 CPLEX 和 LINGO 这类服务于特定求解器的编程规划语言,zimpl 可以被轻易地导出为 LP 或者 MPS 这样的通用线性规划格式,不仅 SCIP 能用,大多数的求解器都能吃,因此 zimpl 实际上具有一定的跨平台性。
语法优势
作为一种轻量化的数学优化建模语言,Zimpl 的语法具有简洁性。关于这个问题,我们以如下所示的这个使用 MTZ 作为消除子回路约束的 TSP 模型为例:
\[\begin{array}{ll}\displaystyle \min f = \sum_{i=1}^n \sum_{j=1}^n d_{i, j} \times x_{i, j}, &\quad \forall (i, j) \in E, i \ne j \\\displaystyle \sum_{i = 1}^n x_{ij} = 1, &\quad \forall i \in V \\\displaystyle \sum_{j = 1}^n x_{ji} = 1, &\quad \forall j \in V \\\displaystyle u_i - u_j + n \cdot x_{ij} \leq n - 1,&\quad \forall (i, j) \in E, i \ne j, i \ne 1, j \ne 1\end{array}\]
对于编程语言 API 或者像是 Pyomo 这种基于现有编程语言所构建的规划建模语言来说,在创建模型和为模型添加约束和规划目标时,往往需要调用有关的类或者方法,而数学公式的部分往往是以参数的方式输入的。这就导致了求解器 API 作为一种数学规划建模语言,其语法的直观性较差、内容冗余较多。下面展示的是使用 Pyomo 编写的一个 TSP 模型的源码,从 params.py 中读取一个 numpy.array() 矩阵 distance_matrix 作为模型参数。这个实现使用了大约 90 多行 Python 代码 (含注释)。
[code]"""The Description Of A TSP Programming ProblemThis script the math description of a TSP problem in Pyomo, aimed to solve it with SCIP. See README.md for the math description of this model.This model uses Miller-Tucker-Zemlin (MTZ) as a form of subtour elimination constraints (SECs).Examples--------You can use the following commands to run model optimize:>>> from pyomo.opt import SolverFactory>>> from model import model>>> opt = SolverFactory("scip")>>> results = opt.solve(model)SCIP can be replaced with the name of any solver you want to use.About-----Name: model_MTZ.pyAuthor: Githubonline1396529License: MITCopyright 2023 Githubonline1396529 """### Initial Pyomo Environment ###from pyomo.environ import *### Load Data from data_process.py ###from params import distance_matrix### Instantiate a model object ###model = ConcreteModel()### Initialize Paramaters #### N is the size of the matrixmodel.N = len(distance_matrix)### Range Sets #### model.I = RangeSet(1, model.N - 1)# model.J = RangeSet(2, model.N)model.V = RangeSet(1, model.N)# Notice it's Python's in-built 'range()' here, which requires an extra `+1`model.E = Set( initialize=[(i, j) for i in model.V for j in model.V if i != j])### Variables ###model.x = Var(model.E, domain=Binary)model.u = Var(model.V, domain=NonNegativeReals)### The Programming Object ###model.obj = Objective( expr=( sum( model.x[i, j] * distance_matrix[i - 1][j - 1] for i, j in model.E ) ), sense=minimize,)### Model Constraints #### The OD Constraintsdef con_o_rule(model, i): return sum(model.x[i, j] for j in model.V if i != j) == 1def con_d_rule(model, j): return sum(model.x[i, j] for i in model.V if i != j) == 1model.con_x = Constraint(model.V, rule=con_o_rule)model.con_y = Constraint(model.V, rule=con_d_rule)# Miller-Tucker-Zemlin (MTZ)def con_u_rule(model, i, j): if i == j or i == 1 or j == 1: return Constraint.Skip # Skip the current Constraint return model.u - model.u[j] + model.N * model.x[i, j] = 0 |