运筹系列93:VRP精确算法

1. 基础版本

定义 x i j k x_{ijk} xijk为边 i j ij ij是否由车辆 k k k去运输。如果有时间窗约束的话,再加上一个变量 c i k c_{ik} cik即可,表示第k辆车到达节点i时的时间点。
第一类客户流量约束,要求每个点都有1个入度和1个出度,并且需要是同一辆车;
第二类车辆容量约束,要求每辆车的总访问边数为容量+1,并且仅有一个从depot出去的边

using JuMP, HiGHS

K = 3
N = 13 #34
Q = 4 #13
q = ones(Int,N);q[1]=0
CVRP = Model(HiGHS.Optimizer)
set_silent(CVRP)
@variable(CVRP,x[1:N,1:N,1:K],Bin)
for i in 2:N
    @constraint(CVRP, sum(x[i,:,:]) == 1)
    @constraint(CVRP, sum(x[:,i,:]) == 1)
    for k in 1:K;@constraint(CVRP, sum(x[i,:,k])==sum(x[:,i,k]));end
end
for k in 1:K
    @constraint(CVRP, sum(x[1,2:N,k]) == 1)
    @constraint(CVRP, sum(x[:,:,k])==Q+1)
    for i in 1:N, j in 1:N;@constraint(CVRP, x[i,j,k]+x[j,i,k]<=1);end
end
@objective(CVRP,Min, sum(x[i,j,k]*distmat[i,j] for i=1:N,j=1:N,k in 1:K))
@time optimize!(CVRP)
draw_vrp(sum(value.(x),dims = 3))

速度比后面的MTZ快一些,但是整体还是慢。
在这里插入图片描述

2. MTZ模型

MTZ是Miller-Tucker-Zemlin inequalities的缩写。除了定义是否用到边 x i j x_{ij} xij外,还需要定义一个 u i u_i ui用来表示此时车辆的当前载货量。注意这里x变量需要定义为有向。
这里定义为pickup问题,代码为:

using JuMP, HiGHS

k = 3 # number of vehicles
N = 11 # number of points, 0 as depot
Q = 4 # vehicle capacity
q = ones(Int,N);q[1]=0 # demand
CVRP = Model(HiGHS.Optimizer)
set_silent(CVRP)
@variable(CVRP,x[1:N,1:N],Bin)
@variable(CVRP,u[1:N],lower_bound = 0, upper_bound = Q)
# 约束1:出度约束
for i in 2:N
    @constraint(CVRP, sum(x[i,1:i-1]) + sum(x[i,i+1:N]) == 1)
    @constraint(CVRP, sum(x[1:i-1,i]) + sum(x[i+1:N,i]) == 1)
end
@constraint(CVRP, sum(x[1,1:N]) == k)
# 约束2:流量约束。若存在i->j,则u_j-u_i==q_j;否则u_j-q_j和u_i没有关系。此外,需要有u_j-q_j>=0
for i=2:N, j=[2:i-1;i+1:N]
    @constraint(CVRP,u[i] - u[j] + Q*x[i,j] <= Q-q[j])
end
for i in 2:N
    @constraint(CVRP,q[i] <= u[i] <= Q)
end

@objective(CVRP,Min, sum(x[i,j]*distmat[i,j] for i=1:N,j=1:N))
@time optimize!(CVRP)

MTZ的求解速度不快,10个点3辆车都需要3秒左右时间。

3. 分支定界法(行生成)

使用Two-index vehicle flow formulations。按照tsp的方式使用行生成法速度极慢(cut的效率太低),因此考虑使用branch-and-cut直接求解。需要cut的主要有2个:1)容量约束;2)subtour约束。如下例子:

using TSPLIB,JuMP, HiGHS, Distances
N = 13
Q = 4
k = 3
m = Model(HiGHS.Optimizer)
set_silent(m)
@variable(m, x[1:N,1:N]>=0,Bin)
@objective(m, Min, sum(x[i,j]*distmat[i,j] for i=1:N,j=1:N))
@constraint(m, x[1,1] == 0)
@constraint(m, sum(x[1,j] for j in 2:N) == k)
@constraint(m, sum(x[j,1] for j in 2:N) == k)
for i=2:N 
    for j in 1:N;@constraint(m, x[i,j]+x[j,i] <= 1);end
    @constraint(m, sum(x[i,j] for j in 1:N) == 1)
    @constraint(m, sum(x[j,i] for j in 1:N) == 1)
end
optimize!(m)
draw_vrp(x)

在这里插入图片描述

