LINGO最小费用流建模笔记|嵌套@for+@sum、哑变量共享绑定理解记录
背景:课堂最小费用流(中转运输问题)案例,记录对两段嵌套约束的理解,重点理解LINGO集合哑变量的共享绑定规则,模型可正常求解最优解
案例描述如下图
模型完整代码
model:
sets:
node/F1 F2 DC W1 W2/:capacity;
link(node,node)/F1,W1 F1,DC DC,W1 DC,W2 F2,W2 F2,DC/:
maxvolume,cost,volume;
endsets
data:
capacity = 80,70,0,60,90;
maxvolume = 1000 50 50 50 1000 50;
cost = 700 300 200 400 1000 500;
enddata
!工厂流出平衡约束
@for(node(i)|i#EQ#1 #OR# i#EQ#2:
@sum(link(i,j):volume(i,j)) = capacity(i)
);
!需求点流入平衡约束
@for(node(j)|j#EQ#4 #OR# j#EQ#5:
@sum(link(i,j):volume(i,j)) = capacity(j)
);
!DC中转节点流量守恒约束
@for(node(i)|i#EQ#3:
@sum(link(i,j):volume(i,j)) = @sum(link(j,i):volume(j,i))
);
!链路容量上限
@for(link:volume<=maxvolume);
!目标函数:最小总运费
min=@sum(link:volume*cost);
end
节点说明:
- F1、F2:工厂(供应点)
- DC:中转仓(中转节点,无供给、无需求)
- W1、W2:需求仓库
链路清单:
- F1,W1:上限1000,单位运费700
- F1,DC:上限50,单位运费300
- DC,W1:上限50,单位运费200
- DC,W2:上限50,单位运费400
- F2,W2:上限1000,单位运费1000
- F2,DC:上限50,单位运费500
一、需求点约束理解(W1、W2流入平衡)
@for(node(j)|j#EQ#4 #OR# j#EQ#5:
@sum(link(i,j):volume(i,j)) = capacity(j)
);
运行逻辑:
-
@for设置过滤条件j#EQ#4 #OR# j#EQ#5,只会迭代node集合索引为4、5的节点,也就是 W1、W2; -
LINGO嵌套结构中,外层哑变量在内部表达式共享绑定,内层
link(i,j)里的j直接复用外层固定的j值;i是当前@sum内部独立的局部哑变量; -
自动筛选
link稀疏集合中,终点为当前j的所有边:- j=4(W1):匹配边
F1,W1、DC,W1 - j=5(W2):匹配边
DC,W2、F2,W2
- j=4(W1):匹配边
-
分别对匹配到的链路运量
volume累加求和; -
约束含义:W1、W2的总流入量 = 自身需求capacity,保证需求被满足。
二、中转DC节点流量守恒约束理解
@for(node(i)|i#EQ#3:
@sum(link(i,j):volume(i,j)) = @sum(link(j,i):volume(j,i))
);
运行逻辑:
@for设置过滤条件i#EQ#3,只迭代索引为3的节点DC,最终只会生成一条约束;- 外层哑变量
i固定等于3(DC),左右两个sum里面的i都复用这个固定值; - 左侧
@sum(link(i,j):volume(i,j)):i固定为DC(DC作为起点),局部哑变量j遍历link里DC出发的边:DC,W1、DC,W2,求和得到DC总流出量; - 右侧
@sum(link(j,i):volume(j,i)):i固定为DC(DC作为终点),局部哑变量j遍历link里流向DC的边:F1,DC、F2,DC,求和得到DC总流入量; - 约束含义:中转仓DC的流入总量 = 流出总量,中转节点不积存货物,满足流量守恒。
三、补充:一组可行解参考
满足全部约束的一组可行方案(LINGO会在可行域内寻找成本最低的最优解)
| 链路 | 运量 | 链路 | 运量 |
|---|---|---|---|
| F1→W1 | 30 | F1→DC | 50 |
| DC→W1 | 30 | DC→W2 | 50 |
| F2→W2 | 40 | F2→DC | 30 |
校验:
- F1流出:30+50=80 ✔ F2流出:40+30=70 ✔
- W1流入:30+30=60 ✔ W2流入:40+50=90 ✔
- DC流入:50+30=80,DC流出:30+50=80 ✔
- 所有链路运量均不超过容量上限 ✔
该方案总运费:30*700 + 50*300 + 30*200 + 50*400 + 40*1000 + 30*500 = 117000
核心知识点总结
- LINGO中i/j这类标识是集合哑变量,不是决策变量,仅作为遍历标记,不参与求解;
- 嵌套结构规则:外层
@for声明并固定哑变量后,内层@sum可以直接复用这个哑变量(共享绑定);如果在集合括号内重新声明同名哑变量,则会发生局部覆盖; - 复合集合
link只会遍历预先定义好的边,不会自动从1开始遍历; @for负责批量生成约束方程,@sum负责累加计算,二者分工不同;