而不是加入
俄罗斯对废物收集和处理的流程进行了完全调整后,这很难说,但我现在不想参与垃圾填埋场的补充。 因此,在许多大城市,以一种方式或另一种方式,都有志愿人员进行特别收集的运动。
在新西伯利亚,这种活动是在“绿色松鼠”运动的周围开展的,在该框架下,每月一次,关注环境的城市居民在特定时间将积累的可回收家庭垃圾带到预定的地方。 同时,一辆租来的卡车到达那里,将收集和分类的可回收材料带到现场,从那里在各种加工企业之间重新分配。 该行动自2014年以来一直存在,自那时以来,可回收物的收集点数量及其数量都大大增加了。 对于卡车路线选择而言,仅仅注视是不够的,我们开始开发优化模型以最小化运输成本。 这些模型中的第一个是本文的主题。
在第1部分中,我将详细说明并举例说明组织行动的方案。 此外,在第2节中,将运输成本最小化的任务正式化为使用时间窗解决异构车辆车队车辆路由问题的任务。 第3节专门使用自由分布的软件包来解决此问题,以解决混合整数线性数学编程问题GLPK。
1.动作“绿松鼠”
自2014年以来,“
生命地球”倡议小组每月进行一次活动,以分离新西伯利亚回收的
绿色松鼠的收藏。 在撰写本文时,已接受了一些保留,包括标签为PET,PE,PP,PS,玻璃,铝,铁,家用电器,废纸等的废塑料被回收。 50多名志愿者参加了行动的准备和实施。 该诉讼不是商业性的,参与是免费的,并不意味着金钱上的回报。
点数行动在城市的17个地点举行,彼此之间的距离为15分钟至90分钟,汽车被汽车覆盖。 照片中的这些要点之一:左边是沿着墙壁收集各种塑料碎片的袋子,右边是一辆卡车,将来要装载所有东西,而右边是一个有耳朵的志愿者。

