車輛路徑優化問題求解工具Jsprit的簡單介紹與入門


今天小編要爲你們介紹一款用於求解車輛路徑優化問題(VRP)的工具箱---jsprit。你們可能沒聽過這個求解工具小編也是經老師介紹才知道的。這裏能夠偷偷的告訴你們老師的團隊正在開發一款更厲害的車輛路徑優化問題的求解器未來會與Jsprit作性能比較。你們能夠期待一下咱們本身的車輛路徑優化問題的求解器哦!html

jsprit是Github上的一個開源項目由Stefan Schröder所建立並由GraphHopper主持。這兩位發如今車輛路徑規劃問題應用如此普遍的狀況下極少有開源的工具可以幫助解決帶有不一樣約束的車輛路徑規劃問題因而他們就建立並完成了這個項目。java

Jsprit是一個開源的工具箱,提供用於求解VRP的工具,基於元啓發式算法(meta-heuristics)。git

什麼是元啓發式算法呢?元啓發式算法是指一類基於直觀或者經驗構造的算法,它能夠在可接受的花費(指時間或空間)下給出問題一個可行解。許多啓發式算法是針對或者是依賴於某一個特定問題的,而元啓發式算法則是一些比較通用的啓發式策略,一般不借助於某個問題特有的條件,將局部搜索和隨機相結合。咱們介紹過的蟻羣算法、模擬退火算法、遺傳算法等都屬於元啓發式算法。
之因此要作這個背景介紹就是爲了告訴你們 jsprit不保證能獲得最優解。 接下來小編將從功能、安裝使用、求解性能和質量幾個方面爲你們簡單地介紹這款工具箱。
Jsprit官網
http://jsprit.github.io/
https://github.com/graphhopper/jsprit


0 1


Jsprit能幹什麼



  據官網介紹,jsprit可以解決下列問題:github

  1. 帶容量限制的VRP(Capacitated VRP)web

  2. 多場站VRP(VRP with Multiple Depots)算法

  3. 帶時間窗的VRP(VRP with Time Windows)編程

  4. 帶回程的VRP(VRP with Backhauls)api

  5. 多車型VRP(VRP with Heterogeneous Fleet)數組

  6. 取送貨VRP(VRP with Pickups and Deliveries) 微信

  7. 時間依賴型VRP(Time-dependent VRP)

  8. 旅行商問題(Traveling Salesman Problem)

  9. Dial-a-Ride問題(Dial-a-Ride Problem)

除了以上問題外,jsprit還支持以上問題的混合。jsprit做爲一個模塊化的工具箱,方便之處在於,這個工具箱求解是經過core模塊裏的一些組件來構建整個VRP以及構建問題的組成元素,例如一個基本的車輛路徑規劃問題的代碼裏有倉庫、車輛、客戶點這幾個元素,那麼構造器會把這些元素一個一個構造出來,經過問題的構造器把這些元素加入到這個問題裏面,而且告知構造器用這些元素構造一個車輛路徑規劃問題的代碼。


爲何說這樣方便使用呢?


一個基本的車輛路徑規劃問題的代碼裏面,客戶點的屬性可能只有座標和需求量。換句話說,構造器在構造這個客戶點的時候,僅僅設置了這個客戶點的座標和需求量,可是除此以外,咱們還能夠爲這個客戶點設置一個時間窗,設置服務時間以及設置客戶點的服務優先級等等,經過這樣對客戶點的設置就可以知足不一樣的問題的需求。同理,對於車輛而言,咱們能夠用循環語句構建一百個車輛實例存到數組裏面。若是要求解一個多車型問題,咱們在構造這些車輛的時候設置好不一樣車型的參數就能夠了。

而對於整個問題的約束條件,在問題的構造器裏面也能夠設置,例如設置總的服務時間,設置是否帶有回程等等。

簡單地說就是構造器既可以實例化一個個元素,也能設置和修改這些元素的屬性從而可以知足不一樣問題的約束條件,這也就是爲何它可以支持以上問題的混合。

小編實踐後發現,這個工具箱除了上手快,使用方便之外,對於解的可視化也作得很好,可以很是詳細和直觀地表達解的狀況和結果。


0 2
如何使用Jsprit


Jsprit有三個比較核心的部件,分別是jsprit-core、jsprit-analysis、jsprit-io


jsprit-core從名字上咱們就能夠知道這個絕對是核心中的核心,裏面包含了一些構造器。


jsprit-analysis提供了將求解的結果進行可視化的工具箱,主要依賴於jfree繪圖並經過graphstream進行圖形流的處理和展現。這兩個都是免費的工具,須要到網上下載響應版本的jar包並在項目里加載,後續會給你們介紹。


jsprit-io則是對求解的過程等進行記錄和輸出。


