sets:
firehouse/1..3/:num
fire/1..7/:
link(firehouse,fire):distance,x
endsets
data:
num=3,2,2
enddata
min=@sum(link:distance*x)
@for(firehouse(i):@sum(fire(j):x(i,j))=num(i))
@for(fire(j):@sum(firehouse(i):x(i,j))=1)
@for(link:@gin(x))
end
结果
X( 1, 1)0.0000000.000000
X( 1, 2)1.0000000.000000
X( 1, 3)0.0000000.000000
X( 1, 4)0.0000000.000000
X( 1, 5)0.0000000.000000
X( 1, 6)1.0000000.000000
X( 1, 7)1.0000000.000000
X( 2, 1)0.0000000.000000
X( 2, 2)0.0000000.000000
X( 2, 3)1.0000000.000000
X( 2, 4)1.0000000.000000
X( 2, 5)0.0000000.000000
X( 2, 6)0.0000000.000000
X( 2, 7)0.0000000.000000
X( 3, 1)1.0000000.000000
X( 3, 2)0.0000000.000000
X( 3, 3)0.0000000.000000
X( 3, 4)0.0000000.000000
X( 3, 5)1.0000000.000000
X( 3, 6)0.0000000.000000
X( 3, 7)0.0000000.000000
就是说1号消防站派车到2 6 7
2号到3 4
3号到1 5
附录 LINGO10.0新增功能介绍A.1 新增功能简介
2006年初,LINDO系统公司正式发布了 LINGO 10.0版本。与 LINGO 9.0及更早的版
本相比,该版本的主要改进包括三个方面:
1.LINGO 10.0最显著的新特征在于增强了用 LINGO编程的能力。这主要包括:
(1)程序流程的控制
在 LINGO 9.0及更早的版本的计算段( CALC)中,控制程序流程粗宏旅的只有一种语句,即
集合循环函岩凳数@FOR引导的语句,此外所有计算段中的语句是顺序执行的。 LINGO10.0在
计算段中增加了控制程序流程的语句,主要包括条件分支控制(@IFC或@IFC/@ELSE语
句)、条件循环控制( @WHILE语句)、循环跳出控制( @BREAK语句)、程序暂停控制
(@PAUSE语句)以及程序终止控制(@STOP语句)。
(2)子模型( SUBMODEL)
在 LINGO 9.0及更早的版本中,在每个 LINGO模型窗口中只允许有一个优化模型,可
以称为主模型( MAIN MODEL)。在 LINGO 10.0中,每个 LINGO模型窗口中除了主模型
外,用户还可以定义子模型(SUBMODEL)。子模型可以在主模型的计算段中被调用,这就
进一步增强了 LINGO的编程能力。相应的新增函数还包括 @SOLVE、@GEN、@PIC、
@SMPI、@RELEASE等。
(3) 其他新增函数
LINGO10.0增加了输出函数 @TABLE,可以更方便地以格式化的表格形式输出数据;
新增了数学函数 @NORMSINV,即标准正态分布的分布函数的逆函数;新增了缺省输出设
备(文件)的重定义函数 @DIVERT;新增了参数设置函数@SET和@APISET等。
2.对 LINGO内部采用的一些求解程序(如混合整数规划、非线性优化和全局优化求
解程序,包括一些相应的选项)的功能进行了完善和改进,使求解过程更快速、更可靠,对
模型进行调试的能力和对模型错误进行更准确定位的能力也得到了进一步增强。
3.增加了对一些新的软硬件的支持,如支持 64位运算和更大的内存等,以及支持 Java
JNI接口技术,新的@ODBC函数支持 Microsoft SQL Server 等。
我们下面只对第1类新增功能(增强 LINGO编程能力的功能)进行简要介绍,关心第
2、3类新增功能的读者请直接阅读 LINGO在线帮助文件或相关介绍文档。
A.2程序流程的控制
A.2.1条件分支控制
在计算段( CALC)中,如果只有当某个条件满足时才执行某个或某些语绝码句,则可以使
用@IFC或@IFC/@ELSE语句,其中 @ELSE部分是可选的(在下面的语法中用方括号表示)。
其基本的使用语法是:
@IFC(condition:
executable statements(可执行语句 1)
[@ELSE
executable statements(可执行语句 2)]
)
其中 condition是一个逻辑表达式(表示相应的条件),当 condition的逻辑值为“真”
(条件成立)时,程序执行语句 1;否则程序执行语句 2。
我们以本书第五章 5.2节(有瓶颈设备的多级生产计划问题)中的数据来说明这个语句
的用法。在该问题中,项目间的消耗系数 Req是一个非常稀疏的矩阵,仅有 6个非零元。如果
我们想输出这个矩阵,但不显示其中的零元素(即显示为空),可以在原来的程序(本书 177-178
页的程序 exam0502.lg4)中增加以下的计算段:
calc:
@WRITE( ' 项目间的消耗系数如下: ')
@WRITE( @NEWLINE(1))
@WRITEFOR(PART(J): 5*' ', PART(J))
@FOR( PART(I):
@WRITE( @NEWLINE(1), PART(I))
@FOR( PART(J):
@IFC( Req(i,j) #GT# 0.0:
@write( @FORMAT( Req(i,j), '#5.0f'))
@ELSE
@WRITE(' ')
)
)
)
@WRITE( @NEWLINE(2))
endcalc
运行修改后的程序,相应的输出如下(只列出与计算段的输出相关的部分):
项目间的消耗系数如下:
A B C D E F G
A
B 5.
C 7.
D 9.
E 11.
F 13.
G 15.
下面我们作几点说明:
1.请注意上面程序中的函数 @WRITE和@WRITEFOR,他们在 LINGO9.0中也出现过
(参见本书 112页),但当时主要是用在程序的数据段 (DATA)方便用户控制输出格式,所
输出的变量的取值是程序运行结束后最后结果的相关数据, 并且输出必须定向到@TEXT函
数,即通过@TEXT函数输出到缺省的输出设备(通常就是报告窗口)或文本文件。 LINGO10.0
中,这两个函数也是为了方便用户控制输出格式,但它们还可以出现在计算段(CALC)随时
输出中间结果,并且不需要使用@TEXT函数,输出的结果也是被定向到缺省的输出设备(通
常就是标准的报告窗口)。如果希望改变缺省的输出设备,可以采用 @DIVERT函数(参见
本附录 A.4.3节)。
作为一个简单例子,我们可以编写以下程序,说明在计算段中可以随时输出中间结果。
calc:
a=5
@write('a= ',a,@newline(1))
b=8
a=a+b
@write('a= ',a,@newline(1))
endcalc
以上程序中第 3行和第 6行的语句是一样的,但输出结果却会不一样:
a= 5
a= 13
2. 请读者特别注意,条件分支控制语句的用法只能出现在计算段( CALC)中。这也意
味着,我们不应该对程序运行结束后才能得到最后结果、计算段中尚未确定具体取值的变量
进行上述判断和输出。否则,输出的取值可能只是变量的初始值或中间计算结果。读者可能
会觉得既然如此,那么这种控制语句的用处就不大了,因为计算段处理的似乎都是已知参数
(或从已知参数很容易直接计算得到的变量值)。实际上并非如此,这是由于 LINGO10.0
增加了子模型功能,而子模型又是可以在计算段进行求解的,这时计算段中的变量所取的值
可能既不是初始参数,又不是整个模型最后的结果,但仍然可以输出中间结果。
而且,LINGO10.0中还增加了条件循环( @WHILE语句)等其他复杂的控制语句,它
们通常也要用到@IFC语句。我们将在后面介绍子模型和条件循环控制时再通过例子进行说
明。
3.请读者注意,@IFC函数和以前用过的@IF函数的功能是不同的:@IFC是引导流程
控制语句的函数(按照不同条件选择不同的程序分支进行执行),而@IF是一个算术函数,
按照不同条件返回不同的计算结果或表达式(参见本书 114页的介绍)。
A.2.2条件循环控制及相关语句
在 LINGO 9.0及更早的版本中,只有一种控制程序流程的语句,即集合循环函数 @FOR
引导的语句,该函数对集合的元素逐个进行循环。在 LINGO 10.0中,如果只要当某个条
件满足时就反复执行某个或某些语句,直到条件不成立为止,则可以使用@WHILE语句。
其基本的使用语法是:
@WHILE(condition:
executable statements(可执行语句)
)
其中 condition是一个逻辑表达式(表示相应的条件),当 condition的逻辑值为“真”
(条件成立)时,程序就执行相应的语句,直到条件不成立为止。请注意,条件循坏控制也
只能出现在计算段(CALC)中。
在条件循环控制中,还经常会使用到循坏跳出控制(@BREAK语句)、程序暂停控制
(@PAUSE语句)以及程序终止控制(@STOP语句):
.
@BREAK函数不需要任何参数,其功能是立即终止当前循环,继续执行当前循环
外的下一条语句。这个函数可以用在条件循环语句(@WHILE语句)中,也可以
用在集合循环语句(@FOR语句)中。此外,由于一般是在满足一定的特定条件
时才终止当前循环的执行,所以函数@BREAK一般也要结合@IFC/@ELSE使用。
.
@PAUSE函数暂停程序执行,并d出一个窗口,等待用户选择继续执行(RESUME)
或者终止程序(
INTERRUPT)。如果希望在d出的窗口中显示某些文本信息或某
个变量的当前取值,只需要将这些文本信息或变量作为
@PAUSE的调用参数即可。
.
@STOP函数终止程序的运行,并d出一个窗口,说明程序已经停止运行。如果希
望在d出的窗口中显示某些文本信息或某个变量的当前取值,只需要将这些文本或
变量作为@STOP的调用参数即可。
例如,如果希望从一个递增排列的正整数数列
X中找到某个具体的数
KEY在数列
X中
所在的位置, 可以采用二分搜索算法。具体的程序是(原程序位于
LINGO安装目录的
examples目录下,文件名为
LOOPBINS.LG4,这里加上了中文注释):
MODEL:
TITLE 二分搜索!Binary-search
SETS:
S1: X
ENDSETS
DATA:
KEY = 16
! 想要找到的数
X = 2 7 8 11 16 20 22 32!递增排列的正整数数列
ENDDATA
CALC:
IB=1!搜索位置的最小值
IE = @SIZE( S1)! 搜索位置的最大值(数列中元素的个数)
@WHILE( IB #LE# IE:
LOC = @FLOOR((IB + IE)/2)! 二分法
@IFC( KEY #EQ# X(LOC):
@BREAK!找到结果,结束循环
@ELSE
@IFC( KEY #LT# X( LOC):
IE = LOC-1
@ELSE
IB = LOC+1
)
)
)
@IFC( IB #LE# IE:
@PAUSE( '找到位置: ', LOC)! 显示结果
@ELSE
@STOP( ' 数列中找不到相应的数 !!!')!程序停止运行
)
ENDCALC
END
注:这里集合 S1没有显式地定义元素,但由于其属性 X有 8个元素,因此 LINGO自
动认为集合 S1={1,2,3,4,5,6,7,8}。
本程序运行时,将找到 KEY = 16位于数列 X中的第 5个位置,于是通过@PAUSE语句
将这一信息报告给用户;如果取 KEY = 15, 由于数列 X中没有 15,程序运行时通过@STOP
语句将这一信息报告给用户。
请读者注意,由于 @BREAK函数不需要参数,因此程序中的语句直接写成“ @BREAK”。
而函数 @PAUSE和@STOP是可以有参数的,所以程序中即使不给出参数,语句也应该写成
“@PAUSE()”和“@STOP()”,即标示参数表的小括号不能省略,否则就会出现语法错误。
这和以前用过的函数 @TEXT的用法非常类似。
A.3子模型功能介绍
A.3.1子模型的定义及求解
在 LINGO 9.0及更早的版本中,在每个 LINGO模型窗口中只允许有一个优化模型,可
以称为主模型( MAIN MODEL)。在 LINGO 10.0中,每个 LINGO模型窗口中除了主模型
外,用户还可以定义子模型(SUBMODEL)。子模型可以在主模型的计算段中被调用,这就
进一步增强了 LINGO的编程能力。
子模型必须包含在主模型之内,即必须位于以“ MODEL:”开头、以“ END”结束的模
块内。同一个主模型中,允许定义多个子模型,所以每个子模型本身必须命名,其基本语法
是:
@SUBMODEL mymodel:
可执行语句(约束 +目标函数)
ENDSUBMODEL
其中 mymodel是该子模型的名字,可执行语句一般是一些约束语句,也可能包含目标
函数,但不可以有自身单独的集合段、数据段、初始段和计算段。也就是说,同一个主模型
内的变量都是全局变量,这些变量对主模型和所有子模型同样有效。
如果已经定义了子模型 mymodel,则在计算段中可以用语句 “@SOLVE( submodel)”求
解这个子模型。
我们来看一个背包问题( Knapsack Problem)的例子:王先生想要出门旅行,需要将一
些旅行用品装入一个旅行背包。旅行背包有一个重量限制,装入的旅行用品总重量不得超过
30千克。候选的旅行用品有 8件,其重量依次为 3、4、6、7、9、10、11、12(千克);
王先生认为这8件旅行用品的价值(或重要性)依次为 4、6、7、9、11、12、13、15。那
么,为了使背包装入的旅行用品的总价值最大,王先生应该选择哪几件旅行用品?
我们用 VAL(I)、WGT(I)分别表示第 I件物品的价值和重量, CAP表示背包的重量
限制,用 Y(I)表示是否装入第 I件物品( 0-1决策变量,1表示装, 0表示不装)。容易建
立如下优化模型(直接按 LINGO的程序格式写出,命名为文件 knapsack01.lg4):
MODEL:
SETS:
ITEM: WGT, VAL, Y
ENDSETS
DATA:
VAL = 4 6 7 9 11 12 13 15
WGT = 3 4 6 7 9 10 11 12
CAP = 30
ENDDATA
MAX = OBJ
[Objective] OBJ= @SUM( ITEM(j): VAL(j)*Y(j))!目标
[Capacity] @SUM( ITEM(j): WGT(j)*Y(j)) <= CAP!重量约束
@FOR( ITEM(j): @BIN( Y(j)))!0/1变量
END
求解本模型,可得到最优解
Y(2)=Y(4)=Y(5)=Y(6)=1(其他
Y(I)为
0),最优值
OBJ=38。
对于这样一个简单的模型,上面的程序中只有主模型。作为一种练习,我们也可以将这
个模型定义为子模型,然后在
CALC中进行求解,相应的程序为(命名为文件
knapsack02.lg4)):
MODEL:
SETS:
ITEM: WGT, VAL, Y
ENDSETS
DATA:
VAL = 4 6 7 9 11 12 13 15
WGT = 3 4 6 7 9 10 11 12
CAP = 30
ENDDATA
SUBMODEL KNAPSACK: !开始定义子模型
KNAPSACK
MAX = OBJ
[objective] OBJ= @SUM( ITEM(j): VAL(j)*Y(j))!目标
[capacity] @SUM( ITEM(j): WGT(j)*Y(j)) <= CAP!重量约束
@FOR( ITEM(j): @BIN( Y(j)))!0/1变量
ENDSUBMODEL !完成子模型KNAPSACK的定义
CALC:
@SOLVE(KNAPSACK)! 求解子模型
KNAPSACK
ENDCALC
END
求解本模型,得到的结果与不用子模型时相同。
A.3.2求背包问题的多个最好解的例子
对于上面的背包问题,最优解并不是唯一的。如果我们希望找到所有的最优解(最优值
OBJ=38的所有解),有没有办法呢?更一般地,能否找出前 K个最好的解?这样我们可以
把这 K个最好的解全部列出来,供王先生(决策者)选择。
为了得到第 2个最好的解,我们需要再次求解子模型 KNAPSACK,但必须排除再次找到
刚刚得到的解 Y(2)=Y(4)=Y(5)=Y(6)=1(其他 Y(I)为 0)。因此,我们需要在第 2次求解子模型
KNAPSACK时,增加一些约束条件(一般称为“割”)。生成“割”的方法可能有很多种,
这里我们介绍一种针对 0-1变量的特殊处理方法。
对于我们刚刚得到的解 Y(2)=Y(4)=Y(5)=Y(6)=1(其他 Y(I)为 0),显然满足
Y(1) -Y(2) + Y(3) -Y(4) - Y(5) -Y(6) + Y(7) + Y(8) = -4;
这个等式左边就是将刚刚得到的解中取 1的 Y(I)的系数定义为-1,取 0的 Y(I)的系数定义
为 1,然后求代数和;等式右边就是解中取 1的 Y(I)的个数的相反数。
为了防止再次求解子模型 KNAPSACK时这个解再次出现,就是要防止 Y(2),Y(4),Y(5),
Y(6)同时取 1的情况出现。下面的约束就可以保证做到这一点:
Y(1) -Y(2) + Y(3) -Y(4) - Y(5) -Y(6) + Y(7) + Y(8) >= -3;
这个约束就是将上面等式中的右端项增加了 1,将等号“=”改成了“>=”。显然,这
个约束排除了 Y(2),Y(4),Y(5),Y(6)同时取 1的情况,因为 Y(2),Y(4),Y(5),Y(6)同时
取 1(其他 Y(I)=0)不能满足这个约束。其次,由于 Y(I)只能取 0或 1,这个约束除了排除
Y(2),Y(4),Y(5),Y(6)同时取 1的情况外,没有对原可行解空间增加任何新的限制。
可以想象,增加这个约束后,新的最优解一定与 Y(2)=Y(4)=Y(5)=Y(6)=1(其他 Y(I)为
0)不同。这种处理方法具有一般性,可以用于找出背包问题的前 K个最好解。
具体的程序如下(以下程序中取 K=7,命名为文件 knapsack03.lg4)):
SETS:
ITEM: WGT, VAL, Y
SOLN: RHS! RHS表示根据每个最优解生成“割”时的右端项
SXI(SOLN,ITEM): COF! “割”的系数,即 1或-1
ENDSETS
DATA:
K=7
VAL = 4 6 7 9 11 12 13 15
WGT = 3 4 6 7 9 10 11 12
CAP = 30
SOLN = 1..K
ENDDATA
SUBMODEL KNAPSACK:
MAX = OBJ
OBJ= @SUM( ITEM(j): VAL(j)*Y(j))!目标
@SUM( ITEM(j): WGT(j)*Y(j)) <= CAP!重量约束
@FOR( ITEM(j): @BIN( Y(j)))!0/1变量
@FOR( SOLN(k)| k #LT# ksofar: !"割去"(排除)已经得到的解
@SUM( ITEM(j): COF(k,j)*Y(j)) >= RHS(k)
)
ENDSUBMODEL
CALC:
@divert('knapsack.txt')!结果保存到文件knapsack.txt
@FOR( SOLN(ks): ! 对ks=1,2,…,K进行循环
KSOFAR = ks! KSOFAR表示当前正在计算的是第几个最优解
@SOLVE( KNAPSACK)
RHS(ks) = 1
!以下打印当前 (第ks个)最优解Y及对应的最优值OBJ
@WRITE(' ',ks,' ', @FORMAT( OBJ,'3.0f'),':')
@writefor(ITEM(j):' ',Y(j))
@write(@newline(1))
!以下计算这个解生成的“割”的系数
@FOR( ITEM(j):
@IFC( Y(j) #GT# .5:
COF(KS,j) = -1
RHS(ks) = RHS(ks) - 1
@ELSE
COF(KS,j) = 1
)!分支 @IFC / @ELSE 结束
)!循环 @FOR( ITEM(j)结束
)!对ks的循环结束
@divert()! 关闭文件knapsack.txt,恢复正常输出模式
ENDCALC
注:计算段中的语句 @divert('knapsack.txt')的含义是,将此后的输出定向到文
本文件knapsack.txt(参见本附录A.4.3节)。
运行这个程序以后,文件 knapsack.txt中将包括以下输出(其他输出略去):
1 38: 0 1 0 1 1 1 0 0
2 38: 1 1 1 1 0 1 0 0
3 38: 1 1 0 0 0 0 1 1
4 37: 1 1 0 0 0 1 0 1
5 37: 0 1 1 1 0 0 0 1
6 37: 1 1 1 1 1 0 0 0
7 37: 0 1 1 0 1 0 1 0
可见,前 7个最好的解中,最优值为 OBJ=38的解一共有 3个,而 OBJ=37的解至少有
4个(因为我们只计算了前 7个最好的解,我们暂时还无法判断 OBJ=37的解是否只有 4个),
每个解(Y的取值)也显示在结果报告中了。
A.3.3多个子模型的例子
同一个 LINGO主模型中,允许定义多个子模型。例如,如果我们希望分别求解以下 4
个优化问题:
(1)在满足约束 x2+4y2≤
1且 x,y非负的条件下,求 x-y的最大值;
(2)在满足约束 x2+4y2≤
1且 x,y非负的条件下,求 x+y的最小值;
(3)在满足约束
x2+4y2≤
1且
x,y可取任何实数的条件下,求
x-y的最大值;
(4)在满足约束
x2+4y2≤
1且
x,y可取任何实数的条件下,求
x+y的最小值。
我们可以编写如下
LINGO程序:
MODEL:
SUBMODEL OBJ1:
MAX=X-Y
ENDSUBMODEL
SUBMODEL OBJ2:
MIN=X+Y
ENDSUBMODEL
SUBMODEL CON1:
x^2+4*y^2<=1
ENDSUBMODEL
SUBMODEL CON2:
@free(x)
@free(y)
ENDSUBMODEL
CALC:
@write('问题1的解:', @newline(1))
@solve(OBJ1,CON1)
@write('问题2的解:', @newline(1))
@solve(OBJ2,CON1)
@write('问题3的解:', @newline(1))
@solve(OBJ1,CON1,CON2)
@write('问题4的解:', @newline(1))
@solve(OBJ2,CON1,CON2)
ENDCALC
END
这个程序中定义了
4个子模型,其中
OBJ1和OBJ2只有目标(没有约束),而
CON1和CON2
只有约束(没有目标)。在计算段,我们将它们进行不同的组合,分别得到针对问题(
1)~(4)
的优化模型进行求解。但需要注意,每个
@solve命令所带的参数表中的子模型是先合并后求解
的,所以用户必须确保每个
@solve命令所带的参数表中的子模型合并后是合理的优化模型,例
如最多只能有一个目标函数。
运行这个后,得到的正是我们预想的结果(只列出部分相关的输出结果):
问题1的解:
Objective value: 1.000000
Variable Value Reduced Cost
X 1.000000 0.5198798E-08
Y 0.4925230E-08 1.000000
问题2的解:
Objective value: 0.2403504E-09
Variable Value Reduced Cost
X 0.000000 1.000000
Y 0.000000 1.000000
问题3的解:
Objective value: 1.118034
Variable Value Reduced Cost
X 0.8944272 0.000000
Y -0.2236068 0.000000
问题4的解:
Objective value: -1.118034
Variable Value Reduced Cost
X -0.8944272 0.000000
Y -0.2236068 0.000000
这4个问题都是非常简单的问题,最优解和最优值很容易用解析方法计算得到,读者不
妨用解析方法计算和验证一下以上得到的解的正确性。
A.3.4其他相关函数
1.@GEN
这个函数只能在计算段使用,功能与菜单命令
LINGO|Generate和
LINGO行命令
GEN
类似(参见本书
118页和
132页),即生成完整的模型并以代数形式显示(主要作用是可供
用户检查模型是否有误)。当不使用任何调用参数时,
“@GEN()”语句只对主模型中出现
在当前“@GEN()”语句之前的模型语句进行处理。
例如,如果在上面
A..3.1节的文件
knapsack01.lg4中增加以下的计算段:
calc:
@gen()
endcalc
则程序运行时报告窗口的显示为:
MODEL:
[_1] MAX= OBJ
[OBJECTIVE] OBJ - 4 * Y_1 - 6 * Y_2 - 7 * Y_3 - 9 * Y_4 - 11 *Y_5 – 12 *
Y_6 - 13 * Y_7 - 15 * Y_8 = 0
[CAPACITY] 3 * Y_1 + 4 * Y_2 + 6 * Y_3 + 7 * Y_4 + 9 * Y_5 + 10 * Y_6 + 11
* Y_7 + 12 * Y_8 <= 30
@BIN( Y_1)@BIN( Y_2)@BIN( Y_3)@BIN( Y_4)@BIN( Y_5)
@BIN( Y_6)@BIN( Y_7)@BIN( Y_8)
END
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)