接下来定义寻找tour的函数,以及branch and cut的代码:

# find all subtours
function tours(x)
    g = JuMP.value.(x)
    # 第一步,找到所有从1出发的tour
    abnormal_paths = []
    paths = []
    path = [1]
    left = collect(1:N)
    while true
        v, idx = findmax(g[path[end],left])
        if v==0
            break
        else
            g[left[idx],path[end]]=0
            g[path[end],left[idx]]=0
            push!(path,left[idx])
        end
        if path[end]==1
            if length(path)>Q+2;push!(abnormal_paths,path);end
            push!(paths,path)
            path = [1]
            setdiff!(left,path[2:end-1])
        end
    end
    # 第二步,找到所有孤立的环(subtour)
    left = collect(1:N)
    for path in paths;setdiff!(left,path);end
    while length(left)>0   
        path = [left[1]]
        while true
            v, idx = findmax(g[path[end],left])
            if idx == 1
                break
            else
                g[left[idx],path[end]]=0
                g[path[end],left[idx]]=0
                push!(path,left[idx])
            end
        end
        setdiff!(left,path)
        push!(paths,path)
        push!(abnormal_paths,path)
    end
    return paths,abnormal_paths
end
    
paths,abnormal_paths = tours(x)
while length(abnormal_paths) > 0
    for path in paths
        s = setdiff(path,1)
        sn = setdiff(2:N,s)
        @constraint(m, sum(x[i,j] for i in s, j in setdiff(1:N,s)) >= ceil(length(s)/Q))
        @constraint(m, sum(x[i,j] for i in sn, j in setdiff(1:N,sn)) >= ceil(length(sn)/Q))
    end
    optimize!(m)
    paths,abnormal_paths = tours(x)
end
draw_vrp(x)

在这里插入图片描述

4. set-partitioning方法

方法很直观,把所有的子路径用TSP问题求解(使用Concorde库),然后用set-partitioning方法选择最合适的几条路线组合成VRP的结果。

using JuMP, HiGHS, Combinatorics, Concorde 

k = 3
N = 13 #34
Q = 4 #13

function getRoutes(k,N,Q)
    Qm = N-1-(k-1)*Q
    route_dists = Dict()
    # 求解所有子路径的最优解
    for q in Qm:Q
        for c in combinations(2:N,q)
            c_index_tour,c_tour_length = Concorde.solve_tsp(floor.(Int,distmat[[1;c],[1;c]].*100)) 
            c_tour = [1;c][c_index_tour]
            route_dists[c_tour] = c_tour_length
        end
    end
    route_dists
end
@time route_dists = getRoutes(k,N,Q);
CVRP = Model(HiGHS.Optimizer)
set_silent(CVRP)
routes = collect(keys(route_dists))
route_dists = collect(values(route_dists))
rn = length(routes)
@variable(CVRP,x[1:rn],Bin)
@objective(CVRP,Min, sum(x[i]*route_dists[i] for i in 1:rn))
a = zeros(Int,rn,N)
for i in 1:rn,j in routes[i];a[i,j]=1;end
for j in 2:N;@constraint(CVRP,sum(x[i]*a[i,j] for i in 1:rn)==1);end
@constraint(CVRP,sum(x)==k)
@time optimize!(CVRP)
rs = routes[findall(x->x>0.1,value.(x))]
plt.figure(figsize=(10,7)) 
plt.scatter(pos[1:N,1],pos[1:N,2],c="red")
for i in 1:N;plt.text(pos[i,1], pos[i,2], i);end
for r in rs
    l = pos[[r;1],:]
    PyPlot.plot(l[:,1], l[:,2], color="b")
end

在这里插入图片描述

5. 列生成方法

接下来设计基于set-partitioning的列生成算法。首先基于启发式算法给出一些tsp子路径。这里模型变量注意要改成线性问题,不然无法给出对偶解。

# 用启发式算法先生成一些路径
routes = [[2,3,4,5],[4,5,6,7],[6,7,8,9],[2,3,4,6]]
route_dists = [Concorde.solve_tsp(floor.(Int,distmat[[1;r],[1;r]].*100))[2]/100 for r in routes]
rn = length(routes)
cg = Model(HiGHS.Optimizer)
set_silent(cg)
@variable(cg,1>=x[1:rn]>=0)#,Bin)
@objective(cg,Min, sum(x[i]*route_dists[i] for i in 1:rn))
a = zeros(Int,rn,N)
for i in 1:rn,j in routes[i];a[i,j]=1;end
cons = []
for j in 2:N;con = @constraint(cg,sum(x[i]*a[i,j] for i in 1:rn)==1);push!(cons,con);end
@constraint(cg,sum(x)==k)
@time optimize!(cg)