此时的所有活动都是由策展人组织的,他们对准备好履行职责的时间有限制。 在计划一个动作时,策展人报告在该时间点动作必须经过的时间间隔。
在每个点上也有关于可回收材料平均收集量的数据。
路线点被组织成由卡车连续驱动的路线。 卡车的行驶由路线负责人监控,路线负责人监视操作环境并做出处理特殊事件的决定。
货车在按小时计算的货车运输建议框架内共同租赁。 无法将塑料压紧到位,因此,表征卡车的主要参数是车身的体积。 在我们的情况下,承载能力不是限制因素。
这项行动的主要费用与支付卡车租金有关,因此,减少这一行动对于我们份额的存在和发展至关重要,从形成负责任的消费观念的意义上说,这一点具有制度上的意义。 接下来,将基于离散优化方法的应用,即数学编程,描述解决该问题的方法。
2.形式化
在构建数学模型时,我们将停留在线性混合整数程序的框架内,对于该程序而言,了解7类代数的知识就足够了。
唯一的困难可以与在公式中使用抽象符号和求和符号相关联。 我希望这不会吓跑那些很少接触数学课本的读者。
在优化模型中,可以区分四个组件:
- 形成单独路线的点组的形成;
- 为每个组定义一个绕道方案;
- 满足卡车到达每个时间点的要求;
- 确定服务每条路线所需的卡车类型。
我们考虑每个部分,并在必要时引入必要的符号。
点分组让
V = \ {1,\点,n \}V = \ {1,\点,n \} 有很多可回收材料的收集点索引,然后将收集的可回收材料运到的地点(我们称为
仓库 )的索引为0。
\ bar {V} = V \ cup \ {0 \}\ bar {V} = V \ cup \ {0 \}每组点形成一条单独的路线。 通过
R 表示路线号的集合。
让数量
zir,i inI,r inR 如果点等于1
我 包含在路线中的数字
,否则为零。 然后要求点
i inV 必须输入其中一条路线可以写为
sumr inRzir=zi1+zi2+\点+zin=1。
仓库必须输入所有路线:
z0r=1,r inR细微之处不幸的是,这样的记录产生了与容许区域的对称性相关的计算问题。 可以通过添加一些限制来消除它,以确保按字典顺序选择最小分解。 例如,您可以在
这里阅读更多有关俄语的
信息 。
1−zir leq sumi−1j=1\左(1− sumr−1k=1zjk\右)+ sumr−1k=1zik, quadi\在I中,r\在R中,r leqi
绕行的定义路线是点和交叉点之间的交替序列。 形式上,它们都始于集合的某一点
V 并在仓库中结束,但是,假设所有路线都是循环的将更容易。 这种假设不会改变本质,但会使所有顶点相同:在这种情况下,没有最终顶点,它们都是过渡的。
积分
i,j in barV 和路线
r\在R$ 设置一个变量
xijr 如果在带数字的路线中等于1
卡车从点移动
我 到这一点
j ,否则为零。
然后我们首先要求卡车沿着路线行驶
r\在R$ 参观了要点
i inV 如果她分成几组然后按数字落入该组
:
sumj in barVxjir=zir, quadi in barV,r inR
其次,卡车到达终点后
i in barV 必须离开她。
sumj in barVxjir= sumj in barVxijr, quadi in barV,r inR
您可能会注意到,这些限制允许数量
xijr 定义一组不相交的回路的路由。 当然,这种路线是没有意义的,为避免这种情况需要采取一些限制措施。
关于消除子周期一种方法是引入辅助非负量
fijr,i,j in barV,r inR 使用这些量记录的一组关系消除了值的组合
xijr 定义不是连接回路的路由。
sumi inVf0jr= sumi inVzir, quadr inR,
fijr leqnxijr, quadi in barV,j in barV,r inR,
sumj in barVfjir= sumj in barVfijr+zir, quadi inV,r inR.
这些比率指定从仓库到其余路线点的流量。 在每个中间点处,都吸收了一个流量单位,因此,为了使网络的功率流等于点数减去一,必须连接该路径。
满足卡车到达各点时的要求换句话说,您只需要在策展人指示的时间窗口内访问点。 通过
Bi 和
Ei 分别表示时间点的开始和结束,在该时间间隔中,点的管理者
我 可以参加。
为了跟踪这些限制的执行情况,我们需要有关卡车在路线上的停靠站和过路处所花费的时间的信息。 通过
Li,i inV 表示该点的加载持续时间
我 ,并通过
Dij,i,j in barV -从一点移动的时间
我 到这一点
j 我们预订
D0i=0 对于所有
i in barV ,通常可以执行
Dij neqDji 对于任何
i neqj我们介绍非负变量
ai,i in barV 和
wir,i in barV,r inR 等于该点的到达时间和等待时间
我 沿路线行驶时
,分别。 对于输入的值,应满足以下关系。
等待时间不能少于加载所需的时间
wir geqLizir, quadi in barV,r inR,
如果该点不属于路线,则等于零
wir leqMzir, quadi in barV,r inR,
到达时间
j 在适当的时间为上一点计算
我 这是一个相当大的常数
M 用于消除相互之间移动时不必要的依赖性
我 和
j 没有完成。
ai+ sumr inRwir+Dij leqaj+M(1− sumr inRxijr), quadi inI,j\在V中,
卡车的进出必须在馆长指定的间隔内。
ai geqBi, quadi inV,
ai+ sumr inRwir leqEi, quadi inV.
确定维修每条路线所需的卡车类型表示以下类型的可供出租的卡车:
T 卡车类型
t inT 以身体体积为特征
Ct 一小时的租金
Pt 最小卡车租赁时间
t 用...表示
U0t 该点收集的可回收物数量
i inV 用...表示
Gi我们介绍变量
ytr,t inT,r inR 如果卡车类型等于1
t 用编号分配给服务路线
,否则为零。
整数变量
utr,t inT,r inR 设置租用卡车类型的时间
t 用号码服务路线
对于每条路线,
r\在R$ ,您必须指定卡车的类型:
sumt inTytr=1, quadr inR
根据路线之间的点的细分,某些路线可能变得微不足道,即仅包含仓库。 如果路线
不平凡,那么至少要租用分配给它的卡车
U0t 小时。
utr geqU0t(ytr− sumi inVzir), quadt inT,r inR
同时,租约的期限也应超过停车和沿路线行驶的总期限。
utr geq sumi inV sumj in barVDijxijr+ sumi in barVwir−M(1−ytr), quadr inR,t inT.
添加限制以提供以下属性:如果卡车是类型的
t 未分配给路线
,其租用时间为零。
utr leqMytr
在路线点收集的所有可回收物品应装在卡车的后部。
sumi inVGizir leq sumt inTCtytr, quadr inR.
最后,我们的目标是最大程度地减少卡车的租赁费用,使用引入的名称写成:
sumt inT sumr inRPtutr寻找解决方案
容易验证优化模型中涉及的所有表达式都是变量的线性函数,因此,要找到精确和近似的解决方案,可以使用标准包来解决混合整数编程问题,例如
Gurobi ,
CPLEX ,
Xpress ,
ORtools ,
SCIP ,
BLIS等
我们使用GMPL语言编写了一个模型,以最小化运输成本。 这将使我们能够使用免费的
GLPK软件包来实现我们的目的。 要编写代码和调试模型,可以方便地下载
GUSEK开发
环境 ,该
环境已包含GLPK。
古瑟克看起来像这样:

