0 前言
2023/10/14:增加代码说明、参考资料;
2015/08/25:首次发布;
非常感慨,这篇文章距离我再次修改竟然已经是八年之后了。八年前的那个大二结束后的暑假,我留校参加了数学建模竞赛的集训,在这道题的训练中,我学会了使用Lingo去解决一些最优化的问题,于是就将学习的笔记和对这道题的解题思路发表了在了博客上。时过境迁,其中的一些细节我自己都有些模糊,更何论是访问这篇文章的同学。因此我决定再优化一下这篇博客,希望能给之后访问到这篇博客的同学一些帮助。本文的思路也不一定对,仅供大家参考。
参考资料:
1 题目介绍
第四次数模培训,2014年研究生数学建模竞赛E题:乘用车物流运输计划问题。
“轿运车”是通过公路来运输乘用车整车的专用运输车,根据型号的不同有单层和双层两种类型,由于单层轿运车实际中很少使用,本题仅考虑双层轿运车。双层轿运车又分为三种子型,每辆轿运车可以装载乘用车的最大数量在6到27辆之间。
- 上下层各装载1列乘用车,故记为1-1型(图1):
- 下、上层分别装载1、2列,记为1-2型(图2):
- 上、下层各装载2列,记为2-2型(图3):
在确保完成运输任务的前提下,物流公司追求降低运输成本。但由于轿运车、乘用车有多种规格等原因,当前很多物流公司在制定运输计划时主要依赖调度人员的经验,在面对复杂的运输任务时,往往效率低下,而且运输成本不尽理想。请你们为物流公司建立数学模型,给出通用算法和程序(评审时要查)。
装载具体要求如下:每种轿运车上、下层装载区域均可等价看成长方形,各列乘用车均纵向摆放,相邻乘用车之间纵向及横向的安全车距均至少为0.1米,下层力争装满,上层两列力求对称,以保证轿运车行驶平稳。受层高限制,高度超过1.7米的乘用车只能装在1-1、1-2型下层。轿运车、乘用车规格(第五问见附件)如下:
- 乘用车规格
乘用车型号 | 长度(米) | 宽度(米) | 高度(米) |
---|---|---|---|
Ⅰ | 4.61 | 1.7 | 1.51 |
Ⅱ | 3.615 | 1.605 | 1.394 |
Ⅲ | 4.63 | 1.785 | 1.77 |
- 轿运车规格
轿运车类型 | 上下层长度(米) | 上层宽度(米) | 下层宽度(米) |
---|---|---|---|
1-1 | 19 | 2.7 | 2.7 |
1-2 | 24.3 | 3.5 | 2.7 |
整车物流的运输成本计算较为繁杂,这里简化为:
- 影响成本高低的首先是轿运车使用数量;
- 其次,在轿运车使用数量相同情况下,1-1型轿运车的使用成本较低,2-2型较高,1-2型略低于前两者的平均值,
- 但物流公司1-2型轿运车拥有量小,为方便后续任务安排,每次1-2型轿运车使用量不超过1-1型轿运车使用量的20%;
- 再次,在轿运车使用数量及型号均相同情况下,行驶里程短的成本低,注意因为该物流公司是全国性公司,在各地均会有整车物流业务,所以轿运车到达目的地后原地待命,无须放空返回。最后每次卸车成本几乎可以忽略。
前三问:
1. 物流公司要运输Ⅰ车型的乘用车100辆及Ⅱ车型的乘用车68辆。
2. 物流公司要运输Ⅱ车型的乘用车72辆及Ⅲ车型的乘用车52辆。
3. 物流公司要运输Ⅰ车型的乘用车156辆、Ⅱ车型的乘用车102辆及Ⅲ车型的乘用车39辆。
要求制定详细计划,含所需要各种类型轿运车的数量、每辆轿运车的乘用车装载方案、行车路线。(前三问目的地只有一个,可提供一个通用程序)
2 解决方案
2.1 使用matlab将每种轿用车的满载方案求出来
I-I型轿用车:
clear all;
clc;
% 1-1车型上层只能装1,2型车
m = 1;
for a = 0:4 %1型车,最多装4辆
for b = 0:5 %2型车,最多装5辆
% 满装策略,1,2型乘用车总长小于1-1型车且剩余空间无法放下一辆2型车(2型车最短)
if (19- (a*4.71+b*3.715) ) <= 3.715&&(a*4.71+b*3.715)<=19
Up(m,1) = a;
Up(m,2) = b;
Up(m,3) = 0;
m = m + 1;
end
end
end
% 1-1车型下层可以装1,2,3型车
n = 1;
for a= 0:4 %1型车,最多装4辆
for b = 0:5 %2型车,最多装5辆
for c = 0:4 %3型车,最多装4辆
% 满装策略,1,2型乘用车总长小于1-1型车且剩余空间无法放下一辆2型车(2型车最短)
if (a*4.71+b*3.715+c*4.73)<=19&&(19-(a*4.71+b*3.715+c*4.73))<=3.715
Down(n,1) = a;
Down(n,2) = b;
Down(n,3) = c;
n = n +1;
end
end
end
end
% 上下层结果统计
k = 1;
for i = 1:(m-1)
for j = 1:(n-1)
% 1型车的装载情况
Total(k,1) = Up(i,1)+Down(j,1);
% 2型车的装载情况
Total(k,2) = Up(i,2)+Down(j,2);
% 3型车的装载情况
Total(k,3) = Up(i,3)+Down(j,3);
k = k + 1;
end
end
这里算出来一共有(m-1) x (n-1) = 75种方案,将每种方案1,2,3型车的装在情况写在一个txt中,每行一个数字,1,2,3型乘用车之间用~
隔开,如下所示:
!1-1,1型号车装载情况:
1
2
…
~
!1-1,2型号车装载情况:
1
2
…
~
!1-1,3型号车装载情况:
1
2
…
~
I-II型轿用车
clear all;
clc;
% 1-2车型下层满装策略
m = 1;
for a=0:5 %1型车,最多装5辆
for b=0:6 %2型车,最多装6辆
for c=0:5 %3型车,最多装5辆
% 满装策略,车总长小于1-2型车长度且剩余空间无法放下一辆2型车(2型车最短)
if (a*4.71+b*3.715+c*4.73)<=24.3&&(24.3-(a*4.71+b*3.715+c*4.73))<=3.715
Down(m,1)=a;
Down(m,2)=b;
Down(m,3)=c;
m = m + 1;
end
end
end
end
% 1-2车型上层满装策略
n =1;
for a=0:5 %1型车,最多装5辆
for b=0:6 %2型车,最多装6辆
% 单列满装策略,车总长小于1-2型车长度且剩余空间无法放下一辆2型车(2型车最短)
if (a*4.71+b*3.715)<=24.3&&(24.3-(a*4.71+b*3.715))<=3.715
Up(n,1)=2*a;
Up(n,2)=2*b;
Up(n,3) = 0;
n = n + 1;
end
end
end
k = 1;
for i = 1:(n-1)
for j = 1:(m-1)
% 1型车的装载情况
Total(k,1) = Up(i,1)+Down(j,1);
% 2型车的装载情况
Total(k,2) = Up(i,2)+Down(j,2);
% 3型车的装载情况
Total(k,3) = Up(i,3)+Down(j,3);
k = k + 1;
end
end
与1-1一样,将1,2,3类型的乘用车的装载情况写入txt中,并用~
隔开。
2.2 使用Lingo进行线性规划
代码如下
model:
sets:
!1-1型车装载方案,共75个:x1,y1,z1分别代表每种方案1,2,3型车的装载数量;
YI/1..75/:x1,y1,z1,m;
!1-2型车装载方案,共13个:x2,y2,z2分别代表每种方案1,2,3型车的装载数量;
ER/1..132/:x2,y2,z2,n;
endsets
data:
x1 = @file('1-1.txt');
y1 = @file('1-1.txt');
z1 = @file('1-1.txt');
x2 = @file('1-2.txt');
y2 = @file('1-2.txt');
z2 = @file('1-2.txt');
enddata
!第一问: 运输Ⅰ车型的乘用车100辆及Ⅱ车型的乘用车68辆;
@sum(YI(I):x1(I)*m(I))+@sum(ER(J):x2(J)*n(J)) >= 100;
@sum(YI(I):y1(I)*m(I))+@sum(ER(J):y2(J)*n(J)) >= 68;
!第二问: Ⅱ车型的乘用车72辆及Ⅲ车型的乘用车52辆;
!@sum(YI(I):x1(I)*m(I))+@sum(ER(J):x2(J)*n(J)) = 0;
!@sum(YI(I):y1(I)*m(I))+@sum(ER(J):y2(J)*n(J)) >= 72;
!@sum(YI(I):z1(I)*m(I))+@sum(ER(J):z2(J)*n(J)) >= 52;
!第三问: Ⅰ车型的乘用车156辆、Ⅱ车型的乘用车102辆及Ⅲ车型的乘用车39辆;
!@sum(YI(I):x1(I)*m(I))+@sum(ER(J):x2(J)*n(J)) >= 156;
!@sum(YI(I):y1(I)*m(I))+@sum(ER(J):y2(J)*n(J)) >= 102;
!@sum(YI(I):z1(I)*m(I))+@sum(ER(J):z2(J)*n(J)) >= 39;
!1-2车的数量不超过1-1的20;
@sum(ER(I):n(I)) <= 0.2*@sum(YI(J):m(J));
!最小化乘用车数量;
min = @sum(ER(I):n(I)) + @sum(YI(J):m(J));
!限制m,n为正整数,表示每种方案的数量;
@for(YI:@gin(m));
@for(ER:@gin(n));
end
3 Lingo学习的总结
3.1 集的理解
sets:
YI/1..75/:x1,y1,z1,m;
ER/1..132/:x2,y2,z2,n;
endsets
在sets 和 endsets命令中设置集,集给人的体验就是首先有一个集的名称,紧接着在集名称后面接上/ /
,双斜杠中的内容称之为集的成员,接上:
后就是集的属性,这样一个集就生成了。
其感觉就像是集的属性相当于几个数组,而数组的大小就是集成员的大小,而且这个数组的地址不是数字,就是集的成员,比如集的成员为/mon..sun/
,那么这个集的属性A就是A[mon]~A[sun]
。
3.2 Lingo中数据的定义
data:
x1 = @file('1-1.txt');
y1 = @file('1-1.txt');
z1 = @file('1-1.txt');
x2 = @file('1-2.txt');
y2 = @file('1-2.txt');
z2 = @file('1-2.txt');
enddata
在lingo中数据的定义是在data: 和 enddata中完成的,数据的内容基本上是集的属性,在上述例子中我用到了一个@file函数,这个函数就是lingo中的读取文本文件的函数,其用法就是@file(‘文件地址\文件名.txt’),在txt中的数据要用~
分开。其意思就是说我每读取一次文件,读取到~
位置就停止,当下一次读取这个文件的时候,从上次的~
读取到下次的~
之间的数据。
3.3 @sum的使用
@sum函数一般使用在求和上,其用法就是@sum(集名:要求和的函数)。
其中,如果是对这个集中某个属性的部分数据进行求和,那么可以在集名后加上或符号再跟上你要约束的条件,即:集名|约束条件
3.4 @for的使用
@for函数一般用于约束条件的定义,如果约束条件过多,且具有一定的相似性,那么就可以中@for函数来声明。使用案例:
@for(YI:@gin(m));
比如上述的代码中,YI代表你要循环声明的集,或者是范围,跟上:
,后面接上你要循环的内容,在上述的代码中我的意思就是声明YI这个集的属性m全为整数。
3.5 @gin
限制变量只能为正整数。