在这里插入图片描述
接下来我们建立子问题来生成新的路径,针对上面的主问题,每一个路径都对应一个残差:cx-πx(这里π是对偶变量的解),我们以最小化残差为目标,如果结果小于0,则说明可以将新的路径转入基变量,完整代码如下:

routes = [[2,3,4,5],[4,5,6,7],[6,7,8,9],[2,3,4,6]]
flag = true
while flag
    route_dists = [Concorde.solve_tsp(floor.(Int,distmat[[1;r],[1;r]].*100))[2]/100 for r in routes]
    rn = length(routes)
    cg = Model(HiGHS.Optimizer)
    set_silent(cg)
    @variable(cg,1>=x[1:rn]>=0)#,Bin)
    @objective(cg,Min, sum(x[i]*route_dists[i] for i in 1:rn))
    a = zeros(Int,rn,N)
    for i in 1:rn,j in routes[i];a[i,j]=1;end
    cons = []
    for j in 2:N;con = @constraint(cg,sum(x[i]*a[i,j] for i in 1:rn)==1);push!(cons,con);end
    optimize!(cg)
    sp = Model(HiGHS.Optimizer)
    set_silent(sp)
    @variable(sp,1>=y[1:N,1:N]>=0,Bin)
    p = [0.0]
    for c in cons;push!(p,dual(c));end
    @objective(sp, Min, sum(distmat[i,j]*y[i,j] for i in 1:N, j in 1:N)-sum(p[i]*sum(y[i,j] for j in 1:N) for i in 2:N))
    for i in 1:N
        for j in 1:N;@constraint(sp,y[i,j]+y[j,i]<=1);end
        @constraint(sp,sum(y[i,j] for j in 1:N)==sum(y[j,i] for j in 1:N))
    end;
    @constraint(sp,sum(y)==Q+1)
    @constraint(sp,sum(y[1,:])==1)
    @constraint(sp,sum(y[:,1])==1)
    optimize!(sp)
    flag = (objective_value(sp)<-0.01)
    yv = value.(y)
    route = [1]
    while length(route)<=Q
        push!(route,findfirst(x->x>=0.5,yv[route[end],:]))
    end
    route = sort(route[2:end])
    if objective_value(sp)<0
        println("残差为",objective_value(sp),", 转入",route)
    else
        println("残差为",objective_value(sp),", 结束")
    end
    push!(routes,route)
end

结果如下:

残差为-38.831449600051535, 转入[2, 5, 7, 9]
残差为-39.150033402407914, 转入[3, 5, 6, 9]
残差为-38.81684590554077, 转入[5, 6, 7, 8]
残差为-49.543219451392396, 转入[3, 4, 7, 8]
残差为-24.007403774319204, 转入[4, 5, 8, 9]
残差为-32.952086371511896, 转入[2, 3, 8, 9]
残差为-16.53067003901679, 转入[2, 3, 7, 9]
残差为-13.61155193986133, 转入[2, 5, 8, 9]
残差为-10.133530272379385, 转入[3, 4, 5, 6]
残差为-8.572327787226246, 转入[2, 5, 6, 9]
残差为-7.603418916493028, 转入[5, 6, 7, 9]
残差为-7.600187922969602, 转入[5, 6, 8, 9]
残差为-5.700835963904262, 转入[2, 3, 4, 8]
残差为-13.765527089229668, 转入[2, 3, 4, 9]
残差为-1.4118420231930586, 转入[3, 4, 5, 9]
残差为-7.4652916644221925, 转入[5, 7, 8, 9]
残差为-7.5561902615201175, 转入[2, 3, 4, 7]
残差为-4.869792462350416, 转入[3, 4, 7, 9]
残差为-1.337641019305987, 转入[3, 5, 7, 8]
残差为-7.712871227881135, 转入[2, 4, 7, 8]
残差为-4.741848715534764, 转入[2, 4, 5, 6]
残差为-5.479199809684055, 转入[2, 3, 5, 6]
残差为-2.4061894903914096, 转入[2, 7, 8, 9]
残差为-1.413493799681662, 转入[2, 4, 5, 9]
残差为-4.5133099650613415, 转入[4, 7, 8, 9]
残差为-1.2752916644218404, 转入[5, 7, 8, 9]
残差为-1.0853635570299593, 转入[2, 3, 4, 6]
残差为-0.5040970280641338, 转入[2, 3, 7, 8]
残差为0.023151284460664213, 结束