左边是模型的描述,右边是显示计算进度信息的窗口,求解器将在启动后提供该窗口。
完整的模型描述param numOfPoints > 0, integer; # param numOfTypes > 0, integer; # param numOfRoutes = numOfPoints;# set V := 1 .. numOfPoints; # set Vbar := V union {0}; # () set T := 1 .. numOfTypes; # set R := 1 .. numOfPoints; # ############### param WDL >= 0, default 8; # param B{i in Vbar} >= 0; # param E{i in Vbar} >= 0; # param L{i in Vbar} >= 0; # param D{i in Vbar, j in Vbar} >= 0, <= WDL; # param G{i in V}, >= 0; # , 3 ########## param C{t in T}, >= 0; # param P{t in T}, >= 0; # param U0{t in T}, >= 0; # , /********************************************** * **********************************************/ var z{Vbar, R} binary; # , , st pointToGroup 'point to group' {i in V}: sum{r in R} z[i, r] == 1; st depotToGroup 'depot to group' {r in R}: z[0, r] == 1; st lexMinGroups 'lexicographycally minimal division' {i in V, r in R: r <= i}: 1 - z[i, r] <= sum{j in V: j <= i - 1}(1 - sum{k in R: k <= r - 1} z[j, k]) + sum{k in R: k <= r - 1}z[i, k] ; /********************************************** * **********************************************/ var x{Vbar, Vbar, R} binary; # , c r i j, . st visitPoint 'visit point' {i in Vbar, r in R}: sum{j in Vbar} x[i, j, r] = z[i, r]; st keepMoving 'keep moving' {i in Vbar, r in R}: sum{j in Vbar} x[j, i, r] = sum {j in Vbar} x[i, j, r]; var f{Vbar, Vbar, R} >= 0; #, . st flowFromDepot 'flow from depot' {r in R}: sum{i in V} f[0, i, r] == sum{i in V} z[i, r]; st flowAlongActiveArcs 'flow along active arcs' {i in Vbar, j in Vbar, r in R}: f[i, j, r] <= numOfPoints * x[i, j, r]; st flowConservation 'flow conservation' {i in V, r in R}: sum{j in Vbar} f[j, i, r] == sum{j in Vbar} f[i, j, r] + z[i, r]; var a{i in V} >= 0; # var w{i in Vbar, r in R} >= 0; # st wait 'wait'{i in Vbar, r in R}: w[i, r] >= L[i] * z[i, r]; st dontWait 'dont wait'{i in Vbar, r in R}: w[i, r] <= (E[i] - B[i]) * z[i, r]; st arrivalTime 'arrival time' {i in V, j in V}: a[i] + sum{r in R}w[i, r] + D[i,j] <= a[j] + 3 * WDL * (1 - sum{r in R} x[i, j, r]); st arriveAfter 'arrive after' {i in V}: a[i] >= B[i]; st departBefore 'depart before' {i in V}: a[i] + sum{r in R}w[i, r] <= E[i]; /********************************************** * , **********************************************/ var y{t in T, r in R}, binary; # , t r, . var u{t in T, r in R}, integer, >= 0; # t, rst assignVehicle 'assign vehicle' {r in R}: sum{t in T} y[t,r] == 1; st rentTime 'rent time' {r in R, t in T}: u[t, r] >= sum{i in V, j in Vbar}D[i, j] * x[i, j, r] + sum{i in Vbar}w[i, r] - WDL * (1 - y[t, r]); st minRentTime 'minimal rent time' {r in R, t in T}: u[t, r] >= U0[t] * (y[t, r] - sum{i in V}z[i, r]); st noRent 'no rent' {t in T, r in R}: u[t, r] <= WDL * y[t, r]; st fitCapacity 'fit capacity' {r in R}: sum{i in V} G[i] * z[i, r] <= sum{t in T} C[t] * y[t, r]; minimize rentCost: sum{t in T, r in R} P[t] * u[t, r]; solve; /********************************************** * **********************************************/ printf{i in V, r in R} (if 0.1 < z[i,r] then "point %s to group %s\n" else ""), i, r, z[i,r]; printf{r in R, i in Vbar, j in Vbar} (if 0.1 < x[i, j, r] then "route %s: %s -> %s\n" else ""), r, i, j; printf{i in V} "point %s arrive between %s and %s (actual = %s)\n", i, B[i], E[i], a[i]; end;
为了快速入门,我还将添加准备好用于模型的头部数据:
输入数据 data; param numOfPoints := 9; # param numOfTypes := 6; # ############### param : B E L := 0 0 8 1 1 0 8 1 2 0 8 1 3 0 8 1 4 0 8 1 5 0 8 1 6 0 8 1 7 0 8 1 8 0 8 1 9 0 8 1; param D default 0 : 0 1 2 3 4 5 6 7 8 9 := 0 . . . . . . . . . . 1 0.1 0.3 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 2 0.3 0.2 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 3 0.4 0.3 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 4 0.4 0.4 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 5 0.1 0.2 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 6 0.5 0.5 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 7 0.3 0.2 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 8 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1 9 0.5 0.2 0.2 0.1 0.2 0.1 0.2 0.1 0.2 0.1; param G := 1 1 2 2 3 1 4 2 5 1 6 2 7 1 8 2 9 1; ########## param : C P := 1 8 500 2 10 500 3 14 600 4 18 650 5 25 700 6 35 800; param U0 default 2; # , end;
将模型代码复制到名称为例如model.mod的文件并将输入数据复制到data.dat文件后,一切就绪。 我们将计算时间设置为100秒(使用键--tmlim [以秒为单位的时间]完成),使用输入数据将路径传输到文件(使用键-d [文件路径]),

