题目背景
-
在本问题中,电网企业利用无人机及其航空站定期对输电铁塔执行巡检操作。每个航空站的空间是有限的,仅能同时容纳一台无人机和固定数量的电池。不同型号的航空站可容纳的无人机数量不一致。并且,无人机电池的续航能力有限,不能巡检距离所属航空站过大的铁塔(假设所有无人机和电池的型号是一致的)。初始状态下,所用无人机均停放在其所属的航空站中,并且所用电池均是充满电的。为了确保无人机拍摄的热成像检测图片能及时反馈回控制中心,需要无人机每次巡检一个输电铁塔便立即返回所属航空站,利用航空站的通讯仪器将图片快速传回控制中心。无人机每次返回航空站时,如果电量不足可立即切换另一块充满电的电池。
-
电网企业旨在于一系列候选点上建立一定数量的航空站,并配备一台无人机和一定数量的电池,使得无人机能够巡检完所有的输电铁塔。电网企业的核心问题在于确定航空站的位置决策、电池的配备决策以及输电铁塔由哪块电池服务的决策。假设有
N个铁塔和M个航空站候选点,巡检铁塔n所需的电量为,无人机从铁塔n飞到航空站候选点m或从航空站候选点m飞到铁塔n所需的电力为。航空站有T种类型,类t的航空站可容纳电池数量为,类型t的航空站建立在候选点m上的成本为。每个候选点只能建立一个航空站, 每块无人机电池的配备成本为h,一块充满电的无人机电池电量为E。假设初始状态下所有电池均为满电,被使用后不能在整个巡检过程中补充能量。
题目内容
- 问题:考虑每类航空站可容纳不同数量的无人机电池,不同候选点不同类型航空站对应着不同的建立成本系数。以最小化航空站选址成本和电池配备成本的总和为目标,构建基于航空站的无人机电力巡检问题的数学模型。建立该问题的数学模型,并编程求解表中给出的算例,要求最终确定航空站建立的位置和建立的类型、每个航空站配备电池的数量以及无人机切换电池的方案和巡检输电铁塔的顺序,以及最优的总成本。
| 参数 | 注释 | 取值 |
|---|---|---|
| N | 输电铁塔数量 | 15 |
| M | 航空站候选点数量 | 5 |
| T | 航空站种类数量 | 2 |
| {f1, f2, … … , f15 } | 巡检铁塔所需的电量 | {10 ,5 ,7 ,6 ,8, 10 ,5 ,7 ,6, 8, 10 ,5 ,7 ,6 ,8} |
| {a1, a2 } | 航空站容纳电池数量 | {5 ,10} |
| h | 每块电池配备成本 | 30 |
| E | 满电电池的电量 | 80 |
| 航空站类型\候选点铁塔 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 1 | 15 | 9 | 15 | 17 | 10 |
| 2 | 20 | 5 | 20 | 8 | 14 |
| 3 | 10 | 13 | 10 | 9 | 13 |
| 4 | 14 | 10 | 14 | 5 | 10 |
| 5 | 13 | 14 | 13 | 5 | 10 |
| 6 | 17 | 17 | 10 | 21 | 5 |
| 7 | 19 | 19 | 14 | 20 | 13 |
| 8 | 22 | 21 | 17 | 10 | 5 |
| 9 | 21 | 8 | 8 | 14 | 13 |
| 10 | 8 | 9 | 9 | 20 | 17 |
| 11 | 9 | 5 | 5 | 13 | 19 |
| 12 | 5 | 21 | 21 | 17 | 14 |
| 13 | 13 | 13 | 13 | 19 | 13 |
| 14 | 9 | 9 | 9 | 22 | 10 |
| 15 | 15 | 5 | 5 | 21 | 8 |
| 候选点\航空站类型 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 1 | 200 | 210 | 200 | 210 | 150 |
| 2 | 300 | 330 | 300 | 330 | 320 |
求解思路
- 首先可以确定这是一个仓库选址问题+01整数规划问题。题目要求最小化总成本,而这个总成本为
怎样可以让总成本更少?
- 很自然的想法是建更少的航空站,电池的成本也是一样的。所以实际上我们的目标是,建更少的航空站,用更少的电池完成巡检目标。
怎么表示巡检过程?
-
如果感到无从下手,我们可以将自己想象成那个无人机,我们在进行巡检任务的时候,需要知道哪些信息?
-
所在的地点:在不同的地点,到不同的铁塔距离不一样,耗电量不一样
-
需要巡检的铁塔:最多可以巡检哪些铁塔
-
电池使用情况:哪个电池被用来巡检哪些铁塔?当前这个航空站一共有几块电池?
-
航空站的种类:知道电池情况后,很自然可以知道属于那种航空站(小于
5个是第一种,大于5个是第二种)
-
数学模型
变量设置
- 从无人机角度出发,我们可以假设以下变量:
- 变量设置后,优化的目标就十分明显了:
- 这个式子和上面提到过的总成本相对应
约束条件
- 首先我们得保证,我们各个约束变量之间的关系是正确的。即:如果,必须成立(如果我们已经让无人机从航空站
m巡视铁塔n,那么m这个位置必须得已经设立了航空站,且只有一种航空站设立)
-
(1)式:如果已经决定在航空站m巡视铁塔n,那么在候选点m必须已经建立了一个航空站 -
(2)式:航空站m的电池总数必须不能超过当前航空站种类t的上限 -
(3)式:不能同时建立两个航空站 -
(4)式:如果已经决定在航空站m用k号电池巡视铁塔n,那么航空站m一定拥有k号电池 -
(5)式:如果已经决定航空站m会巡视铁塔n,那么一定会有某个电池k会被使用 -
(6)式:一个铁塔仅由一个航空站负责巡视 -
接下来我们对具体的巡检过程进行约束。问题中提到电池电量在不够时会更换电池,那么我们对于单块电池,必须得保证它完成巡视任务所需要的电量小于或等于单块电池的最大电量,即
- 理论上,上面的约束已经足以解决这个问题
代码实现
- 有了约束条件和数学模型,我们可以很轻松地写出
OPL代码:
int N = 15;
int M = 5;
int T = 2;
int f[1..N] = [10,5,7,6,8,10,5,7,6,8,10,5,7,6,8];
int h = 30;
int E = 80;
// 航空站容纳电池数量
int a[1..T] = [5,10];
// 无人机从铁塔到候选点的飞行耗电量
int e[1..N][1..M] = [
[15,9,15,17,10],
[20,5,20,8,14],
[10,13,10,9,13],
[14,10,14,5,10],
[13,14,13,5,10],
[17,17,10,21,5],
[19,19,14,20,13],
[22,21,17,10,5],
[21,8,8,14,13],
[8,9,9,20,17],
[9,5,5,13,19],
[5,21,21,17,14],
[13,13,13,19,13],
[9,9,9,22,10],
[15,5,5,21,8]
];
// 航空站在候选点的建立成本
int cost[1..T][1..M] = [
[200,210,200,210,150],
[300,330,300,330,320]
];
// 电池的最大数量
int K = 10;
range TowerRange = 1..N;
range StationRange = 1..M;
range ChargeRange = 1..K;
// 是否在候选点m建立航空站t
dvar boolean x[1..M][1..T];
// 是否从航空站m巡检铁塔n
dvar boolean y[1..M][1..N];
// 航空站m巡检铁塔n,使用k号电池
dvar boolean b[StationRange][TowerRange][ChargeRange];
// 航空站m的电池情况
dvar boolean z[StationRange][ChargeRange];
minimize sum(m in 1..M, t in 1..T) cost[t][m] * x[m][t] + h * sum(m in StationRange, k in ChargeRange) z[m][k];
subject to {
// 每个铁塔必须由一个航空站服务
forall(n in 1..N) {
sum(m in 1..M) y[m][n] == 1;
}
// 无人机电池续航能力
forall(m in 1..M, k in 1..K) {
sum(n in 1..N) (f[n] + 2 * e[n][m]) * y[m][n] * b[m][n][k] <= E;
}
forall( m in StationRange,k in ChargeRange ){
z[m][k] == ( sum( n in TowerRange ) b[m][n][k] >= 1 );
}
forall( m in 1..M ){
sum( t in 1..T ) x[m][t] <= 1;
}
forall( m in StationRange,n in TowerRange){
sum( t in 1..T ) x[m][t] >= y[m][n];
}
forall( m in StationRange ){
sum( k in ChargeRange ) z[m][k] <= sum( t in 1..T ) x[m][t] * a[t];
}
}
- 跑一下代码,看看会发生什么?—— 无法跑出真正的结果!
模型优化
为什么会出现这样的结果?
- 回顾我们的数学模型,可以发现这是一个简直是太过简单而暴力的枚举,通过不断地对不同的约束变量情况进行枚举,一一试出我们最后想要的答案。它所耗费的时间相当之多——不同约束变量的
0-1状态完全不同。单一个,我们都需要进行次运算,更别提我们还有其它的约束变量
这样的枚举该怎么优化?
-
我们应该充分信任
CPLEX解算引擎是相当优秀的——这是一切优化的前提。我们要怎样减少无用的枚举?对于搜索树分支的减少,我们常用的方法有:可行性剪枝,最优性剪枝,记忆化搜索等等。不知道什么是搜索树,也不知道什么是剪枝?没关系。我换一种说法:-
可行性剪枝:根据当前状态,让程序知道无法得到答案,结束本次枚举。如果我们在当前拥有的所有电池无法完成巡检任务,结束此次枚举,进入下一轮枚举
-
最优性剪枝:根据当前状态和已有的答案,如果当前状态的损耗已经超过答案,结束本次枚举。如果我们已经完成了一次枚举,结果为
500,此次枚举还没有结束,但是成本已经到了1000,那本次肯定不是最优解了,结束本次枚举。
-
-
很遗憾,我们没办法完成这两种形式的剪枝——因为这一切都在
CPLEX的解算引擎中完成。但是我们可以通过增加约束条件,让解算引擎间接完成这个过程 -
我们前面约束了单个电池的巡检情况,这个约束条件太过细致了,我们可以增加一个更大的约束条件,让解算引擎知道这个不满足之后直接结束本次枚举——即当前航空站的所有电池电量必须得大于等于航空站巡视它负责的所有铁塔所需要的总电量:
-
再观察一下我们想要的结果:和,我们真的关心航空站
m用的是1,3,5,7号电池还是1,2,3,4号电池吗?或者说,我们更关心的是电池的编号,还是哪个电池被用来巡视哪些铁塔?显然,下面两种情况是完全等价的:-
1号电池巡视1号铁塔,3号电池巡视2号铁塔,5号电池巡视3号铁塔,7号电池巡视4号铁塔 -
1号电池巡视1号铁塔,2号电池巡视2号铁塔,3号电池巡视3号铁塔,4号电池巡视4号铁塔
-
-
而解算引擎在进行枚举时,这两种情况都纳入了考虑范围之中——是不是有了冗余状态?我们只需要严格规定编号的情况,即可减少这类多余的枚举:
- 这样就能保证在使用之前,必须得已经被使用,即电池的编号就连续了
最终代码
int N = 15;
int M = 5;
int T = 2;
int f[1..N] = [10,5,7,6,8,10,5,7,6,8,10,5,7,6,8];
int h = 30;
int E = 80;
// 航空站容纳电池数量
int a[1..T] = [5,10];
// 无人机从铁塔到候选点的飞行耗电量
int e[1..N][1..M] = [
[15,9,15,17,10],
[20,5,20,8,14],
[10,13,10,9,13],
[14,10,14,5,10],
[13,14,13,5,10],
[17,17,10,21,5],
[19,19,14,20,13],
[22,21,17,10,5],
[21,8,8,14,13],
[8,9,9,20,17],
[9,5,5,13,19],
[5,21,21,17,14],
[13,13,13,19,13],
[9,9,9,22,10],
[15,5,5,21,8]
];
// 航空站在候选点的建立成本
int cost[1..T][1..M] = [
[200,210,200,210,150],
[300,330,300,330,320]
];
// 电池的最大数量
int K = 10;
range TowerRange = 1..N;
range StationRange = 1..M;
range ChargeRange = 1..K;
// 是否在候选点m建立航空站t
dvar boolean x[1..M][1..T];
// 是否从航空站m巡检铁塔n
dvar boolean y[1..M][1..N];
// 航空站m巡检铁塔n,使用k号电池
dvar boolean b[StationRange][TowerRange][ChargeRange];
// 航空站m的电池情况
dvar boolean z[StationRange][ChargeRange];
minimize sum(m in 1..M, t in 1..T) cost[t][m] * x[m][t] + h * sum(m in StationRange, k in ChargeRange) z[m][k];
subject to {
// 每个铁塔必须由一个航空站服务
forall(n in 1..N) {
sum(m in 1..M) y[m][n] == 1;
}
// 无人机电池续航能力
forall(m in 1..M, k in 1..K) {
sum(n in 1..N) (f[n] + 2 * e[n][m]) * y[m][n] * b[m][n][k] <= E;
}
forall(m in 1..M) {
sum(n in 1..N) (f[n] + 2 * e[n][m]) * y[m][n] <= sum( k in ChargeRange ) z[m][k] * E;
}
forall( m in StationRange,k in 1..9 ){
z[m][k] >= z[m][k+1];
}
forall( m in StationRange,k in ChargeRange ){
z[m][k] == ( sum( n in TowerRange ) b[m][n][k] >= 1 );
}
forall( m in 1..M ){
sum( t in 1..T ) x[m][t] <= 1;
}
forall( m in StationRange,n in TowerRange){
sum( t in 1..T ) x[m][t] >= y[m][n];
}
forall( m in StationRange ){
sum( k in ChargeRange ) z[m][k] <= sum( t in 1..T ) x[m][t] * a[t];
}
}
- 在笔者的电脑中,整个求解过程耗时
1s左右
总结
-
问题是解决了,但它并不是一个优雅的解决办法,因为我们只是使用了一些玄学的办法来提高了我们求解的速度,而不是一个更优的数学模型。但由于题目要求的数据量并不是很大,最终还是在一个能够接受的时间内解决了问题。
-
这件事告诉我们:
暴力才是真理我们还需要提出更好的数学模型,来解决更大规模的问题。善于思考才能进步!