此外還有jsprit-instances和jsprit-examples,這兩部分咱們在本身求解問題的時候並不須要用到,可是在學習的時候可以給咱們一些幫助。

jsprit-instances裏面有兩個部分,一個是instance,另外一個則是讀取算例的代碼,存放在一個src文件夾中。instance裏面有不一樣約束的VRP的一些經典算例,基本都是txt格式的文件,而src文件夾裏面則是一些代碼,這些代碼的做用是建立一個構造器而後讀入instance裏面的算例,構造算例裏面的元素。你們能夠利用這些代碼來讀入這些算例或者是與這些算例的格式相同的算例,這樣就不用本身寫讀入文件的代碼了。


jsprit-examples正如它的名字同樣,裏面都是一些簡單的運用這個工具箱的例子,若是你們環境搭好了並且配置正確的話,examples裏面的代碼是能夠直接跑的,這一部分的做用就是用簡單的例子向你們展現這個工具箱是怎麼工做的。


好了,囉嗦了這麼多,咱們來動手試試吧。


Step1

下載並解壓項目代碼

jsprit是用JAVA語言寫的,小編推薦用eclipse平臺來跑JAVA代碼嗷,你們能夠直接到官網下載(for free),而後到項目地址

https://github.com/graphhopper/jsprit下載,這裏能夠下載zip再進行解壓。


Step2

下載並解壓外部依賴包

這一步咱們須要下載一些jsprit依賴的外部包,須要的依賴包以及包的版本能夠從項目的介紹裏看的到,你們只須要到網上搜索下載下來而後解壓就行了,小編建議你們把這些包和上面下載解壓的文件放在一個目錄裏以便於管理,爲了方便你們操做,小編已經替你們收集好了這些包,跟源代碼打包到一塊兒了,你們直接下載使用就能夠了。



Step3

在Eclipse裏面建立一個項目並導入上述步驟中下載的包


而後就在eclipse裏面新建一個項目。

在新建的項目代碼文件夾里加載jsprit的工具包。在src文件夾右鍵->import->General->File System->Browse

而後找到剛纔的解壓包解壓的位置,找到jsprit-core等包,一直點進去jsprit-core\src\main\java,而後選中左側的java文件夾,再點擊finish就能夠了。

下一步就是外部依賴包的引入了,這裏咱們以slf4j這個包爲例子,這裏假設你們都已經下載並解壓了slf4j這個包,而後在咱們新建的java項目上右鍵->Build Path->Add External Archives,接下來會跳出來一個選擇框,找到解壓的位置,一直點進去直到看到有這些jar包,導入slf4j-api-1.7.25.jar和slf4j-jdk14-1.7.25.jar兩個包。其它的包依次導入就能夠了。


Step4

寫代碼

在準備好上面這些東西以後,咱們就能夠開始愉快地寫代碼了。

上述提到有幾個核心的組件,這裏咱們以解某個VRP爲例,看看如何使用這些組件,爲了方便你們理解,咱們先用圖大概地給你們介紹一下這幾個組件是怎麼合做的。




可能你們仍是有點懵,下面咱們把代碼放上來,結合代碼你們體會一下)

      
        
      
      
       
       
                
       
       

      
        
      
      
       
       
                
       
       
package maintest;
import com.graphhopper.jsprit.analysis.toolbox.GraphStreamViewer;
import com.graphhopper.jsprit.analysis.toolbox.GraphStreamViewer.Label;
import com.graphhopper.jsprit.analysis.toolbox.Plotter;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.box.Jsprit;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl.Builder;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleType;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl;
import com.graphhopper.jsprit.core.reporting.SolutionPrinter;
import com.graphhopper.jsprit.core.util.Solutions;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Scanner;
public class thetest {
/**
 * @param args
 */