然后按F5。 如果成功,则会在右侧窗口中显示消息,并且一百秒钟后,我们将获得GLPK在分配的时间内设法找到的最佳解决方案。

在蓝色文本中,我们对铭文“ mip =”之后的含义感兴趣。 如您所见,它不时减少。 所有新的解决方案都在工作中,此栏中显示了最多的运输成本值(到目前为止有14700个)。 下一个数字是现有
最佳解决方案的下限。 最初,估计值被大大低估了,但是随着时间的推移而完善,即增加。 左右两边的值在收敛,截屏时它们之间的相对差距为54.1%。 一旦该数字变为零,该算法将证明找到的最佳解决方案是最佳解决方案。 在实践中并不总是需要等待此事件的合理性,这不仅是因为时间长,而且重要的是要保留一个好的解决方案,这通常是相对较快的解决方案,并且主要时间成本与证明最佳可能所需的估计值有关。
而不是结论
路由问题的计算极其复杂,并且随着需要访问的点数的增加,求解器找到解决方案并证明其最优性所需的时间正在迅速增长。 但是,对于相当小的示例,在合理的时间内,求解器能够构建成功的路线集,并可用于支持决策。 对模型提出的工艺路线选项的分析帮助我们发现了降低成本的重要机会,这对于库存的存在和发展至关重要。
我们的进一步努力是在工作地点收集可回收物的数量不确定的情况下进行的。 我们正在开发许多随机编程模型,以制定卡车路线规划中的战略和运营决策。 如果这个话题最终引起人们的关注并引起人们的兴趣,我也将写这篇文章,因为不久我们所有人都将不得不更加彻底地深入研究环境问题,这就是我们希望我们取得成功的原因。