此时主问题的解即为最优解。(如果时间有限,我们也可以在中间停下,也是可行解。)

5. 关于测试数据

测试案例可参考 http://vrp.atd-lab.inf.puc-rio.br/index.php/en/。
我们这里用的数据为:

pos = [121.472641	31.231707
123.429092	41.796768
125.324501	43.886841
126.642464	45.756966
116.405289	39.904987
117.190186	39.125595
111.75199	40.84149
106.23248	38.48644
112.549248	37.857014
114.502464	38.045475
117.000923	36.675808
113.665413	34.757977
108.948021	34.263161
114.298569	30.584354
118.76741	32.041546
117.283043	31.861191
112.982277	28.19409
115.892151	28.676493
120.15358	30.287458
119.306236	26.075302
113.28064	23.125177
121.520076	25.030724
110.19989	20.04422
108.320007	22.82402
106.504959	29.533155
102.71225	25.040609
106.713478	26.578342
104.065735	30.659462
103.83417	36.06138
101.77782	36.61729
91.1145	29.64415
87.61688	43.82663
114.16546	22.27534
113.54913	22.19875];

function dis(i,j)
    A = pos[i,:];B = pos[j,:]
    sqrt(sum((A-B).^2))
end

function drawTree(t,n)
    plt.figure(figsize=(10,7)) 
    plt.scatter(pos[1:n,1],pos[1:n,2],c="red")
    for i in 1:n;plt.text(pos[i,1], pos[i,2], i);end
    for i in 1:length(t)
        l = pos[collect(t[i]),:]
        PyPlot.plot(l[:,1], l[:,2], color="b")
    end
end

function draw_vrp(x)
    xv = value.(x)
    t = []
    for i in 1:size(x)[1],j in 1:size(x)[1]
        if xv[i,j]>0.1;push!(t,(i,j));end
    end
    drawTree(t,size(x)[1])
end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/760075.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ios13多窗口(UIWindowScene)学习笔记

ios13引入了UIWindowScene类、UIWindowSceneDelegate协议以便支持多窗口功能&#xff0c;但其适用于ipad&#xff0c;不适用于iphone&#xff0c;因为iphone不支持多窗口功能。注意&#xff0c;这里说的窗口不是UIWindow&#xff0c;而是UIWindowScene。 ios13前后的app的UI架…

AI陪伴产品的情感设计:从孤独感到恋爱感评分:9/10

本文主要阐述三个话题&#xff1a; 1. 市面上有哪些AI陪伴产品&#xff1f; 2. 我们团队要怎么做&#xff1f; 3. 为什么要做&#xff1f; 市面上有哪些陪伴类产品&#xff1f; Role-play&#xff08;角色扮演&#xff09; 在当前市场上&#xff0c;有不少以角色扮演为核心的…

Wails 安装初体验

文章目录 Wails 安装说明1. 系统要求2. 安装步骤3. 构建应用 结论 Wails 安装说明 Wails 是一个用于构建桌面应用的 Go 框架&#xff0c;结合了现代前端技术。以下是安装步骤&#xff1a; 1. 系统要求 Go 1.16 或更高版本Node.js 和 npm可选&#xff1a;适用于 Windows、mac…

iconfont-阿里巴巴矢量图标库 在vue项目使用记录

官网地址&#xff1a;https://www.iconfont.cn/manage/index?manage_typemyprojects&projectId4539761 第一步&#xff1a; 下载资源 ->解压到项目文件夹 第二步 在项目中main.ts 或者main.js 引入资源 import //assets/iconfont/font/iconfont.js; import //assets…

java基础知识点全集

JAVA的所有知识点 一、基础的数组、数据类型、输入输出二、类与对象1. 三大特征&#xff08;1&#xff09; 封装&#xff08;2&#xff09;继承&#xff08;3&#xff09;多态 2. 类的实例化&#xff08;1&#xff09; 类通过NEW来创建&#xff08;2&#xff09; 类的继承&…

python解锁图片相似度的神奇力量

在这个信息爆炸的时代,图片成为了我们传递信息、表达情感和记录生活的重要方式。然而,面对海量的图片资源,如何快速准确地找到相似的图片,成为了一个亟待解决的问题。现在,让我们为您揭开图片相似度的神秘面纱,带您领略这一创新技术的魅力! 图片相似度技术,就像是一位…

【多媒体】Java实现MP4视频播放器【JavaFX】【音视频播放】