    static int[][] customerNode ;//記錄客戶點信息
    static int capacity = 0;//車輛容量
    static int xdepot = 0;//倉庫橫座標
    static int ydepot = 0;//倉庫縱座標
    static int numberOfCustomer = 0;
    final static int WEIGHT_INDEX = 0;
public static void main(String[] args) {
    //建立輸出文件
    File dir = new File("output");
    if (!dir.exists()) {
        System.out.println("creating directory ./output");
        boolean result = dir.mkdir();
        if (result) System.out.println("./output created");
    }
    String path = "data/examples.txt";
    readData(path);
    buildProblem();
}
public static void readData(String path) {
    //讀入數據
    try {
        Scanner cin = new Scanner(new BufferedReader(new FileReader(path)));
        numberOfCustomer = cin.nextInt();
        cin.next();
        capacity = cin.nextInt();
        xdepot = cin.nextInt();
        ydepot = cin.nextInt();
        customerNode = new int[numberOfCustomer][3];
        for(int i = 0;i<numberOfCustomer;i++) {
            cin.nextInt();
            for(int j = 0;j<3;j++) {
                customerNode[i][j] = cin.nextInt();
            }
        }
        cin.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}
public static void buildProblem() {
    //創建一個求解器類型實例,爲vehicleType
    VehicleTypeImpl.Builder vehicleTypeBuilder = VehicleTypeImpl.Builder.newInstance("vehicleType").addCapacityDimension(WEIGHT_INDEX, capacity);
    VehicleType vehicleType = vehicleTypeBuilder.build();
    //創建一個求解器實例,名稱爲vechicle,座標爲讀入的座標,設定求解器的類型
    Builder vehicleBuilder = VehicleImpl.Builder.newInstance("vehicle");
    vehicleBuilder.setStartLocation(Location.newInstance(xdepot, ydepot));
    vehicleBuilder.setType(vehicleType);
    VehicleImpl vehicle = vehicleBuilder.build();
    //聲明服務點的集合
    Collection<Service> serviceNode = new ArrayList<Service>();
    //讀入各服務點的數據
    for(int i = 0; i < numberOfCustomer;i++) {
        Service serviceNodeTemp = Service.Builder.newInstance(""+i).addSizeDimension(WEIGHT_INDEX, customerNode[i][2]).setLocation(Location.newInstance(customerNode[i][0], customerNode[i][1])).build();
        serviceNode.add(serviceNodeTemp);
    }
    //實例化一個VRP的builder,並將中心點和服務點加入後實例化。
    VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance();
    vrpBuilder.addVehicle(vehicle);
    vrpBuilder.addAllJobs(serviceNode);
    VehicleRoutingProblem problem = vrpBuilder.build();
    //爲問題獲取算法
    VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(problem);
    //記錄解的集合記錄,並尋找最優解
    Collection<VehicleRoutingProblemSolution> solutions = algorithm.searchSolutions();
    VehicleRoutingProblemSolution bestSolution = Solutions.bestOf(solutions);
    //現實求解的結果詳情
    SolutionPrinter.print(problem, bestSolution, SolutionPrinter.Print.VERBOSE);
    //將求解的結果進行可視化
    new Plotter(problem,bestSolution).plot("output/plot.png","simple example");
    new GraphStreamViewer(problem, bestSolution).labelWith(Label.ID).setRenderDelay(200).display();
}
}


通過以上四個Step,咱們就能使用這個工具箱來求解一個帶容量約束的車輛路徑規劃問題了。接下來,咱們來看看運行的結果是怎麼樣的,首先咱們來看看core.reporting這個組件給出的求解信息



  • 經過以上內容你們能夠看到給出的求解信息仍是很是的詳細的。

  • 解的詳細結果打印的格式是統一的,可是這個VRP的代碼裏面並不涉及顧客服務時間窗。

  • 你們能夠看到求解的迭代次數,求解時間爲7.68秒,總路程爲524.6111466425074,注意這裏用的是歐氏距離,在構建問題的時候能夠將cost設爲曼哈頓距離。

