2021-03-10 带时间窗口的路径规划 (使用Local Solver求解实例学习)

带时间窗口的路径规划 (使用Local Solver求解实例学习)

学到的原则

  1. 添加多个列表决策变量
  2. 定义表达式序列
  3. 使用lambda表达式定义递归数组

 

在带时间窗的有限能力车辆路径问题(CVRPTW)中,一个具有统一能力的车队必须以已知的需求和单一商品的开放时间为顾客服务。车辆在公共车场开始和结束其路线。每位客户只能乘坐一辆车。其目标是使车队规模最小化,并为车队中的每辆卡车分配一系列客户,使总行驶距离最小化,以便为所有客户提供服务,并且每辆卡车提供的总需求不超过其容量。

 

数   据

提供的实例来自Solomon实例

数据文件的格式如下:

  1. 第一行给出实例的名称
  2. 第五行包含车辆数量及其共同能力
  3. 从第10行开始,每行包含与每个客户相关联的整数数据,从仓库开始:
  4. 客户的索引
  5. x坐标
  6. y坐标
  7. 需求
  8. 最早到达
  9. 最晚到达
  10. 服务时间

程   序

LocalSolver模型是CVRP模型的扩展。我们让读者参考这个模型来了解问题的路由方面。时间窗口作为首要目标处理。如果提前到达,卡车必须等待客户的营业时间。在迟到的情况下,记录迟到时间,并将总迟到时间降至最低。当累计延迟为零时,该方法是可行的。

在该模型中,每辆卡车的结束时间被定义为一个递归数组。到达时间最长为:

  1. 上次访问的结束时间加上行走时间(在这些情况下等于距离)。对于第一次访问,它等于从车站出发的旅行时间(从t=0开始)
  2. 最早允许到达该客户的时间

结束时间仅仅是这个到达时间加上这个客户的服务时间。旅行结束时到达车站的时间为最后一次访问的结束时间加上从该点回到车站的旅行时间。

这种递归定义是通过函数(i,prev)=>。。。。它允许定义数组的第i个元素和数组的第(i-1)个元素的函数。有关详细信息,请参阅我们关于此主题的文档。

 

每次访问的延迟时间是根据此访问的结束时间与此客户允许的最晚结束时间之间的差来计算的。对于到达车站,我们将到达时间与为问题定义的最大展望期进行比较。

最后,我们按字典顺序最小化:总迟到时间、使用的卡车数量和所有卡车行驶的总距离。

以下为问题的源代码,下载链接在最后一行! 更多问题,请联系LocalSolver中国代理商“无锡迅合信息科技有限公司”技术人员。

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

 

猜你喜欢

转载自blog.csdn.net/qq_31243247/article/details/114648952
今日推荐