lingo语法入门
lingo常用文件格式
1、后缀“lg4”表示lingo格式的模型文件,只有lingo软件才可打开;
2、后缀“lng”表示文本格式的模型文件;
3、后缀“ldt”表示lingo数据文件;
4、后缀“ltf”表示lingo命令脚本文件;
5、后缀“lgr”表示lingo报告文件。
LINGO Solver Status窗口要素详解
窗口右侧内容介绍
1、Variables(变量数量):其中包括变量总数(Total)、非线性变量数(Nonlinear)、整数变量数(Integer);
2、Constraints(约束数量):包括约束总数(Total)、非线性约束个数(Nonlinear)。
3、Nonzeros(非零系数数量):包括总数(Total)、非线性约束个数(Nonlinear)。
4、Generator Memory Used(K)(内存使用量):单位为千字节(K)。
5、Elapsed Runtime(hh:mm:ss)(求解花费的时间):显示格式是“时:分:秒”。
窗口左上角内容介绍
左上角是求解器(求解程序)状态框(Solver Status)。具体介绍如下表所示:
域名 | 含义 | 可能的显示 |
---|---|---|
Model Class | 当前的模型类型 | LP,QP,NLP,ILP,IQP等 |
State | 当前解的状态 | Global Optimum,Local Optimum,Feasible,Infeasible(不可行),Unbounded(无界),Interrupted(中断),Undetermined(不确定) |
Objective | 当前解的目标函数值 | 实数 |
Infeasibility | 当前约束不满足的总量(不是不满足的约束个数) | 实数(即使该值=0,当前解也可能不可行,因为这个量中没有考虑用上下界命令形式给出的约束) |
Iterations | 到目前为止的迭代次数 | 非负整数 |
窗口左下角内容介绍
左下角是扩展的求解器(求解程序)状态框(Extended Solver Status)。具体介绍如下表所示:
域名 | 含义 | 可能的显示 |
---|---|---|
Solver Type | 使用的特殊求解程序 | B-and-B(分支定界算法);Global(全局最优求解程序);Multistart(用多个初始点求解的程序) |
Best Obj | 到目前为止找到的可行解的最佳目标函数 | 实数 |
Obj Bound | 目标函数值的界 | 实数 |
Steps | 特殊求解程序当前运行步数:分支数(对B-and-B程序);子问题数(对Global程序);初始点数(对Multistart程序) | 非负整数 |
Active | 有效步数 | 非负整数 |
Lingo的基本用法注意事项
(1)lingo中不区分大小写字母;但lingo中的变量和行名可以超过8个字符,只是不能超过32个字符,且仍然必须以字母开头。
(2)lingo解优化模型时已经假定所有变量非负(除非用限定变量取值范围的函数@free或@bnd)。
(3)尽可能采用线性表达式定义目标和约束(如果可能的话)。
(4)lingo模型是由一系列的语句组成的,即语句是组成lingo模型的基本单位。每个语句都是以分号“;”结束的,最好每一行只写一条语句。
(5)lingo中以感叹号“!”开始的是说明语句(说明语句也需要以分号“;”结束)。
Lingo模型建立基本组成要素
一般来说lingo模型的建立需要5个部分组成,也可称为5段语句。
集合段(SETS)
这部分要以“SETS:”开始,以“ENDSETS”结束,作用在于定义必要的集合变量(SET)及其元素(member,含义类似于数组的下标)和属性(attribute,含义类似于数组)。
QUARTERS/1,2,3,4/:DEM,RP,OP,INV;
其中,QUARTERS为集合名称,斜线中的内容是元素,冒号后的是属性。
关于lingo的集合段含义可以借助于数据结构课程中的广义表来理解,也就是“列表之列表”。
具体的理解从下表的结构出发:
集合元素 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
DEM(属性1) | DEM(1) | DEM(2) | DEM(3) | DEM(4) |
… | … | … | … | … |
INV(属性4) | INV(1) | INV(2) | INV(3) | INV(4) |
目标与约束段
这部分实际上定义了目标函数、约束条件等,但这部分并没有段的开始和结束标记,因此实际上就是除其它4个段(都有明确的段标记)外的lingo模型。
这里一般要用到函数。例如:
MIN=@SUM(QUARTERS:400*RP+450*OP+20*INV);
@FOR(QUARTERS(I):RP(I)<40);
@FOR(QUARTERS(I):I#GT#1:INV(I)=INV(I-1)+RP(I)+OP(I)-DEM(I););
INV(1)=10+RP(1)+OP(1)-DEM(1);
目标函数(MIN=后面接的表达式)是用求和函数
“@SUM(集合(下标):关于集合的属性的表达式)”
的方式定义的,这个函数的功能是对语句中冒号“:”后面的表达式,按照“:”前面的集合指定的下标(元素)进行求和。
当然本例中的目标函数也可以写成:
@sum(quarters(i):400*rp(i)+450*op(i)+20*inv(i));
因为这里的@sum相当于求和符号“Σ”而"quarters(i)"相当于“i∈quarters”的含义。
约束使用循环函数“@for(集合(下标):关于集合的属性的约束关系式)”的方式定义的,意思是对冒号“:”前面的集合的每个元素(下标),冒号“:”后面的约束关系式都要成立。
可以对下标集合的元素增加一个逻辑关系式“I #GT# 1(这个限制条件与集合之间有一个竖线“|”分开,称为过滤条件)”。限制条件“I#GT#1”是一个逻辑表达式,意思就是I>1;“#GT#”是逻辑运算符号,意思是“大于”的含义。
数据段(DATA)
这部分要以“DATA:”开始,以“ENDDATA”结束,作用在于对集合的属性(数组)输入必要的常数数据。格式为:
attribute(属性)=value_list(常数列表);
常数列表中的数据之间可以用逗号“,”分开,也可以用空格分开(回车的作用也等价于一个空格)。
如果想在运行时才对参数赋值,可以在数据段使用输入语句。单着仅用于对单个变量(包括属性变量)赋值,而不能用于属性变量(数组)的单个元素。
输入语句格式为:
变量名=?;
初始段(INIT)
这部分要以“INIT:”开始,以“ENDINIT”结束,作用在于对集合的属性(数组)定义初值(因为求解算法一般是迭代算法,所以用户如果能给出一个比较好的迭代初值,对提高算法的计算效果是有益的)。初始段多用于对决策变量进行初始值赋值。
定义初值的格式:
attribute(属性)=value_list(常数列表);
计算段(CALC)
这部分要以“CALC:”开始,以“ENDCALC”结束,作用在于对一些原始数据进行计算处理(这种初始是在数据段的数据输入完成以后,lingo正式求解模型之前进行的)。
设计这个段的目的在于对一些原始数据进行预处理,得到在模型中真正需要的数据。
例如:
CALC:
T_DEM=@SUM(QUARTERS:DEM);!总需求;
A_DEM=T_DEM/@size(QUARTERS);!平均需求;
ENDCALC
其中函数@size表示集合quarters中的元素个数。如果需要的话,这两个变量就可以在程序的其它地方作为常数使用了。
请大家注意,在计算段中语句是顺序执行的,所以上面的两个语句不能交换顺序,因为计算A_DEM必须要用到T_DEM的值。此外,在计算段中只能直接使用赋值语句,而不能包含需要经过解方程或经过求解优化问题以后才能决定的变量。
派生集合
前面讲述的集合段相当于一维数组,那么如何产生更高维的数组呢?
下面以二维数组为例:
可以使用两个一维数组组合形成一个新的集合,即一个二元对。
用上面的例子中的demand和supply集合下构成一个新集合:
link(demand,supply):c;
表示集合link中的元素就是集合demand和supply的元素组成成的有序二元组,从数学上看,link就是demand和supply的笛卡尔积,也就是说:
l
i
n
k
=
[
(
s
,
t
)
∣
s
∈
D
E
M
A
N
D
,
t
∈
s
u
p
p
l
y
]
link=[(s,t)|s∈DEMAND,t∈supply]
link=[(s,t)∣s∈DEMAND,t∈supply]
类似于demand和supply这种直接把元素列举出来的集合,称为基本集合,也称为原始集合;而把link这种基于其它集合派生出来的二维或者多维集合称为派生集合。demand和supply称为link的父集合。
赋值规则
lingo对数据是按列赋值的,不是按行赋值的。
所以:
x,y=5,1,2,7;
语句的实际赋值顺序是 x = ( 5 , 2 ) , y = ( 1 , 7 ) x=(5,2),y=(1,7) x=(5,2),y=(1,7)。
模型命名
可以在程序的开头部分用Title语句对这个模型取一个标题“Location Problem”;
model:
Title Location Problem;
...
end
对目标行([OBJ])和两类约束(DEMAND_CON、SUPPLY_CON)分别进行了命名。这是行号命名方法。
[OBJ] MIN=@SUM...;
@FOR(DEMAND(I):[DEMAND_CON]...);
@FOR(SUPPLY(I):[SUPPLY_CON]...);
稠密集合与稀疏集合
前面的例子中派生集合link的元素定义为demand和supply的笛卡尔积,即包含了两个基本集合构成的所有二元有序对。这种派生集合称为稠密集合。
在lingo中派生集合的元素可以定义为只是这个笛卡尔积的一个真子集合,这种派生集合称为稀疏集合。
例如,在进行最短路径搜索的时候采用的动态规划方法,对图进行广度优先搜索的一个例子,构造出图的邻接矩阵或者邻接表的时候可以用到。
稀疏集合构造分为两种,一个是枚举法,另一个是“元素过滤”法。所谓枚举法就是列举出派生稀疏集合中的每一个元素,而元素过滤法则是在集合定义的过程中添加过滤条件,这样就可以构造成适合的矩阵形式了。
例如:
sets:
students/S1..S8/;
pairs(students,students)| &2 #gt# &1:
benefit,match;
endsets
其中逻辑关系式“&2 #gt# &1”,意思是第2个父集合的元素的索引值(用“&2”表示)大于第1个父集合的元素的索引值(用“&1”表示)。
这里也就引出了索引的使用方法:
1、&符号索引,含义如上述解释;2、@index函数。例如:@index(S)表示元素S在集合中的索引值,实际上这个是@index(cities,S)的简写,即返回S在集合cities中的索引值。
一个代码示例
model:
sets:
students/S1..S8/;
pairs(students,students)| &2 #GT# &1:
benefit,match;
endsets
data:
benefit=
9 3 4 2 1 5 6
1 7 3 5 2 1
4 4 2 9 2
1 5 5 2
8 7 6
2 3
4;
enddata
[obj] max=@sum(pairs(i,j):benefit(i,j)*match(i,j));
@for(students(i):[constraints]
@sum(pairs(j,k)| j #EQ# i #OR# k #EQ# i: match(j,k))=1);
@for(pairs(i,j):@bin(match(i,j)));
end
lingo中的运算符
算术运算符
lingo中的算术运算符分为:
加(+),减(-),乘(*),除(/),求幂(^)
逻辑运算结果只有“真”和“假”两个值。
lingo中用数字1代表TRUE,其他值都是FALSE。在lingo中,逻辑运算表达式通常作为过滤条件使用。
逻辑运算符
lingo中的逻辑运算符有9种,可以分为两类:
(1)#and#(与),#or#(或),#not#(非):这三个运算是逻辑值之间的运算,也就是它们操作对象本身必须已经是逻辑值或逻辑表达式,计算结果也是逻辑值。
(2)#eq#(等于),#ne#(不等于),#gt#(大于),#ge#(大于等于),#lt#(小于),#le#(小于等于):这6个操作实际上是“数与数”之间的比较,也就是它们操作的对象本身必须是两个数(或相应的表达式),而逻辑表达式计算得到的结果是逻辑值。
关系运算符
关系运算符表示的是“数与数之间”的大小,因此在lingo中用来表示优化模型的约束条件,所以可以认为不是真正的运算操作符。
lingo中的关系运算符有三种:
<(即<=,小于等于);=(等于);>(即>=,大于等于)。
请注意区分关系运算符和“数与数之间”进行比较的6个逻辑运算符的不同之处。
一般而言,关系运算符常常出现在约束优化模型的约束中,而逻辑运算符多用于对集合进行约束和过滤。
运算符的优先级
同一个优先级按从左到右的顺序执行;如果有括号“()”,则括号内的表达式优先进行计算。
优先级 | 运算符 |
---|---|
最高(1) | #not# 、 -(负号) |
2 | ^ |
3 | * , / |
4 | +,-(减法) |
5 | #eq#,#ne#,#gt#,#ge#,#lt#,#le# |
6 | #and#,#or# |
7 | <,=,> |
集合使用小结
基本集合
基本集合的定义形式为:(凡是在方括号“[]”中的内容,表示是可选的项,即该项可以有也可以没有):
setname[/member_list][:attribute_list];
其中,setname为定义的集合名,member_list为元素列表,attribute_list为属性列表。元素列表可以采用显式列举法(即直接将所有元素全部列出,元素之间用逗号或空格分开),也可以采用隐式列举法。隐式列举法可以有几种不同格式:
基本集合的隐式列举法
类型 | 隐式列举格式 | 示例 | 示例集合表示的元素 |
---|---|---|---|
数字型 | 1…n | 1…5 | 1,2,3,4,5 |
字符-数字型 | stringM…stringN | Car101…car208 | car101,car102,…,car208 |
日期(星期)型 | dayM…dayN | MON…FRI | MON,TUE,WED,THU,FRI |
月份型 | monthM…monthN | OCT…JAN | OCT,NOV,DEC,JAN |
年份-月份型 | monthYearM…monthYearN | JAN2002 | DEC2001,JAN2002 |
上面的语法还说明元素列表和属性列表都是可选的。当属性列表不在集合定义中出现时,这样的集合往往只是为了将来在程序中作为一个循环变量来使用,或者作为构造更复杂的派生集合的父集合使用。(例如在介绍稠密集合中的students集合)。而当元素列表不在基本集合的定义中出现时,则必须在程序的数据段以赋值语句的方式直接给出元素列表。
例如:
sets:
quarters:dem,rp,op,inv;
!注意没有给出集合的元素列表;
endsets
data:
quarters dem=1 40 2 60 3 75 4 25;
!注意lingo按列赋值的特点;
enddata
派生集合
派生集合的一般定义格式为:
setname(parent_set_list)[/member_list/][:attribute_list];
与基本集合相比只是多了一个parent_set_list(父集合列表)。父集合列表中的集合(如:set1,set2,…)称为派生集合setname的父集合。它们本身也可以是派生集合。当元素列表(member_list)不在集合定义中出现时,还可以在程序的数据段的以赋值语句的方式给出元素列表;若在程序的数据段也不以赋值语句的方式给出元素列表,则认为定义的是稠密集合,即父集合中所有元素的有序组合(笛卡尔积)都是setname的元素。当元素列表在集合定义中出现时,又有“元素列表法”(直接列出元素)和“元素过滤法”(利用过滤条件)两种不同方式。
参考文献
谢金星,薛毅. 优化建模与LINDO/LINGO软件. 北京:清华大学出版社,2018.