《Zimpl 用户手册》中文翻译!轻量化的数学优化建模语言

前言

早在半年多前,我在查阅资料的时候偶然找到了 zimpl 这样一种轻量化的数学优化建模语言。我当时就觉得 zimpl 对于当下正在学习运筹优化方法的大学生来说应当是一种十分实用的工具。有下面这几方面原因:

  1. 相交于常用的 CPLEX、LINGO / LINDO 一类的商业软件,Zimpl 数学优化建模语言及其所属的 SCIP Optimization Suite 都是免费开源的。学生不需要费心去找破解版资源,也就不需要绞尽脑汁地和爷爷辈的老弱病残 IDE 斗智斗勇[1]
  2. Zimpl 语言的语法十分简单易用,不必费心就能很快地学会。相交于功能更加完善的求解器 API (如 gurobipy),zimpl 没有额外的学习成本。这是因为你不需要为此学习一门额外的编程语言;
  3. 初学运筹优化方法的时候,我们往往需要一种轻捷的手段,用以测试各种运筹模型的建模可行性。比如,如何知道你的数学公式有没有错误?如何确认求解器是否接受你所定义的约束形式?对此,zimpl 的语法与运筹模型的数学公式表述有着很强的关联性,例如 \(\forall \rightarrow \mathtt{for all}\) 这样的语法,都能有助于对运筹优化建模方法理解和学习。

但与此同时,中文互联网上对于 zimpl 的介绍少之又少,于是我便决定要提供一份 Zimpl 语言手册的中文译本。时隔半年,我的这份 37 页的《Zimpl 用户手册》中文翻译终于是和读者们见面了。我在这里写一篇博客,推广一下我自己的这个翻译项目,顺便跟读者们介绍一下 zimpl 这样一种数学优化建模语言。

[!note]

请访问 gitee.com/BOXonline_1396529/zimpl-doc.zh_CN 以获取最新的翻译版本。

关于手册翻译项目

关于翻译项目的许多详细说明,可以在项目的 README.md 文件中找到。我这里只是简单地说一下,翻译本手册的过程中我使用了 AI 翻译 + 人工校订的模式,对许多翻译内容都进行了细致的推敲斟酌。包括但不限于:

  1. 技术术语:查阅了有关的书籍,根据国内读者的用词习惯进行了翻译和勘误。在此基础之上,我统一了每个词汇在全文中的对应译文,在每个术语第一次出现时,用括号标记术语原文。
  2. 语言通顺性:为了读者们能够更快更好地阅读手册,我改善了中文的遣词造句。在保持与英语原文一致的同时,确保中文内容读起来通顺流畅。
  3. 脚注内容:对于本文手册内容从英文转换到中文的过程中可能产生歧义的各种内容,我添加了额外的脚注 (形如“译者注:xxx...”),确保读者能够正确理解。

本项目同时在 GiteeGitHub 平台更新。目前由于工作流限制,Gitee 平台的更新速度会比 GitHub 更快一些。因此,对于国内用户,这里建议您优先从 Gitee 平台获取文件。

关于 Zimpl 语言

Zimpl 语言简介

Zimpl 的全称叫做 Zuse Institue Mathematical Programming Language,即“祖萨研究院数学规划建模设计语言”,是德国 ZIB 研究所设计的一种用于数学优化领域的轻量级建模语言。按照官方手册中的解释:“Zimpl 是一种轻量化的特定领域语言 (little language),用于将问题的数学模型描述转译为线性或 (混合) 整数规划程序,并保存为 (希望是) 能被 LPMIP 的求解器求解的 .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 代码 (含注释)。

