LINGO10新增功能.docx
《LINGO10新增功能.docx》由会员分享,可在线阅读,更多相关《LINGO10新增功能.docx(22页珍藏版)》请在冰豆网上搜索。
LINGO10新增功能
附录LINGO10.0新增功能介绍
A.2程序流程的控制
A.2.1条件分支控制
在计算段(CALC)中,如果只有当某个条件满足时才执行某个或某些语句,则可以使
用@IFC或@IFC/@ELSE语句,其中@ELSE部分是可选的(在下面的语法中用方括号表示)。
其基本的使用语法是:
@IFC(condition:
executablestatements(可执行语句1);
[@ELSE
executablestatements(可执行语句2);]
)
其中condition是一个逻辑表达式(表示相应的条件),当condition的逻辑值为“真”
(条件成立)时,程序执行语句1;否则程序执行语句2。
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
运行修改后的程序,相应的输出如下(只列出与计算段的输出相关的部分):
项目间的消耗系数如下:
ABCDEFG
A
B5.
C7.
D9.
E11.
F13.
G15.
下面我们作几点说明:
1.请注意上面程序中的函数@WRITE和@WRITEFOR,它们还可以出现在计算段(CALC)随时输出中间结果,并且不需要使用@TEXT函数,输出的结果也是被定向到缺省的输出设备(通常就是标准的报告窗口)。
如果希望改变缺省的输出设备,可以采用@DIVERT函数作为一个简单例子,我们可以编写以下程序,说明在计算段中可以随时输出中间结果。
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.这时计算段中的变量所取的值可能既不是初始参数,又不是整个模型最后的结果,但仍然可以输出中间结果。
而且,LINGO10.0中还增加了条件循环(@WHILE语句)等其他复杂的控制语句,它们通常也要用到@IFC语句。
我们将在后面介绍子模型和条件循环控制时再通过例子进行说明。
3.@IFC函数和以前用过的@IF函数的功能是不同的:
@IFC是引导流程
控制语句的函数(按照不同条件选择不同的程序分支进行执行),而@IF是一个算术函数,
按照不同条件返回不同的计算结果或表达式。
A.2.2条件循环控制及相关语句
如果只要当某个条件满足时就反复执行某个或某些语句,直到条件不成立为止,则可以使用@WHILE语句。
其基本的使用语法是:
@WHILE(condition:
executablestatements(可执行语句);
)
其中condition是一个逻辑表达式(表示相应的条件),当condition的逻辑值为“真”
(条件成立)时,程序就执行相应的语句,直到条件不成立为止。
请注意,条件循坏控制也
只能出现在计算段(CALC)中。
在条件循环控制中,还经常会使用到循坏跳出控制(@BREAK语句)、程序暂停控制
(@PAUSE语句)以及程序终止控制(@STOP语句):
@BREAK函数不需要任何参数,其功能是立即终止当前循环,继续执行当前循环
外的下一条语句。
这个函数可以用在条件循环语句(@WHILE语句)中,也可以
用在集合循环语句(@FOR语句)中。
@PAUSE函数暂停程序执行,并弹出一个窗口,等待用户选择继续执行(RESUME)
或者终止程序(INTERRUPT)。
如果希望在弹出的窗口中显示某些文本信息或某
个变量的当前取值,只需要将这些文本信息或变量作为@PAUSE的调用参数即可。
@STOP函数终止程序的运行,并弹出一个窗口,说明程序已经停止运行。
如果希
望在弹出的窗口中显示某些文本信息或某个变量的当前取值,只需要将这些文本或
变量作为@STOP的调用参数即可。
例如,如果希望从一个递增排列的正整数数列X中找到某个具体的数KEY在数列X中
所在的位置,可以采用二分搜索算法。
具体的程序是(原程序位于LINGO安装目录的
examples目录下,文件名为LOOPBINS.LG4,这里加上了中文注释):
MODEL:
TITLE二分搜索;!
Binary-search;
SETS:
S1:
X;
ENDSETS
DATA:
KEY=16;!
想要找到的数;
X=2781116202232;!
递增排列的正整数数列;
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子模型的定义及求解
在LINGO9.0及更早的版本中,在每个LINGO模型窗口中只允许有一个优化模型,可
以称为主模型(MAINMODEL)。
在LINGO10.0中,每个LINGO模型窗口中除了主模型
外,用户还可以定义子模型(SUBMODEL)。
子模型可以在主模型的计算段中被调用,这就
进一步增强了LINGO的编程能力。
子模型必须包含在主模型之内,即必须位于以“MODEL:
”开头、以“END”结束的模
块内。
同一个主模型中,允许定义多个子模型,所以每个子模型本身必须命名,其基本语法
是:
@SUBMODELmymodel:
可执行语句(约束+目标函数);
ENDSUBMODEL
其中mymodel是该子模型的名字,可执行语句一般是一些约束语句,也可能包含目标
函数,但不可以有自身单独的集合段、数据段、初始段和计算段。
也就是说,同一个主模型
内的变量都是全局变量,这些变量对主模型和所有子模型同样有效。
如果已经定义了子模型mymodel,则在计算段中可以用语句“@SOLVE(submodel);”求
解这个子模型。
我们来看一个背包问题(KnapsackProblem)的例子:
王先生想要出门旅行,需要将一
些旅行用品装入一个旅行背包。
旅行背包有一个重量限制,装入的旅行用品总重量不得超过
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=467911121315;
WGT=34679101112;
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=467911121315;
WGT=34679101112;
CAP=30;
ENDDATA
SUBMODELKNAPSACK:
!
开始定义子模型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=467911121315;
WGT=34679101112;
CAP=30;
SOLN=1..K;
ENDDATA
SUBMODELKNAPSACK:
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中将包括以下输出(其他输出略去):
138:
01011100
238:
11110100
338:
11000011
437:
11000101
537:
01110001
637:
11111000
737:
01101010
可见,前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:
SUBMODELOBJ1:
MAX=X-Y;
ENDSUBMODEL
SUBMODELOBJ2:
MIN=X+Y;
ENDSUBMODEL
SUBMODELCON1:
x^2+4*y^2<=1;
ENDSUBMODEL
SUBMODELCON2:
@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的解:
Objectivevalue:
1.000000
VariableValueReducedCost
X1.0000000.5198798E-08
Y0.4925230E-081.000000
问题2的解:
Objectivevalue:
0.2403504E-09
VariableValueReducedCost
X0.0000001.000000
Y0.0000001.000000
问题3的解:
Objectivevalue:
1.118034
VariableValueReducedCost
X0.89442720.000000
Y-0.22360680.000000
问题4的解:
Objectivevalue:
-1.118034
VariableValueReducedCost
X-0.89442720.000000
Y-0.22360680.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
用户可以指定@GEN处理哪个(或多个)子模型,这只需要将子模型名(可以多个)
作为调用@GEN函数的参数即可。
如果为@GEN函数指定多个子模型作为调用参数,则显
示的是这几个子模型合并后的结果。
例如,如果在上面A..3.1节的文件knapsack02.lg4的计算段中增加以下语句:
@gen(KNAPSACK);
则程序运行时报告窗口的显示与上面的显示相同(因为knapsack02.lg4的子模型KNAPSACK
与knapsack01.lg4的主模型本质上是一样的)。
注意:
如果在上面A..3.2节的文件knapsack03.lg4的计算段中不同的位置增加
@gen(KNAPSACK)语句,将得到不同的输出结果。
这是因为在计算段中不同的位置,子模
型KNAPSACK的具体内容是有可能发生变化的(因为“割”在不断增加)。
例如,如果将
@gen(KNAPSACK)语句增加在计算段的最后,得到的输出为(可以看出此时模型中有6个
“割”,即约束[_4]~[_9]):
MODEL:
[_1]MAX=OBJ;
[_2]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;
[_3]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;
[_4]Y_1-Y_2+Y_3-Y_4-Y_5-Y_6+Y_7+Y_8>=-3;
[_5]-Y_1-Y_2-Y_3-Y_4+Y_5-Y_6+Y_7+Y_8>=-4;
[_6]-Y_1-Y_2+Y_3+Y_4+Y_5+Y_6-