  • 共使用了五輛車輛,並在detail中給出了每一個車輛的路徑,這個結果能夠用jsprit-io組件寫出爲xml文件,可是這個工具箱更秀的是它能直接將上述路線直接生成路線圖並輸出,請看:



是否是很省事?圖畫出來也還不錯,你覺得這就完了?還記得上文導入的外部包裏有一個graphstream嗎,這個東西能夠動態地呈現整個運輸過程,來看看效果圖吧



怎麼樣,是否是很酷炫?並且上述可視化只須要兩行代碼,就能還你一幅酷炫的路線圖。

0 2
與Cplex求解對比


上述是一個簡單的入門的例子,前文提到這個工具箱是基於元啓發式算法的,在上述算例中,獲得的解是算例的最優解,那它跟例如Cplex這樣的求解器在求解性能上會差多少呢,這裏咱們以一個帶時間窗的車輛路徑規劃問題的代碼爲例來比較一下二者的求解結果。因爲篇幅關係,這裏就只放用該求解器求解帶時間窗的車輛路徑規劃問題的代碼,用Cplex求解的代碼以及用到的算例和外部依賴包等等都會給你們。


代碼以下:

import com.graphhopper.jsprit.analysis.toolbox.GraphStreamViewer;
import com.graphhopper.jsprit.analysis.toolbox.Plotter;
import com.graphhopper.jsprit.analysis.toolbox.GraphStreamViewer.Label;
import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.box.Jsprit;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl.Builder;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleType;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl;
import com.graphhopper.jsprit.core.reporting.SolutionPrinter;
import com.graphhopper.jsprit.core.util.ManhattanCosts;
import com.graphhopper.jsprit.core.util.Solutions;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Scanner;
public class Vrptw_test {
    static int capacity;
    static int xdepot;
    static int ydepot;
    static int numberOfCustomer = 30;//這裏選取30個客戶點
    final static int WEIGHT_INDEX = 0;
    static int[][] customerNode;//用戶點數據
    public static void main(String[] args) {
        String path = "data/c104.txt";
        readData(path);
        buildProblem();
    }
    public static void readData(String path) {
        //讀入數據
        try {
            Scanner cin = new Scanner(new BufferedReader(new FileReader(path)));
            xdepot = cin.nextInt();
            ydepot = cin.nextInt();
            capacity = cin.nextInt();
            customerNode = new int[numberOfCustomer][6];
            for(int i = 0;i<numberOfCustomer;i++) {
                cin.nextInt();
                for (int j = 0;j<6;j++) {
                    customerNode[i][j] = cin.nextInt();
                }
            }
            cin.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static void buildProblem() {
        //實例化問題的類型
        VehicleTypeImpl.Builder vehicleTypeBuilder = VehicleTypeImpl.Builder.newInstance("vehic
leType"
)
            .addCapacityDimension(WEIGHT_INDEX, capacity).setCostPerWaitingTime(1.);
        VehicleType vehicleType = vehicleTypeBuilder.build();
        Builder vehicleBuilder = Builder.newInstance("vehicle");
        //設置中心的座標
        vehicleBuilder.setStartLocation(Location.newInstance(xdepot, ydepot));
        //最長時間
        vehicleBuilder.setLatestArrival(1351);
        vehicleBuilder.setType(vehicleType);
        //實例化構造器
        VehicleImpl vehicle = vehicleBuilder.build();
        //構建並實例化全部的客戶點
        Collection<Service> serviceNode = new ArrayList<Service>();
        for(int i = 0;i<numberOfCustomer;i++) {
            Service temp = Service.Builder.newInstance(""+i)
                    .addTimeWindow(customerNode[i][3],customerNode[i][4])
                    .addSizeDimension(WEIGHT_INDEX, customerNode[i][2])
                    .setServiceTime(customerNode[i][5])
                    .setLocation(Location.newInstance(customerNode[i][0], customerNode[i][1]))
                    .build();
            serviceNode.add(temp);
        }
        //構造問題
        VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance();
        vrpBuilder.addVehicle(vehicle);
        vrpBuilder.addAllJobs(serviceNode);
        vrpBuilder.setFleetSize(VehicleRoutingProblem.FleetSize.INFINITE);
        //vrpBuilder.setRoutingCost(new ManhattanCosts());//這一行是設置花費爲曼哈頓距離
        VehicleRoutingProblem problem = vrpBuilder.build();
        //根據問題尋找算法和solution
        VehicleRoutingAlgorithm algorithm = Jsprit.createAlgorithm(problem);
        Collection<VehicleRoutingProblemSolution> solutions = algorithm.searchSolutions();
        //尋找BestSolution
        VehicleRoutingProblemSolution bestSolution = Solutions.bestOf(solutions);
        //打印求解過程和結果
        SolutionPrinter.print(problem, bestSolution, SolutionPrinter.Print.VERBOSE);
        //繪圖
        new Plotter(problem,bestSolution).setLabel(Plotter.Label.ID).plot("output/plot2.png","mtw");
        new GraphStreamViewer(problem, bestSolution).labelWith(Label.ID).setRenderDelay(300).display();
    }
}


而後咱們先來看一下調用Cplex給出的求解結果

再來看看這個工具箱給出的求解結果,爲了減小篇幅,這裏貼部分結果

很遺憾,雖然這個工具箱的速度比Cplex要快得多,可是精確度上仍是差得仍是有點遠的。固然咱們能夠修改工具箱源代碼裏面的迭代次數,這樣有可能會達到一個更優的解,可是這樣作也會增長求解的時間,這個取捨就取決於使用者了,因爲篇幅和時間的緣由,這裏不可能做大量的測試。



小結


雖然這個工具箱不必定能找到最優解,並且使用前須要導入許多外部依賴包,也要求使用者要有一點JAVA編程的基礎,可是這個工具箱的一大優勢是它的可視化作的很好,解的詳細信息也能夠很直觀地表示出來,各個組件是模塊化的,掌握和理解起來不會太難。小編以爲最好的理解和學習方式是讀代碼,做者在Github上面也給出了不少代碼實例供你們學習。總的來講小編仍是以爲這個東西不錯的,起碼在使用上仍是比Cplex方便一些的,正所謂技多不壓身,各位能夠學一學,看一看啦。

END


欲下載所用源代碼和外部依賴包,請移步留言區。

-The End-

文案 / 排版 / 代碼 莊浩城

指導老師 / 秦虎 華中科技大學管理學院 tigerqin@hust.edu.cn



本文分享自微信公衆號 - 程序猿聲(ProgramDream)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索