在Java中播放视频可以使用多种方案&#xff0c;最常见的是通过Swing组件JFrame和JLabel来嵌入JMF(Java Media Framework)或Xuggler。不过&#xff0c;JMF已经不再被推荐使用&#xff0c;而Xuggler是基于DirectX的&#xff0c;不适用于跨平台。而且上述方案都需要使用第三方库。…

医院管理系统带万字文档医院预约挂号管理系统基于spingboot和vue的前后端分离java项目java课程设计java毕业设计

文章目录 仓库管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 仓库管理系统 一、项目演示 医院管理系统 二、项目介绍 基于springbootvue的前后端分离医院管…

QListView自定义item(结合QSqlQueryModel)

QListView:绘制自定义List&#xff08;一&#xff09;——设置ItemDelegate_qt_繁星执着-开放原子开发者工作坊 (csdn.net) QListView自定义Item_qlistview 自定义item-CSDN博客 结合我写的上一篇文章&#xff1a; QTableView与QSqlQueryModel的简单使用-CSDN博客 这次尝试…

webStorm debug vue项目的两种方案

一、前言 本文将介绍通过webstorm对vue项目进行debugger调试的两种方案。 但是&#xff0c;不管通过那种方案&#xff0c;都无法达到类似后端idea调试的体验&#xff0c;感觉十分难受&#xff0c;不过&#xff0c;比起用console.log还是好一些。如果各位有更好的方案&#xf…

扩展阅读:什么是中断

如果用一句话概括操作系统的原理,那就是:整个操作系统就是一个中断驱动的死循环,用最简单的代码解释如下: while(true){doNothing(); } 其他所有事情都是由操作系统提前注册的中断机制和其对应的中断处理函数完成的。我们点击一下鼠标,敲击一下键盘,执行一个程序,…

马斯克的SpaceX发展历史:从濒临破产到全球领先

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Space Exploration Technologies Corp.&#xff0c;简称SpaceX&#xff0c;是由埃隆马斯克&#xff08;Elon Musk&#xff09;于2002年创办的一…

观察者模式在金融业务中的应用及其框架实现

引言 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;使得多个观察者对象同时监听某一个主题对象。当这个主题对象发生变化时&#xff0c;会通知所有观察者对象&#xff0c;使它们能够自动更新。…

淀山湖之行随笔

我们仰望清新&#xff0c;但又不得不被世俗所伴。 近日上海开始进入梅雨季节&#xff0c;每天大大小小的雨水不断&#xff0c;整个环境也格外的潮湿&#xff0c;不过已经逐渐习惯这种气候&#xff0c;所谓的见怪不怪。 今日是周日&#xff0c;思绪好久&#xff0c;准备去淀山湖…

混合专家模型(MoE)的前世今生

在文章《聊聊最近很火的混合专家模型&#xff08;MoE&#xff09;》中&#xff0c;我们简单介绍了MoE模型的定义和设计&#xff0c;并且比较了MoE和Dense模型的区别&#xff0c;今天我们继续来回顾一下MoE模型发展的历史和最新的发展现状。 从去年GPT-4发布至今&#xff0c;MoE…

Crontab命令详解:轻松驾驭Linux定时任务,提升系统效率

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 引言&#xff1a; crond是Linux系统中用来定期执行命令或指定程序任务的一种服务或软件…

C++ | Leetcode C++题解之第199题二叉树的右视图

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> rightSideView(TreeNode* root) {unordered_map<int, int> rightmostValueAtDepth;int max_depth -1;stack<TreeNode*> nodeStack;stack<int> depthStack;nodeStack.push(ro…

【数据结构】(C语言):二叉搜索树

二叉搜索树&#xff1a; 树不是线性的&#xff0c;是层级结构。基本单位是节点&#xff0c;每个节点最多2个子节点。有序。每个节点&#xff0c;其左子节点都比它小&#xff0c;其右子节点都比它大。每个子树都是一个二叉搜索树。每个节点及其所有子节点形成子树。可以是空树。…

leetCode.98. 验证二叉搜索树

leetCode.98. 验证二叉搜索树 题目描述 代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(n…

鱼叉式钓鱼

鱼叉式网络钓鱼&#xff1a; 鱼叉式网络钓鱼是一种网络钓鱼形式&#xff0c;它针对特定个人或组织发送定制消息&#xff0c;旨在引发特定反应&#xff0c;例如泄露敏感信息或安装恶意软件。这些攻击高度个性化&#xff0c;使用从各种来源收集的信息&#xff0c;例如社交媒体资…