【练习】带时间窗口约束的路径规划CVRPTW (使用Local Solver数学求解优化器)
通过这个实例学到的原则:
l 添加多个列表决策变量
l 定义表达式序列
l 使用lambda表达式定义递归数组
在带时间窗的有限能力车辆路径问题(CVRPTW)中,一个具有统一能力的车队必须以已知的需求和单一商品的开放时间为顾客服务。车辆在公共停车场开始和结束其路线。每位客户只能乘坐一辆车。其目标是使车队规模最小化,并为车队中的每辆卡车分配一系列客户,使总行驶距离最小化,以便为所有客户提供服务,并且每辆卡车提供的总需求不超过其容量。
数据
提供的实例来自Solomon实例。
数据文件的格式如下:
第一行给出实例的名称
第五行包含车辆数量及其共同能力
从第10行开始,每行包含与每个客户相关联的整数数据,从仓库开始:
客户的索引
x坐标
y坐标
需求
最早到达
最晚到达
服务时间
程序
LocalSolver模型是CVRP模型的扩展。我们让读者参考这个模型来了解问题的路由方面。时间窗口作为首要目标处理。如果提前到达,卡车必须等待客户的营业时间。在迟到的情况下,记录迟到时间,并将总迟到时间降至最低。当累计延迟为零时,该方法是可行的。
在该模型中,每辆卡车的结束时间被定义为一个递归数组。到达时间最长为:
l 上次访问的结束时间加上行走时间(在这些情况下等于距离)。对于第一次访问,它等于从车站出发的旅行时间(从t=0开始)
l 最早允许到达该客户的时间
结束时间仅仅是这个到达时间加上这个客户的服务时间。旅行结束时到达车站的时间为最后一次访问的结束时间加上从该点回到车站的旅行时间。
这种递归定义是通过函数(i,prev)=>。。。。它允许定义数组的第i个元素和数组的第(i-1)个元素的函数。有关详细信息,请参阅我们关于此主题的文档。
每次访问的延迟时间是根据此访问的结束时间与此客户允许的最晚结束时间之间的差来计算的。对于到达车站,我们将到达时间与为问题定义的最大展望期进行比较。
最后,我们按字典顺序最小化:总迟到时间、使用的卡车数量和所有卡车行驶的总距离。
C#调用LOCAL SOLVER源代码
Compilation / Execution (Windows)
copy %LS_HOME%\bin\localsolvernet.dll .
csc Cvrptw.cs /reference:localsolvernet.dll
Cvrp instances\R101.25.txt
/********** Cvrptw.cs **********/
using System;
using System.IO;
using System.Collections.Generic;
using localsolver;
public class Cvrptw : IDisposable
{
// Solver
LocalSolver localsolver;
// Number of customers (= number of nodes minus 1)
int nbCustomers;
// Capacity of the trucks
int truckCapacity;
// Latest allowed arrival to depot
int maxHorizon;
// Demand on each node
List<int> demands;
// Earliest arrival on each node
List<int> earliestStart;
// Latest departure from each node
List<int> latestEnd;
// Service time on each node
List<int> serviceTime;
// Distance matrix between customers
double[][] distanceMatrix;
// Distances between customers and warehouse
double[] distanceWarehouses;
// Number of trucks
int nbTrucks;
// Decision variables
LSExpression[] customersSequences;
// Are the trucks actually used
LSExpression[] trucksUsed;
// Distance traveled by each truck
LSExpression[] routeDistances;
// End time array for each truck
LSExpression[] endTime;
// Home lateness for each truck
LSExpression[] homeLateness;
// Cumulated Lateness for each truck
LSExpression[] lateness;
// Cumulated lateness in the solution (must be 0 for the solution to be valid)
LSExpression totalLateness;
// Number of trucks used in the solution
LSExpression nbTrucksUsed;
// Distance traveled by all the trucks
LSExpression totalDistance;
public Cvrptw()
{
localsolver = new LocalSolver();
}
// Reads instance data.
void ReadInstance(string fileName)
{
ReadInputCvrptw(fileName);
}
public void Dispose()
{
if (localsolver != null)
localsolver.Dispose();
}
void Solve(int limit)
{
// Declares the optimization model.
LSModel model = localsolver.GetModel();
trucksUsed = new LSExpression[nbTrucks];
customersSequences = new LSExpression[nbTrucks];
routeDistances = new LSExpression[nbTrucks];
endTime = new LSExpression[nbTrucks];
homeLateness = new LSExpression[nbTrucks];
lateness = new LSExpression[nbTrucks];
// Sequence of customers visited by each truck.
for (int k = 0; k < nbTrucks; k++)
customersSequences[k] = model.List(nbCustomers);
// All customers must be visited by the trucks
model.Constraint(model.Partition(customersSequences));
// Create demands and distances as arrays to be able to access it with an "at" operator
LSExpression demandsArray = model.Array(demands);
LSExpression earliestArray = model.Array(earliestStart);
LSExpression latestArray = model.Array(latestEnd);
LSExpression serviceArray = model.Array(serviceTime);
LSExpression distanceWarehouseArray = model.Array(distanceWarehouses);
LSExpression distanceArray = model.Array(distanceMatrix);
for (int k = 0; k < nbTrucks; k++)
{
LSExpression sequence = customersSequences[k];
LSExpression c = model.Count(sequence);
// A truck is used if it visits at least one customer
trucksUsed[k] = c > 0;
// The quantity needed in each route must not exceed the truck capacity
LSExpression demandSelector = model.LambdaFunction(i => demandsArray[sequence[i]]);
LSExpression routeQuantity = model.Sum(model.Range(0, c), demandSelector);
model.Constraint(routeQuantity <= truckCapacity);
// Distance traveled by truck k
LSExpression distSelector = model.LambdaFunction(i => distanceArray[sequence[i - 1], sequence[i]]);
routeDistances[k] = model.Sum(model.Range(1, c), distSelector)
+ model.If(c > 0, distanceWarehouseArray[sequence[0]] + distanceWarehouseArray[sequence[c - 1]], 0);
//End of each visit
LSExpression endSelector = model.LambdaFunction((i, prev) => model.Max(earliestArray[sequence[i]],
model.If(i == 0,
distanceWarehouseArray[sequence[0]],
prev + distanceArray[sequence[i-1], sequence[i]])) +
serviceArray[sequence[i]]);
endTime[k] = model.Array(model.Range(0, c), endSelector);
// Arriving home after max_horizon
homeLateness[k] = model.If(trucksUsed[k],
model.Max(0, endTime[k][c-1] + distanceWarehouseArray[sequence[c-1]] - maxHorizon),
0);
//completing visit after latest_end
LSExpression lateSelector = model.LambdaFunction(i => model.Max(endTime[k][i] - latestArray[sequence[i]], 0));
lateness[k] = homeLateness[k] + model.Sum(model.Range(0, c), lateSelector);
}
totalLateness = model.Sum(lateness);
nbTrucksUsed = model.Sum(trucksUsed);
totalDistance = model.Round(100*model.Sum(routeDistances))/100;
// Objective: minimize the number of trucks used, then minimize the distance traveled
model.Minimize(totalLateness);
model.Minimize(nbTrucksUsed);
model.Minimize(totalDistance);
model.Close();
// Parameterizes the solver.
localsolver.GetParam().SetTimeLimit(limit);
localsolver.Solve();
}
// Writes the solution in a file with the following format:
// - number of trucks used and total distance
// - for each truck the nodes visited (omitting the start/end at the depot)
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine(nbTrucksUsed.GetValue() + " " + totalDistance.GetDoubleValue());
for (int k = 0; k < nbTrucks; k++)
{
if (trucksUsed[k].GetValue() != 1) continue;
// Values in sequence are in [0..nbCustomers-1]. +2 is to put it back in [2..nbCustomers+1]
// as in the data files (1 being the depot)
LSCollection customersCollection = customersSequences[k].GetCollectionValue();
for (int i = 0; i < customersCollection.Count(); i++)
{
output.Write((customersCollection[i] + 2) + " ");
}
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: Cvrptw inputFile [solFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "20";
using (Cvrptw model = new Cvrptw())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
private string[] SplitInput(StreamReader input) {
string line = input.ReadLine();
if (line == null) return new string[0];
return line.Split(new [] {' '}, StringSplitOptions.RemoveEmptyEntries);
}
// The input files follow the "Solomon" format.
private void ReadInputCvrptw(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] splitted;
input.ReadLine();
input.ReadLine();
input.ReadLine();
input.ReadLine();
splitted = SplitInput(input);
nbTrucks = int.Parse(splitted[0]);
truckCapacity = int.Parse(splitted[1]);
input.ReadLine();
input.ReadLine();
input.ReadLine();
input.ReadLine();
splitted = SplitInput(input);
int depotX = int.Parse(splitted[1]);
int depotY = int.Parse(splitted[2]);
maxHorizon = int.Parse(splitted[5]);
List<int> customersX = new List<int>();
List<int> customersY = new List<int>();
demands = new List<int>();
earliestStart = new List<int>();
latestEnd = new List<int>();
serviceTime = new List<int>();
while(!input.EndOfStream)
{
splitted = SplitInput(input);
if (splitted.Length < 7) break;
customersX.Add(int.Parse(splitted[1]));
customersY.Add(int.Parse(splitted[2]));
demands.Add(int.Parse(splitted[3]));
int ready = int.Parse(splitted[4]);
int due = int.Parse(splitted[5]);
int service = int.Parse(splitted[6]);
earliestStart.Add(ready);
latestEnd.Add(due+service);//in input files due date is meant as latest start time
serviceTime.Add(service);
}
nbCustomers = customersX.Count;
ComputeDistanceMatrix(depotX, depotY, customersX, customersY);
}
}
// Computes the distance matrix
private void ComputeDistanceMatrix(int depotX, int depotY, List<int> customersX, List<int> customersY)
{
distanceMatrix = new double[nbCustomers][];
for (int i = 0; i < nbCustomers; i++)
distanceMatrix[i] = new double[nbCustomers];
for (int i = 0; i < nbCustomers; i++)
{
distanceMatrix[i][i] = 0;
for (int j = i + 1; j < nbCustomers; j++)
{
double dist = ComputeDist(customersX[i], customersX[j], customersY[i], customersY[j]);
distanceMatrix[i][j] = dist;
distanceMatrix[j][i] = dist;
}
}
distanceWarehouses = new double[nbCustomers];
for (int i = 0; i < nbCustomers; ++i) {
distanceWarehouses[i] = ComputeDist(depotX, customersX[i], depotY, customersY[i]);
}
}
private double ComputeDist(int xi, int xj, int yi, int yj)
{
return Math.Sqrt(Math.Pow(xi - xj, 2) + Math.Pow(yi - yj, 2));
}
}
下载数据文件和数学模型请点击下载链接: CVRPTW
更多问题,请联系LocalSolver中国代理商无锡迅合信息科技有限公司技术人员。