GAMS教程 3:子集与条件表达式
我们在前面的章节中介绍了GAMS中的基础数据类型、GAMS程序的基本结构、GAMS语法、GAMS的基本运算符以及GAMS中的函数。
本节我们将继续深入,讲解GAMS中的子集与逻辑表达式。
1. 子集(Subset/Dynamic Set)
我们在最前面介绍了GAMS中的基础数据类型:集合(Set)、参数(Parameter)、表格(Table)、标量(Scalar)以及变量(Variable)
其中:
- 变量可以是集合、参数、表格、标量中的任意一种。
- 集合和参数、表格不同。集合起着索引的作用,而没有具体的值;参数和表格和具体得集合关联,并且对应集合中每一个元素都有对应的值。
本节中我们将介绍集合的另外一个知识,即子集。
GAMS中将数学上的子集(Subset)称为动态集合(Dynamic Set),而之所以将子集称为动态集合,是因为GAMS中允许向子集中随时增减元素。相反,前面的介绍的集合中的元素是不能动态增减的,因此相应的称为静态集合(Static Set)
A. 子集的声明
GAMS
中子集声明的语法如下
Set dynamic_set_name(parent_set_name) [/item1, {item2}/];
其中:
[]
表示可选项,{}
表示可重复项parent_set_name
表示子集的父集。需要注意的是,在声明子集的时候,一定要先声明父集。- 如果在声明子集的时候指定子集中包含的元素,那么子集中的元素必须包含在父集中。因为GAMS会进行范围检查(Domain Check),如果子集中存在不在父集中的元素的话,那么就会报错。
下面给出一个声明子集的例子。
Set item "all items" / dish, ink, lipstick, pen, pencil, perfume /,
subitem1(item) "first subset of item" / pen, pencil /,
subitem2(item) "second subset of item";
需要注意的是,子集的声明和参数很类似,都是变量名字(另一个集合名字)
的形式。
Set set_a /one, two, three, four/;
; 子集的()表示声明父集
Set subset(set_a) /one, four/;
; 参数的()是和索引集合关联
Parameter param(set_a) /"one" 1, "two" 2/;
虽然形式一样,但其实两者存在本质上的不同。GAMS
编译器会根据上下文来区别对待。
B. 子集动态元素增减
我们前面说过,之所以GAMS
中将子集称为动态集合,就是因为GAMS中的子集可以动态增减元素。
GAMS
中子集动态增减元素的语法如下
set_name(index_list | label) = yes | no ;
其中:
yes
/no
分别表示将元素加入集合或者从集合中删除index_list
表示对某个集合整体设置,而label
表示对某个元素进行设置
子集的动态增减如下
Set item "all items" / dish, ink, lipstick, pen, pencil, perfume /
subitem1(item) "first subset of item" / pen, pencil /
subitem2(item) "second subset of item";
* subitem1 = /pen, pencil/;
* subitem2 = null;
subitem1('ink') = yes; * subitem1 = /pen, pencil, ink/;
subitem1('lipstick') = yes; * subitem1 = /pen, pencil, ink, lipstick/;
subitem2(item) = yes; * subitem2 = /disk, ink, lipstick, pen, pencil, perfume/;
subitem2('perfume') = no ; * subitem2 = /disk, ink, lipstick, pen, pencil/;
index_list
可以是一个静态集合,也可以是一个动态集合,即子集。
2. 条件表达式
GAMS
中提供了条件表达式,我们下面就将介绍进行介绍。
1. 条件表达式的声明
GAMS
中条件表达式的语法如下
term $ logical_condition
即
项 $ 逻辑表达式
条件表达式类似于C语言中的三元运算符,但是相比于三元运算符能够根据逻辑表达语句的值在两个项中选择,GAMS
中的条件表达式只能根据逻辑表达式的值在有项和无项之间选择。
例如下面这段C语言代码
if (b > 1.5)
a = 2;
else
a = null;
注意,这里的null
并不是C语言中的null
,而是直接没有这项。上面的C语言的描述使用GAMS
的条件表达式描述,如下
a $ (b > 1.5) = 2 ;
再举例来说
Scalar x /15/;
Scalar y;
y = 12$(x < 20);
表示如果x
的值小于20
,那么y=12
,否则y
的值就依旧是待定的。
再举一个例子,如果我们想要实现下面这段C语言程序
if (x < 20)
y = 12;
else
y = 21;
对应的GAMS
的语句为
Scalar x /15/;
Scalar y;
y = 12$(x < 20) + 21$(x >= 20);
注意GAMS
中的条件语句在条件不成立的时候直接就没有这一项,所以不要看这里有两个条件语句,y = 12$(x < 20) + 21$(x >= 20)
这个表达式,其实在任何时候都只有一项。当然这个其实还和我们的条件有关。
Set A /1, 2, 3, 4, 5, 6/;
Parameter param(A);
param(A) = 1$(ord(A) < 4);
2. 条件表达式与参数
我们上面的例子中展示了如何使用条件表达式为一个标量赋值,条件表达式其实也能够为参数赋值
子集
对于逻辑表达式与子集的结合,我们举例如下
Set i / i1*i5 /
j(i) / i1*i3 / ;
Parameter s(i) / i1 3, i2 5, i3 11, i4 8, i5 1 /
t(i);
t(i) $ j(i) = s(i) + 3;
首先,集合j
是集合i
的子集,而逻辑表达式t(i)$j(i)
则表示对于集合j(i)
中的所有值。
因此参数t(i)
最终的值为
t(i) = / "i1" 6, "i2" 8, "i3" 14/;
参数
对于逻辑表达式与参数的使用,我们举例如下
Set i / i1*i5 /
Parameter s(i) / i1 3, i2 5, i3 11, i4 8, i5 1 /
t(i);
t(i) $ (s(i) < 6) = s(i) + 3;
首先,s(i)
与t(i)
都是参数,而逻辑表达式t(i) $ (s(i) < 6)
表示遍历s
中的所有元素。当s(i)
的值小于6时,存在t(i)
这一项,否则就不存在。
所以t(i)
最终的值为
t(i) = /"i1" 6, "i2" 8, "i5" 4/
ord函数
逻辑表达式与ord
函数的联合使用如下
Set I /a, b, c, d, e, f/;
Parameter X(I) /"a" -1, "b" -2, "c" -3, "d" -4, "e" -5, "f" -6/;
Parameter A(I);
A(I) = 1$(ord(I) <= 2) + X(I-2)$(ord(I) > 2);
ord
函数将会返回一个参数,具体来说,该参数的值为/"a" 1, "b" 2, "c" 3, "d" 4, "e" 5, "f" 6/
那么这个时候,情况就转为了上面逻辑表达式与参数的情况。此外,还需要注意的是X(I-2)
这一项。GAMS
中对参数取值的时候,对index
集合进行减法,则只有当括号里的值大于0的时候,才会获取参数的内容。
所以对于X(I-2)
这一项,当I > 2
的时候,才会取参数X
中的值。
所以综上,表达式A(I) = 1$(ord(I) <= 2) + X(I-2)$(ord(I) > 2)
的意思就是,让A(I)
的前两项是1,后面的项等于X
的前面的项。
所以A(I)
最终的值为:
A(I) = /"a" 1, "b" 1, "c" -1, "d" -2, "e" -3, "f" -4/;
3. 逻辑表达式与逻辑运算符
上面我们说GAMS
中的条件表达式是根据逻辑表达式的值来计算项的,但是我们其实并没有详细的介绍GAMS
中的逻辑表达式。
我们下面就将详细讲解GAMS
中的逻辑表达式,以及与此相关的逻辑运算符。
逻辑表达式
GAMS
中的逻辑表达式的值判断准则很简单,计算得到的逻辑表达式的值为0则为False,除此以外所有值都为True
例如下面的例子
b $ (2*a - 4) = 7;
当a = 2
时,逻辑表达式2*a - 4
的逻辑值为False,其他时候都是True。
注意,集合没有值,而参数有值,所有以后有些情况我们可以这样写
Set i /a * e/,
j(i) /a, b, c/;
Parameter pm(i) /"a" 1, "b" 2, "c" 3, "d" 4, "e" 5/,
p1(i),
p2(i);
* 下面的式子因为pm是参数,所有pm(i)是有值的,因此下面的式子表示当pm(i)不等于0的时候,p1(i)等于1
p1(i) $ (pm(i)) = 1;
* 下面的式子因为j是子集,所以表达的意思遍历子集j中的所有元素
p2(i) $ (j(i)) =
逻辑运算符
GAMS
中有很多用于计算逻辑表达式的值的运算符,具体来说有有
4. 嵌套条件表达式
GAMS
中允许使用嵌套表达式,即
term $ (logical_condition1$(logical_condition2$(...)))
但是其实嵌套表达式可以使用and
语句来连接,两者在本质上是等价的。
term $ (logical_condition1 and logical_condition2 and ...)
举一个嵌套条件表达式的例子。假设i
是一个静态集合,而k
和j
都是i
的子集,v
和u
都是参数。那么下面的式子表示让j
和v
的交集中的元素等于v(i)
中对应的值。
u(i) $ (j(i)$k(i)) = v(i) ;
我们举具体得值为例
Sets i /i1 * i6/,
j(i) /i1 * i4/,
k(i) /i3 * i6/;
Parameters v(i) /"i1" -1, "i2" -2, "i3" -3, "i4" -4, "i5" -5, "i6" -6/,
u(i);
u(i) $ (j(i)$k(i)) = v(i) ;
display u;
则最终u
的值为
u = /"i3" -3, "i4" -4/;
话说回来,其实嵌套的条件表达式并不推荐使用,最好还是使用and
条件运算符。
上面的条件用and
条件运算符的版本为
u(i) $ (j(i) and k(i)) = v(i) ;
5. 条件表达式与赋值语句
事实上,条件表达式的位置不同,赋值的结果也不同。我们上面出现了条件表达式在=
左边、在=
右边,但是我们其实没有强调在左边和在右边之间的不同。
其实这两者还是存在微妙的不同的,下面我们就将介绍之间的不同。
$() = xxx:条件表达式在左
条件表达式在=
左边表示只有当条件表达式值为真的时候,才会有整个赋值表达式。
例如下面的例子
rho(i) $ (sig(i) <> 0) = (1./sig(i)) - 1. ;
假设sig
是一个参数,则只有当sig(i)
不等于0的时候,才会有rho(i) = (1./sig(I)) -1.
这个赋值表达式。
当然,上面说过了只有非0值的逻辑表达式的值为True,所以上面的式子其实可以这样简写
rho(i) $ sig(i) = (1./sig(i)) - 1. ;
于此类似的,所有不等于的某个数x
的式子可以这样写
s(i) $ (t(i) - x) = t(i);
xxx $= xxx:条件表达式在中间
当条件表达式在中间的时候,形式上有些奇怪,因为没有逻辑表达式。具体形式为
项 $= 项
而条件表达式在中间的含义就是只有当右侧的值不为0的时候,才会有整个赋值表达式。
例如
Set i /a,b,c/
Parameter d2(i) "Data to be used to overwrite d1" /a 0, b -2 /
d3(i) "Empty data parameter";
d3(i) $= d2(i);
* Result: d3('a')=1; d3('b')=-2; d3('c')=1;
xxx = $():条件表达式在右边
当条件表达式在=
右侧的时候,表示逻辑表达式的值不为0时,条件表达式的值为项的值,否则为0。
例子就是前面的例子:
A(I) = 1$(ord(I) <= 2) + X(I-2)$(ord(I) > 2);
6. 条件表达式与等式(Equation)
GAMS
中的等式与赋值语句是完全不同的两码事。等式就是首先使用equation
声明,而后具体定义的式子。等式中使用的符号和正常的=
、<
、>
不同,就是因为等式是和赋值语句是完全不同的两码事。
上面介绍了赋值语句和条件表达式的细节,下面将介绍等式与条件表达式之间的细节。
条件表达式出现在等式定义中
当条件表达式出现在等式中,含义其实和条件表达式出现在赋值语句的右侧相同,都是当逻辑表达式的值不为0的时候,存在该项,否则该项为0
注意,在等式中,条件表达式和等式中的等号=e=
的位置其实没有关系,不管在那边都是上面的含义。
所以下面的式子
equation eq2;
eq2(i)..
sum(j, x(i,j)) $ b =e= -s(i) $ b;
表达就是当b = 0
时,等式eq2
就被定义为0 =e= 0
条件表达式出现在等式定义前
当条件表达式出现在等式定义前,表达的含义是当逻辑表达式的值为0的时候,不会定义该等式,否则定义该等式
所以下面的式子
equation eq1(i);
eq1(i) $ b..
sum(j, x(i,j)) =g= -s(i);
表达的就是当b = 0
时,不定义等式eq1(i)
,所以此时,仅有eq1(i)
的声明。如果稍后不定义而直接在模型中使用的话,就会报错。