问题定义:
VRPTW,即带时间窗约束的车辆路径问题,是在经典的VRP问题上加入了一个时间窗约束。
数学模型:
求解:
代码结构:
其中Data类表示定义参数,读取数据和初始化参数
package com.chb;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Scanner;
public class Data {
public static int pointnum=102;
public static int carnum;
public static int cap;
public static double E;
public static double L;
public static int point[][]=new int[pointnum][2];
public static int s[]=new int[pointnum];
public static double a[]=new double[pointnum];
public static double b[]=new double[pointnum];
public static int demand[]=new int[pointnum];
public static int car[]=new int[pointnum];
public static double dist[][]=new double[pointnum][pointnum];
public static double arcs[][]=new double[pointnum][pointnum];
//截断小数3.26434-->3.2
public static double double_truncate(double v){
int iv = (int) v;
if(iv+1 - v <= 0.000000000001)
return iv+1;
double dv = (v - iv) * 10;
int idv = (int) dv;
double rv = iv + idv / 10.0;
return rv;
}
//读取数据并且初始化参数
public static void read_data(String path,Data data) throws Exception {
String line=null;
String substr[]=null;
Scanner cin=new Scanner(new BufferedReader(new FileReader(path)));
for (int i = 0; i < 4; i++) {
line=cin.nextLine();
}
line=cin.nextLine();
line.trim();
substr=line.split("\\s+");
carnum=Integer.parseInt(substr[1]);
cap=Integer.parseInt(substr[2]);
for (int i = 0; i < 4; i++) {
line=cin.nextLine();
}
for (int i = 0; i < pointnum-1; i++) {
line=cin.nextLine();
line.trim();
substr=line.split("\\s+");
point[i][0]=Integer.parseInt(substr[2]);
point[i][1]=Integer.parseInt(substr[3]);
demand[i]=Integer.parseInt(substr[4]);
a[i]=Integer.parseInt(substr[5]);
b[i]=Integer.parseInt(substr[6]);
s[i]=Integer.parseInt(substr[7]);
}
cin.close();
//终点数据初始化
point[pointnum-1]=point[0];
demand[pointnum-1]=demand[0];
a[pointnum-1]=a[0];
b[pointnum-1]=b[0];
s[pointnum-1]=s[0];
E=a[0];
L=b[0];
double min1=1e15;
double min2=1e15;
//距离矩阵初始化
for (int i = 0; i < pointnum; i++) {
for (int j = 0; j < pointnum; j++) {
if(i==j) {
dist[i][j]=0;
continue;
}else {
dist[i][j]=Math.sqrt(Math.pow(point[i][0]-point[j][0], 2)+Math.pow(point[i][1]-point[j][1], 2));
dist[i][j]=double_truncate(dist[i][j]);
}
}
}
dist[pointnum-1][0]=0;
dist[0][pointnum-1]=0;
//距离矩阵满足三角关系
for (int k = 0; k < pointnum; k++) {
for (int i = 0; i < pointnum; i++) {
for (int j = 0; j < pointnum; j++) {
if(dist[i][j]>dist[i][k]+dist[k][j]) {
dist[i][j]=dist[i][k]+dist[k][j];
}
}
}
}
//初始化为完全图
for (int i = 0; i < pointnum; i++) {
for (int j = 0; j < pointnum; j++) {
if(i==j) {
arcs[i][j]=0;
}else {
arcs[i][j]=1;
}
}
}
//除去不符合时间窗和容量约束的边
for (int i = 0; i < pointnum; i++) {
for (int j = 0; j < pointnum; j++) {
if(i==j) {
continue;
}
if(a[i]+s[i]+dist[i][j]>b[j]||demand[i]+demand[j]>cap) {
arcs[i][j]=0;
}
if(a[0]+s[i]+dist[0][i]+dist[i][pointnum-1]>b[pointnum-1]) {
System.out.println("计算错误");
}
}
}
for (int i = 1; i < pointnum-1; i++) {
if(b[i]-dist[0][i]min1||L
Solution类是对解的可行性进行判断
package com.chb;
import java.util.ArrayList;
import ilog.concert.IloException;
public class Solution {
double epsilon = 0.0001;
Data data = new Data();
ArrayList> routes = new ArrayList<>();
ArrayList> servetimes = new ArrayList<>();
public Solution(Data data, ArrayList> routes, ArrayList> servetimes) {
super();
this.data = data;
this.routes = routes;
this.servetimes = servetimes;
}
//函数功能:比较两个数的大小
public int double_compare(double v1,double v2) {
if (v1 < v2 - epsilon) {
return -1;
}
if (v1 > v2 + epsilon) {
return 1;
}
return 0;
}
//函数功能:解的可行性判断
public void fesible() throws IloException {
//车辆数量可行性判断
if (routes.size() > data.carnum) {
System.out.println("error: carnum!!!");
System.exit(0);
}
//车辆载荷可行性判断
for (int k = 0; k < routes.size(); k++) {
ArrayList route = routes.get(k);
double capasity = 0;
for (int i = 0; i < route.size(); i++) {
capasity += data.demand[route.get(i)];
}
if (capasity > data.cap) {
System.out.println("error: cap!!!");
System.exit(0);
}
}
//时间窗、车容量可行性判断
for (int k = 0; k < routes.size(); k++) {
ArrayList route = routes.get(k);
ArrayList servertime = servetimes.get(k);
double capasity = 0;
for (int i = 0; i < route.size()-1; i++) {
int origin = route.get(i);
int destination = route.get(i+1);
double si = servertime.get(i);
double sj = servertime.get(i+1);
if (si < data.a[origin] && si > data.b[origin]) {
System.out.println("error: servertime!");
System.exit(0);
}
if (double_compare(si + data.dist[origin][destination],data.b[destination]) > 0) {
System.out.println(origin + ": [" + data.a[origin] + ","+data.b[origin]+"]"+ " "+ si);
System.out.println(destination + ": [" + data.a[destination] + ","+data.b[destination]+"]"+ " "+ sj);
System.out.println(data.dist[origin][destination]);
System.out.println(destination + ":" );
System.out.println("error: forward servertime!");
System.exit(0);
}
if (double_compare(sj - data.dist[origin][destination],data.a[origin]) < 0) {
System.out.println(origin + ": [" + data.a[origin] + ","+data.b[origin]+"]"+ " "+ si);
System.out.println(destination + ": [" + data.a[destination] + ","+data.b[destination]+"]"+ " "+ sj);
System.out.println(data.dist[origin][destination]);
System.out.println(destination + ":" );
System.out.println("error: backward servertime!");
System.exit(0);
}
}
if (capasity > data.cap) {
System.out.println("error: cap!!!");
System.exit(0);
}
}
}
}
类 Vrptw 是建立模型并求解模型
package com.chb;
import java.util.ArrayList;
import ilog.concert.IloException;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloNumVarType;
import ilog.cplex.IloCplex;
public class Vrptw {
Data data; //定义类Data的对象
IloCplex cplex; //定义cplex内部类的对象
public IloNumVar[][][] x; //x[i][j][k]表示弧arcs[i][j]被车辆k访问
public IloNumVar[][] w; //车辆访问所有点的时间矩阵
double cost; //目标值object
Solution solution;
public Vrptw(Data data) {
this.data = data;
}
//函数功能:解模型,并生成车辆路径和得到目标值
public void solve() throws IloException {
ArrayList> routes = new ArrayList<>(); //定义车辆路径链表
ArrayList> servetimes = new ArrayList<>(); //定义花费时间链表
//初始化车辆路径和花费时间链表,链表长度为车辆数k
for (int k = 0; k < data.carnum; k++) {
ArrayList r = new ArrayList<>(); //定义一个对象为int型的链表
ArrayList t = new ArrayList<>(); //定义一个对象为double型的链表
routes.add(r); //将上述定义的链表加入到链表routes中
servetimes.add(t); //同上
}
//判断建立的模型是否可解
if(cplex.solve() == false){
//模型不可解
System.out.println("problem should not solve false!!!");
return; //若不可解,则直接跳出solve函数
}
else{
//模型可解,生成车辆路径
for(int k = 0; k < data.carnum; k++){
boolean terminate = true;
int i = 0;
routes.get(k).add(0);
servetimes.get(k).add(0.0);
while(terminate){
for (int j = 0; j < data.pointnum; j++) {
if (data.arcs[i][j]>=0.5 && cplex.getValue(x[i][j][k])>=0.5) {
routes.get(k).add(j);
servetimes.get(k).add(cplex.getValue(w[j][k]));
i = j;
break;
}
}
if (i == data.pointnum-1) {
terminate = false;
}
}
}
}
solution = new Solution(data,routes,servetimes);
cost = cplex.getObjValue();
System.out.println("routes="+solution.routes);
}
//函数功能:根据VRPTW数学模型建立VRPTW的cplex模型
private void build_cplex() throws IloException {
//model
cplex = new IloCplex();
cplex.setOut(null);
//variables
x = new IloNumVar[data.pointnum][data.pointnum][data.carnum];
w = new IloNumVar[data.pointnum][data.carnum]; //车辆访问点的时间
//定义cplex变量x和w的数据类型及取值范围
for (int i = 0; i < data.pointnum; i++) {
for (int k = 0; k < data.carnum; k++) {
w[i][k] = cplex.numVar(0, 1e15, IloNumVarType.Float, "w" + i + "," + k);
}
for (int j = 0; j < data.pointnum; j++) {
if (data.arcs[i][j]==0) {
x[i][j] = null;
}
else{
//Xijk,公式(10)-(11)
for (int k = 0; k < data.carnum; k++) {
x[i][j][k] = cplex.numVar(0, 1, IloNumVarType.Int, "x" + i + "," + j + "," + k);
}
}
}
}
//加入目标函数
//公式(1)
IloNumExpr obj = cplex.numExpr();
for(int i = 0; i < data.pointnum; i++){
for(int j = 0; j < data.pointnum; j++){
if (data.arcs[i][j]==0) {
continue;
}
for(int k = 0; k < data.carnum; k++){
obj = cplex.sum(obj, cplex.prod(data.dist[i][j], x[i][j][k]));
}
}
}
cplex.addMinimize(obj);
//加入约束
//公式(2)
for(int i= 1; i < data.pointnum-1;i++){
IloNumExpr expr1 = cplex.numExpr();
for (int k = 0; k < data.carnum; k++) {
for (int j = 1; j < data.pointnum; j++) {
if (data.arcs[i][j]==1) {
expr1 = cplex.sum(expr1, x[i][j][k]);
}
}
}
cplex.addEq(expr1, 1);
}
//公式(3)
for (int k = 0; k < data.carnum; k++) {
IloNumExpr expr2 = cplex.numExpr();
for (int j = 1; j < data.pointnum; j++) {
if (data.arcs[0][j]==1) {
expr2 = cplex.sum(expr2, x[0][j][k]);
}
}
cplex.addEq(expr2, 1);
}
//公式(4)
for (int k = 0; k < data.carnum; k++) {
for (int j = 1; j < data.pointnum-1; j++) {
IloNumExpr expr3 = cplex.numExpr();
IloNumExpr subExpr1 = cplex.numExpr();
IloNumExpr subExpr2 = cplex.numExpr();
for (int i = 0; i < data.pointnum; i++) {
if (data.arcs[i][j]==1) {
subExpr1 = cplex.sum(subExpr1,x[i][j][k]);
}
if (data.arcs[j][i]==1) {
subExpr2 = cplex.sum(subExpr2,x[j][i][k]);
}
}
expr3 = cplex.sum(subExpr1,cplex.prod(-1, subExpr2));
cplex.addEq(expr3, 0);
}
}
//公式(5)
for (int k = 0; k < data.carnum; k++) {
IloNumExpr expr4 = cplex.numExpr();
for (int i = 0; i < data.pointnum-1; i++) {
if (data.arcs[i][data.pointnum-1]==1) {
expr4 = cplex.sum(expr4,x[i][data.pointnum-1][k]);
}
}
cplex.addEq(expr4, 1);
}
//公式(6)
double M = 1e5;
for (int k = 0; k < data.carnum; k++) {
for (int i = 0; i < data.pointnum; i++) {
for (int j = 0; j < data.pointnum; j++) {
if (data.arcs[i][j] == 1) {
IloNumExpr expr5 = cplex.numExpr();
IloNumExpr expr6 = cplex.numExpr();
expr5 = cplex.sum(w[i][k], data.s[i]+data.dist[i][j]);
expr5 = cplex.sum(expr5,cplex.prod(-1, w[j][k]));
expr6 = cplex.prod(M,cplex.sum(1,cplex.prod(-1, x[i][j][k])));
cplex.addLe(expr5, expr6);
}
}
}
}
//公式(7)
for (int k = 0; k < data.carnum; k++) {
for (int i = 1; i < data.pointnum-1; i++) {
IloNumExpr expr7 = cplex.numExpr();
for (int j = 0; j < data.pointnum; j++) {
if (data.arcs[i][j] == 1) {
expr7 = cplex.sum(expr7,x[i][j][k]);
}
}
cplex.addLe(cplex.prod(data.a[i], expr7), w[i][k]);
cplex.addLe(w[i][k], cplex.prod(data.b[i], expr7));
}
}
//公式(8)
for (int k = 0; k < data.carnum; k++) {
cplex.addLe(data.E, w[0][k]);
cplex.addLe(data.E, w[data.pointnum-1][k]);
cplex.addLe(w[0][k], data.L);
cplex.addLe(w[data.pointnum-1][k], data.L);
}
//公式(9)
for (int k = 0; k < data.carnum; k++) {
IloNumExpr expr8 = cplex.numExpr();
for (int i = 1; i < data.pointnum-1; i++) {
IloNumExpr expr9 = cplex.numExpr();
for (int j = 0; j < data.pointnum; j++) {
if (data.arcs[i][j] == 1) {
expr9=cplex.sum(expr9,x[i][j][k]);
}
}
expr8 = cplex.sum(expr8,cplex.prod(data.demand[i],expr9));
}
cplex.addLe(expr8, data.cap);
}
}
public static void main(String[] args) throws Exception {
Data data=new Data();
String path="data/c102.txt";
data.read_data(path, data);
System.out.println("输入成功");
System.out.println("程序执行中**************************");
Vrptw cplex=new Vrptw(data);
cplex.build_cplex();
double cplex_time1=System.nanoTime();
cplex.solve();
cplex.solution.fesible();
double cplex_time2=System.nanoTime();
double cplex_time=( cplex_time2- cplex_time1)/1e9;
System.out.println("cplex_time:"+cplex_time);
System.out.println("bestcost:"+cplex.cost);
}
}
Data文件夹中是测试数据
运行结果截图:
本文转载、提炼来源于:https://mp.weixin.qq.com/s/OdzX_b9sLJ2PZ4WoDM5NnQ