目录
前言
对实际问题建模的时候,总会遇到一群或多群相联系的对象,比如工厂、消费者群体、 交通工具和雇工等等。LINGO 允许把这些相联系的对象聚合成集(sets)。一旦把对象聚合成集,就可以利用集来最大限度的发挥 LINGO 建模语言的优势。集是 LINGO 建模语言的基础,是程序设计最强有力的基本构件。借助于集,能够用一个单一的、长的、简明的复合公式表示一系列相似的约束,从而可以快速方便地表达规模较大的模型。
一、集是什么?
集是一群相联系的对象,这些对象也称为集的成员。一个集可能是一系列产品、卡车或 雇员。每个集成员可能有一个或多个与之有关联的特征,我们把这些特征称为属性。例如,产品集中的每个产品可以有一个价格属性;卡车集中的每辆卡车可以有一个牵引力属性;雇员集中的每位雇员可以有一个 薪水属性,也可以有一个生日属性等等。(PS:属性值可以预先给定,也可以是未知的,有待于 LINGO 求解。)
当然,你可以用c语言来类比:
集 ←→ 结构体
集成员 ←→ 结构体的域
集属性 ←→ 结构体实例
集的类型
总的来说,LINGO 可识别的集只有两种类型:原始集和派生集。
一个原始集是由一些最基本的对象组成的。
一个派生集是用一个或多个其它集来定义的,也就是说,它的成员来自于其它已存在的集。
1、定义一个原始集
注意:集部分以关键字“sets:”开始,以“endsets”结束。原始集,定于语法:
setname/member_list(成员变量)/:attribute_list(属性)
当然Member_list 是集成员列表。如果集成员放在集定义中,那么对它们可采取显式罗列和隐式罗列两种方式。如果集成员不放在集定义中,那么可以在随后的数据部分定义它们。
① 当显式罗列成员时,必须为每个成员输入一个不同的名字,中间用空格或逗号搁开, 允许混合使用。
② 当隐式罗列成员时,不必罗列出每个集成员。可采用如下语法: setname/member1..memberN/attribute_list;
这里的 member1 是集的第一个成员名,memberN 是集的最末一个成员名。LINGO 将自动产生中间的所有成员名。LINGO 也接受一些特定的首成员名和末成员名,用于创建一些特殊的集。
下面我们定义一个原始集:
注释为开头用感叹号(!),末尾用分号(;)表示注释,可跨多行。
!这个表示注释;
data:
students,sex,age= John 1 16
Jill 0 14
Rose 0 17
Mike 1 13;
enddata
2、定义一个派生集
派生集的语法定义:
setname(parent_set_list)/member_list/attribute_list;
setname 是集的名字。parent_set_list 是已定义的集的列表,多个时必须用逗号隔开。 如果没有指定成员列表,那么 LINGO 会自动创建父集成员的所有组合作为派生集的成员。派生集的父集既可以是原始集,也可以是其它的派生集。
sets:
product/A B/;
machine/M N/;
week/1..2/;
allowed(product,machine,week):x;
endsets
运行之后结果为
成员列表被忽略时,派生集成员由父集成员所有的组合构成,这样的派生集成为稠密集。 如果限制派生集的成员,使它成为父集成员所有组合构成的集合的一个子集,这样的派生集成为稀疏集。同原始集一样,派生集成员的声明也可以放在数据部分。一个派生集的成员列表有两种方式生成:①显式罗列;②设置成员资格过滤器。当采用方式①时,必须显式罗列出所有要包含在派生集中的成员,并且罗列的每个成员必须属于稠密集。使用前面的例子, 显式罗列派生集的成员:
allowed(product,machine,week)/A M 1,A N 2,B N 1/;
如果需要生成一个大的、稀疏的集,那么显式罗列就很讨厌。幸运地是许多稀疏集的成员都 满足一些条件以和非成员相区分。我们可以把这些逻辑条件看作过滤器,在 LINGO 生成派生集的成员时把使逻辑条件为假的成员从稠密集中过滤掉。
例如:
sets:
!学生集:性别属性 sex,1 表示男性,0 表示女性;年龄属性 age. ;
students/John,Jill,Rose,Mike/:sex,age;
!男学生和女学生的联系集:友好程度属性 friend,[0,1]之间的数。 ;
linkmf(students,students)|sex(&1) #eq# 1 #and# sex(&2) #eq# 0: friend;
!男学生和女学生的友好程度大于 0.5 的集;
linkmf2(linkmf) | friend(&1,&2) #ge# 0.5 : x;
endsets
data:
sex,age = 1 16
0 14
0 17
0 13;
friend = 0.3 0.5 0.6;
enddata
用竖线(|)来标记一个成员资格过滤器的开始。#eq#是逻辑运算符,用来判断是否“相等”。
&1 可看作派生集的第 1 个原始父集的索引,它取遍该原始父集的所有成 员;&2 可看作派生集的第 2 个原始父集的索引,它取遍该原始父集的所有成员;&3,&4,„„, 以此类推。
注意如果派生集 B 的父集是另外的派生集 A,那么上面所说的原始父集是集 A 向前回溯到最终的原始集,其顺序保持不变,并且派生集 A(linkmf) 的过滤器对派生集 B(linkmf2) 仍然有效。
我们可以注意到X(JOHN,ROSE)和X(JOHN,MIKE)被筛选出来了。
二、模型的数据部分和初始部分
在处理模型的数据时,需要为集指派一些成员并且在 LINGO 求解模型之前为集的某些属 性指定值。为此,LINGO 为用户提供了两个可选部分:输入集成员和数据的数据部分(Data Section)和为决策变量设置初始值的初始部分(Init Section)。
1、模型的数据部分
数据部分以关键字“data:”开始,以关键字“enddata”结束。
在这里,可以指定集成员、集的属性。其语法如下: object_list = value_list;例如:
sets:
set1/A,B,C/: X,Y;
endsets
data:
X=1,2,3;
Y=4,5,6;
enddata
当然,在某些情况,对于模型中的某些数据并不是定值。譬如模型中有一个通货膨胀率的参数, 我们想在 2%至 6%范围内,对不同的值求解模型,来观察模型的结果对通货膨胀的依赖有多 么敏感。我们把这种情况称为实时数据处理(what if analysis)。LINGO 有一个特征可方 便地做到这件事。 在本该放数的地方输入一个问号(?)。
data:
interest_rate,inflation_rate = .085 ?;
enddata
每一次求解模型时,LINGO 都会提示为参数 inflation_rate 输入一个值。在 WINDOWS 操作 系统下,将会接收到一个类似下面的对话框:
直接输入一个值再点击 OK 按钮,LINGO 就会把输入的值指定给 inflation_rate,然后继续 求解模型。 除了参数之外,也可以实时输入集的属性值,但不允许实时输入集成员名。
数据部分的未知数值
有时只想为一个集的部分成员的某个属性指定值,而让其余成员的该属性保持未知,以便让LINGO 去求出它们的最优值。在数据声明中输入两个相连的逗号表示该位置对应的集成 员的属性值未知。两个逗号间可以有空格。
sets:
years/1..5/: capacity;
endsets
data:
capacity = ,34,20,,;
enddata
模型的初始部分
初始部分是 LINGO 提供的另一个可选部分。在初始部分中,可以输入初始声明 (initialization statement),和数据部分中的数据声明相同。对实际问题的建模时,初始部分并不起到描述模型的作用,在初始部分输入的值仅被 LINGO 求解器当作初始点来用,并且仅仅对非线性模型有用。和数据部分指定变量的值不同,LINGO 求解器可以自由改变初 始部分初始化的变量的值。
一个初始部分以“init:”开始,以“endinit”结束。
初始部分的初始声明规则和数据 部分的数据声明规则相同。也就是说,我们可以在声明的左边同时初始化多个集属性,可以把集属性初始化为一个值,可以用问号实现实时数据处理,还可以用逗号指定未知数值。好的初始点会减少模型的求解时间。
init:
X, Y = 0, .1;
endinit
Y=@log(X);
X^2+Y^2<=1;
总结
今天主要就是介绍关于如何去定义一个集以及它的数据。