GLPK/脚本和 MathProg
值不能作为命令行参数传递给 glpsol。相反,可以使用脚本将命令行参数写入数据文件。该数据文件可以与模型一起加载。
以下 Bash 脚本 test.sh 获取第一个命令行参数并将其写入数据文件 test.dat,然后为模型 test.mod 和数据文件 test.dat 调用 glpsol
#!/bin/sh
echo data\; > test.dat
echo param p := \'$1\'\; >> test.dat
echo end\; >> test.dat
glpsol -m test.mod -d test.dat
您可以使用以下模型文件 test.mod 测试脚本
param p, symbolic; printf "%s\n", p; end;
通过执行
/test.sh 'Hello world'
创建的数据文件 test.dat 将包含以下行
data; param p := 'Hello world'; end;
为了简单和高效,MathProg 语言不提供 控制结构(此限制不适用于使用 GLPK API 进行的 编译语言 编程)。这种缺乏控制结构阻止了在 MathProg 中直接实现 参数研究——在这种情况下,人们希望对一系列 参数 值执行简单的 循环,其中 MathProg 的求解语句嵌套在其中。参数研究与敏感度分析相同。
一种解决方法是使用脚本语言来 生成和运行 许多 GLPK 模型实例。更复杂的方法是使用 关系数据库 来存储中间解决方案。
AWK 是一种成熟的脚本语言,具有类似 C 的语法。大多数 Linux 发行版默认情况下都包含 GNU AWK,否则请安装gawk包。 Windows 用户可以从 gnuwin32.sourceforge.net/packages/gawk.htm 获取 GNU AWK 二进制文件。
AWK 使得创建 GMPL 数据文件然后对这些新创建的文件调用 glpsol 成为可能。这意味着 AWK 是进行与 GMPL 相关的参数研究的不错选择。通常最好使用一个易失性数据文件来存放您扫描的参数,以及另一个稳定数据文件来存放模型数据的其余部分(glpsol 支持多个数据文件)。
以下基本示例作为开发您自己的参数扫描脚本的存根。此示例反复将参数iter写入易失性数据文件test.dat然后调用 glpsol 来显示此参数的值。此脚本还在开始时生成模型文件test.mod但通常您的模型文件会预先存在。 Windows 用户需要将 Linux 删除 命令 rm --force 替换为 del。
# AWK script
# provides a starting point for developing custom parameter scanning scripts
BEGIN {
modfile = "test.mod";
datfile = "test.dat";
system("rm --force " modfile); # Linux remove call
printf ("Writing model file\n");
printf ("# model file\n") > modfile;
printf ("param iter;\n") > modfile;
printf ("solve;\n") > modfile;
printf ("display iter;\n") > modfile;
printf ("end;\n") > modfile;
close(modfile);
for (i = 1; i <= 5; i++) {
system("rm --force " datfile); # Linux remove call
printf("\n\nIteration %i\n", i);
printf ("Writing data file\n");
printf("# data file %i\n", i) > datfile;
printf("data;\n") > datfile;
printf("param iter := %i;\n", i) > datfile;
printf("end;\n") > datfile;
close(datfile);
system("glpsol --model " modfile " --data " datfile);
}
exit;
}
将此脚本保存为文本文件scan.awk.
确保行之间用换行符(0x0A)隔开。注意,一些 OS X 编辑器使用回车符(0x0D)。
从命令行运行脚本
$ awk -f scan.awk
来自迭代 2 的编辑后的终端输出如下
Iteration 2 Writing data file GLPSOL: GLPK LP/MIP Solver, v4.44 Reading model section from test.mod... Reading data section from test.dat... Model has been successfully generated GLPK Simplex Optimizer, v4.44 0 rows, 0 columns, 0 non-zeros ~ 0: obj = 0.000000000e+00 infeas = 0.000e+00 OPTIMAL SOLUTION FOUND Display statement at line 4 iter = 2 Model has been successfully processed
几乎所有脚本语言都可以实现相同的基本思路,从 bash 向上。此外,敏锐的读者可能会注意到,使用类似的脚本方法也可以在运行时构建修改后的模型(而不是数据)文件。
Visual Basic Script 是与 Microsoft Windows 一起提供的编程语言。
Visual Basic Script 使得创建 GMPL 数据文件然后对这些新创建的文件调用 glpsol 成为可能。
以下示例演示了如何执行此操作。
创建一个模型文件test.mod它只打印参数p.
param p;
printf "Parameter p = %d\n", p;
end;
创建一个脚本test.vbs
Const ForWriting = 2
Set wshShell = WScript.CreateObject ("WSCript.shell")
Set fso = CreateObject("Scripting.FileSystemObject")
Set sout = WScript.StdOut
For i = 1 To 3
'Write data file
Set MyFile = fso.OpenTextFile("test.dat", ForWriting, True)
MyFile.WriteLine "data;"
MyFile.WriteLine "param p := " & i & ";"
MyFile.WriteLine "end;"
MyFile.Close
'Execute glpsol
Set oExec = wshShell.exec("glpsol -m test.mod -d test.dat")
'Copy output of glpsol to console used by script
While Not oExec.StdOut.AtEndOfStream
sout.Write oExec.StdOut.Read(1)
Wend
Next
使用以下命令运行脚本
cscript test.vbs
Visual Basic Script 是与 Microsoft Office 一起提供的编程语言。
VBA 使得创建 GMPL 文件然后对这些新创建的文件调用 glpsol 成为可能。
以下示例演示了如何执行此操作。
Option Explicit
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Private Declare Function OpenProcess Lib "kernel32.dll" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Const SYNCHRONIZE = &H100000
Private Const INFINITE = -1&
Private Const MinimizedNoFocus = 6
' Model file
Private Const modfile = "C:\TEMP\test.mod"
' Result file
Private Const resfile = "C:\TEMP\test.res"
Public Sub parametricStudy()
Dim p As Double
Dim r As String
For p = 0.5 To 2.5 Step 0.2
r = r & runGLPK(p) & vbCrLf
Next p
MsgBox r, vbOKOnly, "Result"
End Sub
Private Function runGLPK(p As Double) As String
Dim f As Integer
Dim s As String
Dim pid As Long
Dim h As Long
Dim x As String
Dim y As String
' Convert double to string
s = p
s = Replace(s, ",", ".")
' Create model file
f = FreeFile()
Open modfile For Output As f
Print #f, "param f, symbolic := """ & resfile & """;"
Print #f, "var x, >=0;"
Print #f, "var y, >=0;"
Print #f, "maximize obj : x + "; s; " * y; "
Print #f, "s.t. c1 : x + y <= 4;"
Print #f, "s.t. c2 : x + 2 * y <= 6;"
Print #f, "solve;"
Print #f, "printf ""%f\n"", x > f;"
Print #f, "printf ""%f\n"", y >> f;"
Print #f, "end;"
Close f
' Delete result fle
If Dir(resfile) <> "" Then
Kill resfile
End If
' Start glpsol
pid = Shell("""C:\Program Files\GLPK\glpk-4.47\w32\glpsol.exe"" -m " & modfile, MinimizedNoFocus)
If pid = 0 Then
MsgBox "Failure to start glpsol.exe", vbCritical, "Error"
Exit Function
End If
' Wait for glpsol to end
h = OpenProcess(SYNCHRONIZE, 0, pid)
If h <> 0 Then
WaitForSingleObject h, INFINITE
CloseHandle h
End If
' Check if result file written
If Dir(resfile) = "" Then
MsgBox "No result from glpsol.exe", vbCritical, "Error"
Exit Function
End If
' Output result
Open resfile For Input As f
Line Input #f, x
Line Input #f, y
Close f
runGLPK = "p = " & s & " => x = " & x & ", y = " & y
End Function
如果基于 shell 命令的脚本(使用 AWK)不够灵活,那么 Python 语言和 PyMathProg 包提供了一个更强大的替代方案。 PyMathProg 允许使用 Python 以非常类似于 GMPL 的形式编写线性规划和混合整数规划模型。关于如何使用 PyMathProg 实现子巡回消除启发式的简明示例见 此处。
评论:此材料应扩展。
Sage 是一种开源数学环境,提供符号计算和数值计算以及良好的可视化功能。Sage 支持 Python 语言,GLPK 可通过 Sage 优化模块 使用。
首先使用类实例构建混合整数模型MixedIntegerLinearProgram——然后使用其求解方法求解,将求解器设置为 GLPK
sage: p = MixedIntegerLinearProgram(maximization=True) sage: x = p.new_variable() ... sage: p.solve(solver="GLPK", log="filename.log")
安装 Sage 的开销显然很高,但该环境运行良好。
可以按如下方式抑制终端输出
import subprocess
capture = subprocess.check_output(["glpsol", "--math", "noisy.mod", "--output", "noisy.out"])
print ("complete")
将上述脚本保存到名为quiet.py的文件中,然后执行它
> python quiet.py complete
GLPSOL 的正常输出存储在capture中,以便以后使用。模型的解保存为文件noisy.out。只有明确的打印语句被发送到控制台。
类似的技术可以应用于其他脚本语言,包括 Bash 和 Perl。此外,可以将命令行参数传递给最终的调用以提高灵活性。