"""
The Description Of A TSP Programming Problem

This 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.py
Author: Githubonline1396529
License: MIT
Copyright 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 matrix
model.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 Constraints
def con_o_rule(model, i):
    return sum(model.x[i, j] for j in model.V if i != j) == 1


def con_d_rule(model, j):
    return sum(model.x[i, j] for i in model.V if i != j) == 1


model.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[i] - model.u[j] + model.N * model.x[i, j] <= model.N - 1


model.con_u = Constraint(model.V, model.V, rule=con_u_rule)

而相比之下,如下的代码所展示的是使用 Zimpl 编写的同样的一个 Zimpl 模型,从 data.dat 中读取城市名称和 X、Y 坐标数据 (data.dat 可以直接存储使用逗号“,”或者空白字符 Space / Tabs 的分隔符数据),这个实现只需要大约不到 50 行代码 (含注释)。

# Solving a TSP problem 
#
# Name: main.py
# Author: Githubonline1396529
# License: Copyright 2023 Githubonline1396529 
#
# See Also: README.md

### Load Data File ###
# Path to data file
param data := "./data/data.dat";

### Sets Definations ###
set V := { read data as "<1s>" comment "#" };
set E := { <i, j> in V * V with i != j };

### Patameters Definations ###
param N := card(V);
param s_node := # name of the first node (start node)
  read data as "1s" use 1 comment "#";

param px[V] := read data as "<1s> 2n" comment "#";
param py[V] := read data as "<1s> 3n" comment "#";

# Define the function of distance between two coordinates
defnumb dist(a, b) := sqrt((px[a] - px[b])^2 + (py[a] - py[b])^2);

param w[<i, j> in E] := dist(i, j);

### Variables Definations ###
var x[E] binary;
var u[V] real >= 0 <= N;

### Model Object ###
minimize distance : sum <i, j> in E: w[i, j] * x[i, j];

### Subject to Constricts ###
subto con_o: forall <v> in V do
  sum <v, j> in E : x[v, j] == 1;

subto con_d: forall <v> in V do
  sum <i, v> in E : x[i, v] == 1;

subto mtz_u: 
  forall <i, j> in E 
  with i != j and i != s_node 
              and j != s_node do 
  u[i] - u[j] + (N * x[i, j]) <= (N - 1);

与此同时,我们还可以注意到 Zimpl 的代码具有较好的数学直观性。上述模型中的目标函数如果是如下的形式:

\[\text{minimize}\ D^* = \sum_{(i, j) \in E} w_{i, j} \cdot x_{i, j} \]

那么,其对应的 \LaTeX 源码为:

\text{minimize}\ D^* = \sum_{i, j \in E} w_{i, j} \cdot x_{i, j}

而如果将其转化为 Zimpl 表达式,就是如下的形式。

minimize distance : sum <i, j> in E: w[i, j] * x[i, j];

可以注意到,Zimpl 的语法有意与其对应的数学形式保持一致,从某种程度上来说,甚至比 LaTeX 的源码的可读性还要好一些。这样的语法样式设计能够有助于使用者更快地编写模型的代码,同时快速检查自己的模型是否存在问题。

Zimpl 语言的使用

下载与安装

Zimpl 是 SCIP Optimization Suite 的组件之一,因此你在安装了 SCIP Optimization Suite 之后就可以直接使用 Zimpl 了。Zimpl 是命令行应用程序,没有图形交互。在命令行下使用 Zimpl 指令可以将 .zpl 结尾的 Zimpl 源码转化为其他求解器能够读取的文件格式。关于这些内容的详细说明,在中文手册里都有,我这里不再赘述了。

编辑器与环境方案

Zimpl 没有专门的编辑器,但是 Visual Studio Code 提供了一个用于编写 Zimpl 的插件,直接在 Visual Studio Code 的插件页面搜索然后下载安装即可。除此之外,Vim 也有一个 Zimpl 的高亮插件,也可以按照个人习惯直接安装使用。

除此之外,前几天我为了在命令行下的 Nano 编辑器中编写 Zimpl,特此写了一个 用于 Nano 的语法高亮方案,也可以点击链接直接下载使用,或者,这个项目我在 GitHub 上面也传了一份,读者们也可以访问 该项目的 Github 地址[2]

耻辱柱

本来不想加这个章节的,奈何真是太难崩了,实在是忍不住。当然,我能理解现在的 CSDN 就是这样的氛围,由于平台在中间有巨额的抽成,如果不靠上传垃圾资源来引流,仅仅通过有限的高质量内容赚来的 C 豆是不够自己下载资源的。所以我把这些挂在这里并不是针对某个用户。这些应当是我们所有人的耻辱。

总结

对于这个 Zimpl 用户指南的翻译项目,主要还是希望能够帮助到一些运筹优化方法的初学者。除此之外,如果在手册的译本当中发现存在有任何问题,也欢迎随时与我联系。感谢我的朋友 白色竹鼠,他为本项目的第七章提供了一份准确的 AI 通篇翻译的对照版本,大大加快了我的翻译进度。


  1. 没错,说的就是你 LINGO 12.0 破解版,我可以接受代码关键字渲染出错,但我不接受你把一个关键字的前半部分和后半部分渲染成两种不同的颜色! ↩︎

  2. 该项目目前也只是刚刚达到了能用的程度,我实在是不太会用正则表达式,所以如果读者们发现这个高亮方案存在什么问题的话,欢迎给我提交 Issue,但我不一定能修好。 ↩︎

posted @ 2025-06-24 13:35  多玩我的世界盒子  阅读(79)  评论(0)    收藏  举报