遺傳算法實(shí)踐(十) VRPTW問(wèn)題求解

問(wèn)題描述

車輛配送模型(Vehicle routing problem)是指從配送中心用車輛把物資配送給顧客時(shí),規(guī)劃調(diào)用哪些車輛矾屯,按照何種順序配送貨物的問(wèn)題少梁。該問(wèn)題通常假定配送中心與顧客的地理位置之間距離擎值、配送用車輛的載重量和可行駛距離(時(shí)間)均為已知骑素,以高效配送和減少成本為目標(biāo)進(jìn)行決策優(yōu)化。

帶時(shí)間窗的車輛配送模型(Vehicle routing problem with time window冀墨,VRPTW)是在VRP基礎(chǔ)上額外附加配送時(shí)間約束條件產(chǎn)生的一個(gè)變種闸衫。在這類問(wèn)題中,給定配送車輛須到達(dá)顧客點(diǎn)的最早時(shí)間和最遲時(shí)間诽嘉,當(dāng)車輛從一個(gè)或者多個(gè)配送中心向帶時(shí)間窗約束的多個(gè)顧客點(diǎn)配送物資的時(shí)候蔚出,求使費(fèi)用最低的車輛調(diào)度方案。

這個(gè)問(wèn)題基于以下假設(shè):

  • 所有距離采用歐幾里得表示
  • 每個(gè)顧客只接受一輛車的一次配送
  • 從配送中心出發(fā)的車輛必須返回配送中心
  • 每輛車載重量相同
  • 一條路徑上顧客的總需求量不能超過(guò)車輛的最大載重量
  • 每個(gè)顧客分別在各自時(shí)間窗內(nèi)接受服務(wù)

定義j,k為需要服務(wù)的兩個(gè)顧客編號(hào)虫腋,e為配送中心的車輛編號(hào)骄酗,C為顧客和倉(cāng)庫(kù)的集合。
參數(shù):

c_{jk}: 從顧客j到顧客k的行駛距離

t_{jk}:從顧客j到顧客k的行駛時(shí)間

s_j:分配給顧客j的服務(wù)時(shí)間

d_j:顧客j的需求量

Y_e:車輛的最大載重量

g_j:允許到達(dá)顧客j處的最早時(shí)間

h_j:允許到達(dá)顧客j處的最晚時(shí)間

決策變量:

a_j:車輛到達(dá)顧客j處的時(shí)間

z_{ejk}:當(dāng)車輛e被分配從顧客i運(yùn)行到顧客j時(shí)悦冀,取1趋翻;否則取0

在給定了參數(shù)和定義了決策變量之后,VRPTW問(wèn)題可以用數(shù)學(xué)模型表示為:
min\sum_{e\in E}\sum_{j\in C}\sum_{k \in C}c_{jk}z_{ejk} \\s.t.\begin{aligned} \sum_{e\in E}\sum_{j\in C}z_{ejk} = 1, \forall j,k \in C \backslash \{0\} \\ \sum_{j\in C\backslash \{0\}}\sum_{k\in C\backslash \{0\}}d_k z_{ejk} \le Y_e, \forall i,e \in E \\ \sum_{k\in C}z_{e0k}=1, \forall e \in E \\ \sum_{j\in C\backslash \{0\}}z_{ejk} - \sum_{k\in C\backslash \{0\}}z_{ejk}=0, \forall e \in E \\ \sum_{j\in C_i}z_{ejk}=1, \forall e\in E \\ \sum_{e=1}^{E}(a_j+s_j+t_{jk}+w_k)z_{ejk} \le a_{k}, \forall k \\ g_j \le (a_j+w_j) < h_j, \forall i,j \\ z_{ejk} = 0\ or\ 1, \forall e \in E, \forall j,k \in C \end{aligned}

VRPTW問(wèn)題示例

示例為1個(gè)配送中心盒蟆,位于坐標(biāo)(15,12)踏烙,30個(gè)顧客的問(wèn)題,為簡(jiǎn)化問(wèn)題历等,設(shè)配送中心只提供一種型號(hào)的卡車讨惩,卡車的最大載重為400,平均行駛速度為30單位距離/單位時(shí)間寒屯,各顧客的位置荐捻、需求量和時(shí)間窗約束如下表所示:

j (u_j,v_j) d_j g_j h_j j (u_j,v_j) d_j g_j h_j
1 (3,13) 50 3 4 16 (23,22) 51 8 9
2 (3,17) 50 4 5 17 (24,11) 20 4 6
3 (6,18) 60 5 7 18 (27,21) 20 7 11
4 (8,17) 30 2 9 19 (26,6) 20 10 15
5 (10,14) 90 10 11 20 (26,9) 30 13 14
6 (14,13) 10 12 13 21 (27,2) 30 17 18
7 (15,11) 20 11 13 22 (27,4) 30 16 18
8 (15,15) 10 1 5 23 (27,17) 10 10 12
9 (17,11) 30 11 15 24 (28,7) 60 13 14
10 (17,16) 20 8 9 25 (29,14) 30 5 8
11 (18,19) 30 10 12 26 (29,18) 20 6 8
12 (19,9) 10 15 16 27 (30,1) 30 7 9
13 (19,21) 10 18 20 28 (30,8) 40 3 6
14 (21,22) 10 1 7 29 (30,15) 20 4 5
15 (23,9) 40 6 9 30 (30,17) 20 6 9

遺傳算法求解VRPTW問(wèn)題

個(gè)體編碼

遺傳算法求解VRPTW的文獻(xiàn)中有多種編碼方式,這里對(duì)于個(gè)體采用自然數(shù)編碼寡夹,0代表配送中心处面,1--n代表顧客;不同車輛的配送路線之間用0分隔(即每輛車都從倉(cāng)庫(kù)出發(fā))菩掏;對(duì)于有n個(gè)顧客鸳君,k輛車的VRP問(wèn)題來(lái)說(shuō),染色體長(zhǎng)度為n+k+1患蹂。

例如配送中心有3輛車為8個(gè)客戶服務(wù)或颊,一條可能的染色體如下:
0, 7, 0, 1, 2, 3, 5, 0, 8, 4, 6, 0
這條染色體表示的三輛車的行駛路線為:
第一輛車:0-7-0
第二輛車:0-1-2-3-5-0
第三輛車:0-8-4-6-0

懲罰

在交叉和突變產(chǎn)生的子代中,可能會(huì)有兩種違反約束的形式:

  • 車輛超載
  • 不能在時(shí)間窗口約束給出的最晚時(shí)間點(diǎn)內(nèi)到達(dá)指定顧客處

對(duì)這兩種違反約束的情況采用靜態(tài)懲罰传于,考慮到時(shí)間窗口更容易被違反囱挑,對(duì)其施加較大的懲罰因子。對(duì)這兩種約束違反的懲罰因子分別設(shè)置為10和500沼溜。

交叉

這里參考《基于電動(dòng)汽車的帶時(shí)間窗的路徑優(yōu)化問(wèn)題研究》中給出的交叉操作平挑,細(xì)節(jié)可以參考上一篇博文

突變

對(duì)選中的個(gè)體中各條子路線用2-opt算法優(yōu)化

選擇

  • 育種選擇:binary錦標(biāo)賽選擇
  • 環(huán)境選擇:采用精英保留策略,合并子代和父代后系草,選擇數(shù)量等同于族群規(guī)模的個(gè)體

代碼示例

完整代碼如下:

## 環(huán)境設(shè)定
import numpy as np
import matplotlib.pyplot as plt
from deap import base, tools, creator, algorithms
import random

params = {
    'font.family': 'serif',
    'figure.dpi': 300,
    'savefig.dpi': 300,
    'font.size': 12,
    'legend.fontsize': 'small'
}
plt.rcParams.update(params)

from copy import deepcopy
#-----------------------------------
## 問(wèn)題定義
creator.create('FitnessMin', base.Fitness, weights=(-1.0,)) # 最小化問(wèn)題
# 給個(gè)體一個(gè)routes屬性用來(lái)記錄其表示的路線
creator.create('Individual', list, fitness=creator.FitnessMin) 

#-----------------------------------
## 個(gè)體編碼
# 用字典存儲(chǔ)所有參數(shù) -- 配送中心坐標(biāo)通熄、顧客坐標(biāo)、顧客需求找都、到達(dá)時(shí)間窗口唇辨、服務(wù)時(shí)間、車型載重量
dataDict = {}
# 節(jié)點(diǎn)坐標(biāo)能耻,節(jié)點(diǎn)0是配送中心的坐標(biāo)
dataDict['NodeCoor'] = [(15,12), (3,13), (3,17), (6,18), (8,17), (10,14),
                           (14,13), (15,11), (15,15), (17,11), (17,16),
                            (18,19), (19,9), (19,21), (21,22), (23,9),
                            (23,22), (24,11), (27,21), (26,6), (26,9),
                            (27,2), (27,4), (27,17), (28,7), (29,14),
                            (29,18), (30,1), (30,8), (30,15), (30,17)
                           ]
# 將配送中心的需求設(shè)置為0
dataDict['Demand'] = [0,50,50,60,30,90,10,20,10,30,20,30,10,10,10,
                      40,51,20,20,20,30,30,30,10,60,30,20,30,40,20,20]
# 將配送中心的服務(wù)時(shí)間設(shè)置為0
dataDict['Timewindow'] = [(0,0), (3,4), (4,5), (5,7), (2,9), (10,11),
                         (12,13), (11,13), (1,5), (11,15), (8,9),
                          (10,12), (15,16), (18,20), (1,7), (6,9),
                          (8,9), (4,6), (7,11), (10,15), (13,14),
                          (17,18), (16,18), (10,12), (13,14), (5,8),
                          (6,8), (7,9), (3,6), (4,5), (6,9)
                         ]
dataDict['MaxLoad'] = 400
dataDict['ServiceTime'] = 1
dataDict['Velocity'] = 30 # 車輛的平均行駛速度

def genInd(dataDict = dataDict):
    '''生成個(gè)體赏枚, 對(duì)我們的問(wèn)題來(lái)說(shuō),困難之處在于車輛數(shù)目是不定的'''
    nCustomer = len(dataDict['NodeCoor']) - 1 # 顧客數(shù)量
    perm = np.random.permutation(nCustomer) + 1 # 生成顧客的隨機(jī)排列,注意顧客編號(hào)為1--n
    pointer = 0 # 迭代指針
    lowPointer = 0 # 指針指向下界
    permSlice = []
    # 當(dāng)指針不指向序列末尾時(shí)
    while pointer < nCustomer -1:
        vehicleLoad = 0
        # 當(dāng)不超載時(shí)晓猛,繼續(xù)裝載
        while (vehicleLoad < dataDict['MaxLoad']) and (pointer < nCustomer -1):
            vehicleLoad += dataDict['Demand'][perm[pointer]]
            pointer += 1
        if lowPointer+1 < pointer:
            tempPointer = np.random.randint(lowPointer+1, pointer)
            permSlice.append(perm[lowPointer:tempPointer].tolist())
            lowPointer = tempPointer
            pointer = tempPointer
        else:
            permSlice.append(perm[lowPointer::].tolist())
            break
    # 將路線片段合并為染色體
    ind = [0]
    for eachRoute in permSlice:
        ind = ind + eachRoute + [0]
    return ind
#-----------------------------------
## 評(píng)價(jià)函數(shù)
# 染色體解碼
def decodeInd(ind):
    '''從染色體解碼回路線片段饿幅,每條路徑都是以0為開頭與結(jié)尾'''
    indCopy = np.array(deepcopy(ind)) # 復(fù)制ind,防止直接對(duì)染色體進(jìn)行改動(dòng)
    idxList = list(range(len(indCopy)))
    zeroIdx = np.asarray(idxList)[indCopy == 0]
    routes = []
    for i,j in zip(zeroIdx[0::], zeroIdx[1::]):
        routes.append(ind[i:j]+[0])
    return routes

def calDist(pos1, pos2):
    '''計(jì)算距離的輔助函數(shù)戒职,根據(jù)給出的坐標(biāo)pos1和pos2栗恩,返回兩點(diǎn)之間的距離
    輸入: pos1, pos2 -- (x,y)元組
    輸出: 歐幾里得距離'''
    return np.sqrt((pos1[0] - pos2[0])*(pos1[0] - pos2[0]) + (pos1[1] - pos2[1])*(pos1[1] - pos2[1]))

#
def loadPenalty(routes):
    '''輔助函數(shù),因?yàn)樵诮徊婧屯蛔冎锌赡軙?huì)產(chǎn)生不符合負(fù)載約束的個(gè)體洪燥,需要對(duì)不合要求的個(gè)體進(jìn)行懲罰'''
    penalty = 0
    # 計(jì)算每條路徑的負(fù)載磕秤,取max(0, routeLoad - maxLoad)計(jì)入懲罰項(xiàng)
    for eachRoute in routes:
        routeLoad = np.sum([dataDict['Demand'][i] for i in eachRoute])
        penalty += max(0, routeLoad - dataDict['MaxLoad'])
    return penalty

def calcRouteServiceTime(route, dataDict = dataDict):
    '''輔助函數(shù),根據(jù)給定路徑蚓曼,計(jì)算到達(dá)該路徑上各顧客的時(shí)間'''
    # 初始化serviceTime數(shù)組亲澡,其長(zhǎng)度應(yīng)該比eachRoute小2
    serviceTime = [0] * (len(route) - 2)
    # 從倉(cāng)庫(kù)到第一個(gè)客戶時(shí)不需要服務(wù)時(shí)間
    arrivalTime = calDist(dataDict['NodeCoor'][0], dataDict['NodeCoor'][route[1]])/dataDict['Velocity']
    arrivalTime = max(arrivalTime, dataDict['Timewindow'][route[1]][0])
    serviceTime[0] = arrivalTime
    arrivalTime += dataDict['ServiceTime'] # 在出發(fā)前往下個(gè)節(jié)點(diǎn)前完成服務(wù)
    for i in range(1, len(route)-2):
        # 計(jì)算從路徑上當(dāng)前節(jié)點(diǎn)[i]到下一個(gè)節(jié)點(diǎn)[i+1]的花費(fèi)的時(shí)間
        arrivalTime += calDist(dataDict['NodeCoor'][route[i]], dataDict['NodeCoor'][route[i+1]])/dataDict['Velocity']
        arrivalTime = max(arrivalTime, dataDict['Timewindow'][route[i+1]][0])
        serviceTime[i] = arrivalTime
        arrivalTime += dataDict['ServiceTime'] # 在出發(fā)前往下個(gè)節(jié)點(diǎn)前完成服務(wù)
    return serviceTime

def timeTable(distributionPlan, dataDict = dataDict):
    '''輔助函數(shù),依照給定配送計(jì)劃纫版,返回每個(gè)顧客受到服務(wù)的時(shí)間'''
    # 對(duì)于每輛車的配送路線床绪,第i個(gè)客戶受到服務(wù)的時(shí)間serviceTime[i]是min(TimeWindow[i][0], arrivalTime[i])
    # arrivalTime[i] = serviceTime[i-1] + 服務(wù)時(shí)間 + distance(i,j)/averageVelocity
    timeArrangement = [] #容器,用于存儲(chǔ)每個(gè)顧客受到服務(wù)的時(shí)間
    for eachRoute in distributionPlan:
        serviceTime = calcRouteServiceTime(eachRoute)
        timeArrangement.append(serviceTime)
    # 將數(shù)組重新組織為與基因編碼一致的排列方式
    realignedTimeArrangement = [0]
    for routeTime in timeArrangement:
        realignedTimeArrangement = realignedTimeArrangement + routeTime + [0]
    return realignedTimeArrangement

def timePenalty(ind, routes):
    '''輔助函數(shù)其弊,對(duì)不能按服務(wù)時(shí)間到達(dá)顧客的情況進(jìn)行懲罰'''
    timeArrangement = timeTable(routes) # 對(duì)給定路線癞己,計(jì)算到達(dá)每個(gè)客戶的時(shí)間
    # 索引給定的最遲到達(dá)時(shí)間
    desiredTime = [dataDict['Timewindow'][ind[i]][1] for i in range(len(ind))]
    # 如果最遲到達(dá)時(shí)間大于實(shí)際到達(dá)客戶的時(shí)間,則延遲為0梭伐,否則延遲設(shè)為實(shí)際到達(dá)時(shí)間與最遲到達(dá)時(shí)間之差
    timeDelay = [max(timeArrangement[i]-desiredTime[i],0) for i in range(len(ind))]
    return np.sum(timeDelay)/len(timeDelay)

def calRouteLen(routes,dataDict=dataDict):
    '''輔助函數(shù)痹雅,返回給定路徑的總長(zhǎng)度'''
    totalDistance = 0 # 記錄各條路線的總長(zhǎng)度
    for eachRoute in routes:
        # 從每條路徑中抽取相鄰兩個(gè)節(jié)點(diǎn),計(jì)算節(jié)點(diǎn)距離并進(jìn)行累加
        for i,j in zip(eachRoute[0::], eachRoute[1::]):
            totalDistance += calDist(dataDict['NodeCoor'][i], dataDict['NodeCoor'][j])    
    return totalDistance

def evaluate(ind, c1=10.0, c2=500.0):
    '''評(píng)價(jià)函數(shù)糊识,返回解碼后路徑的總長(zhǎng)度绩社,c1, c2分別為車輛超載與不能服從給定時(shí)間窗口的懲罰系數(shù)'''
    routes = decodeInd(ind) # 將個(gè)體解碼為路線
    totalDistance = calRouteLen(routes)
    return (totalDistance + c1*loadPenalty(routes) + c2*timePenalty(ind,routes)),
#-----------------------------------
## 交叉操作
def genChild(ind1, ind2, nTrail=5):
    '''參考《基于電動(dòng)汽車的帶時(shí)間窗的路徑優(yōu)化問(wèn)題研究》中給出的交叉操作摔蓝,生成一個(gè)子代'''
    # 在ind1中隨機(jī)選擇一段子路徑subroute1,將其前置
    routes1 = decodeInd(ind1) # 將ind1解碼成路徑
    numSubroute1 = len(routes1) # 子路徑數(shù)量
    subroute1 = routes1[np.random.randint(0, numSubroute1)]
    # 將subroute1中沒(méi)有出現(xiàn)的顧客按照其在ind2中的順序排列成一個(gè)序列
    unvisited = set(ind1) - set(subroute1) # 在subroute1中沒(méi)有出現(xiàn)訪問(wèn)的顧客
    unvisitedPerm = [digit for digit in ind2 if digit in unvisited] # 按照在ind2中的順序排列
    # 多次重復(fù)隨機(jī)打斷愉耙,選取適應(yīng)度最好的個(gè)體
    bestRoute = None # 容器
    bestFit = np.inf
    for _ in range(nTrail):
        # 將該序列隨機(jī)打斷為numSubroute1-1條子路徑
        breakPos = [0]+random.sample(range(1,len(unvisitedPerm)),numSubroute1-2) # 產(chǎn)生numSubroute1-2個(gè)斷點(diǎn)
        breakPos.sort()
        breakSubroute = []
        for i,j in zip(breakPos[0::], breakPos[1::]):
            breakSubroute.append([0]+unvisitedPerm[i:j]+[0])
        breakSubroute.append([0]+unvisitedPerm[j:]+[0])
        # 更新適應(yīng)度最佳的打斷方式
        # 將先前取出的subroute1添加入打斷結(jié)果贮尉,得到完整的配送方案
        breakSubroute.append(subroute1)
        # 評(píng)價(jià)生成的子路徑
        routesFit = calRouteLen(breakSubroute) + loadPenalty(breakSubroute)
        if routesFit < bestFit:
            bestRoute = breakSubroute
            bestFit = routesFit
    # 將得到的適應(yīng)度最佳路徑bestRoute合并為一個(gè)染色體
    child = []
    for eachRoute in bestRoute:
        child += eachRoute[:-1]
    return child+[0]

def crossover(ind1, ind2):
    '''交叉操作'''
    ind1[:], ind2[:] = genChild(ind1, ind2), genChild(ind2, ind1)
    return ind1, ind2

#-----------------------------------
## 突變操作
def opt(route,dataDict=dataDict, k=2, c1=1.0, c2=500.0):
    # 用2-opt算法優(yōu)化路徑
    # 輸入:
    # route -- sequence,記錄路徑
    # k -- k-opt朴沿,這里用2opt
    # c1, c2 -- 尋求最短路徑長(zhǎng)度和滿足時(shí)間窗口的相對(duì)重要程度
    # 輸出: 優(yōu)化后的路徑optimizedRoute及其路徑長(zhǎng)度
    nCities = len(route) # 城市數(shù)
    optimizedRoute = route # 最優(yōu)路徑
    desiredTime = [dataDict['Timewindow'][route[i]][1] for i in range(len(route))]
    serviceTime = calcRouteServiceTime(route)
    timewindowCost = [max(serviceTime[i]-desiredTime[1:-1][i],0) for i in range(len(serviceTime))]
    timewindowCost = np.sum(timewindowCost)/len(timewindowCost)
    minCost = c1*calRouteLen([route]) +  c2*timewindowCost # 最優(yōu)路徑代價(jià)
    for i in range(1,nCities-2):
        for j in range(i+k, nCities):
            if j-i == 1:
                continue
            reversedRoute = route[:i]+route[i:j][::-1]+route[j:]# 翻轉(zhuǎn)后的路徑
            # 代價(jià)函數(shù)中需要同時(shí)兼顧到達(dá)時(shí)間和路徑長(zhǎng)度
            desiredTime = [dataDict['Timewindow'][reversedRoute[i]][1] for i in range(len(reversedRoute))]
            serviceTime = calcRouteServiceTime(reversedRoute)
            timewindowCost = [max(serviceTime[i]-desiredTime[1:-1][i],0) for i in range(len(serviceTime))]
            timewindowCost = np.sum(timewindowCost)/len(timewindowCost)
            reversedRouteCost = c1*calRouteLen([reversedRoute]) + c2*timewindowCost
            # 如果翻轉(zhuǎn)后路徑更優(yōu)猜谚,則更新最優(yōu)解
            if  reversedRouteCost < minCost:
                minCost = reversedRouteCost
                optimizedRoute = reversedRoute
    return optimizedRoute

def mutate(ind):
    '''用2-opt算法對(duì)各條子路徑進(jìn)行局部?jī)?yōu)化'''
    routes = decodeInd(ind)
    optimizedAssembly = []
    for eachRoute in routes:
        optimizedRoute = opt(eachRoute)
        optimizedAssembly.append(optimizedRoute)
    # 將路徑重新組裝為染色體
    child = []
    for eachRoute in optimizedAssembly:
        child += eachRoute[:-1]
    ind[:] = child+[0]
    return ind,
#-----------------------------------
## 注冊(cè)遺傳算法操作
toolbox = base.Toolbox()
toolbox.register('individual', tools.initIterate, creator.Individual, genInd)
toolbox.register('population', tools.initRepeat, list, toolbox.individual)
toolbox.register('evaluate', evaluate)
toolbox.register('select', tools.selTournament, tournsize=2)
toolbox.register('mate', crossover)
toolbox.register('mutate', mutate)

## 生成初始族群
toolbox.popSize = 100
pop = toolbox.population(toolbox.popSize)

## 記錄迭代數(shù)據(jù)
stats=tools.Statistics(key=lambda ind: ind.fitness.values)
stats.register('min', np.min)
stats.register('avg', np.mean)
stats.register('std', np.std)
hallOfFame = tools.HallOfFame(maxsize=1)

## 遺傳算法參數(shù)
toolbox.ngen = 400
toolbox.cxpb = 0.8
toolbox.mutpb = 0.1

## 遺傳算法主程序
pop,logbook=algorithms.eaMuPlusLambda(pop, toolbox, mu=toolbox.popSize, 
                                      lambda_=toolbox.popSize,cxpb=toolbox.cxpb, mutpb=toolbox.mutpb,
                   ngen=toolbox.ngen ,stats=stats, halloffame=hallOfFame, verbose=True)

輸出結(jié)果:

from pprint import pprint

def calLoad(routes):
    loads = []
    for eachRoute in routes:
        routeLoad = np.sum([dataDict['Demand'][i] for i in eachRoute])
        loads.append(routeLoad)
    return loads

bestInd = hallOfFame.items[0]
distributionPlan = decodeInd(bestInd)
bestFit = bestInd.fitness.values
print('最佳運(yùn)輸計(jì)劃為:')
pprint(distributionPlan)
print('總運(yùn)輸距離為:')
print(evaluate(bestInd,c1=0,c2=0))
print('各輛車上負(fù)載為:')
print(calLoad(distributionPlan))

timeArrangement = timeTable(distributionPlan) # 對(duì)給定路線,計(jì)算到達(dá)每個(gè)客戶的時(shí)間
# 索引給定的最遲到達(dá)時(shí)間
desiredTime = [dataDict['Timewindow'][bestInd[i]][1] for i in range(len(bestInd))]
# 如果最遲到達(dá)時(shí)間大于實(shí)際到達(dá)客戶的時(shí)間赌渣,則延遲為0魏铅,否則延遲設(shè)為實(shí)際到達(dá)時(shí)間與最遲到達(dá)時(shí)間之差
timeDelay = [max(timeArrangement[i]-desiredTime[i],0) for i in range(len(bestInd))]
print('到達(dá)各客戶的延遲為:')
print(timeDelay)

# 畫出迭代圖
minFit = logbook.select('min')
avgFit = logbook.select('avg')
plt.plot(minFit, 'b-', label='Minimum Fitness')
plt.plot(avgFit, 'r-', label='Average Fitness')
plt.xlabel('# Gen')
plt.ylabel('Fitness')
plt.legend(loc='best')

## 計(jì)算結(jié)果如下:
##最佳運(yùn)輸計(jì)劃為:
#[[0, 1, 2, 3, 4, 6, 0],
# [0, 9, 0],
# [0, 14, 29, 17, 30, 26, 18, 23, 21, 0],
# [0, 8, 25, 15, 16, 0],
# [0, 10, 11, 24, 12, 0],
# [0, 5, 7, 0],
# [0, 28, 27, 20, 19, 22, 13, 0]]
#總運(yùn)輸距離為:
#(278.62210617851554,)
#各輛車上負(fù)載為:
#[200, 30, 150, 131, 120, 110, 160]
#到達(dá)各客戶的延遲為:
#[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

迭代過(guò)程可視化:

GA for VRPTW_iterations

配送路線可視化:

Derived routes
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坚芜,隨后出現(xiàn)的幾起案子览芳,更是在濱河造成了極大的恐慌,老刑警劉巖货岭,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件路操,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡千贯,警方通過(guò)查閱死者的電腦和手機(jī)屯仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搔谴,“玉大人魁袜,你說(shuō)我怎么就攤上這事《氐冢” “怎么了峰弹?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芜果。 經(jīng)常有香客問(wèn)我鞠呈,道長(zhǎng),這世上最難降的妖魔是什么右钾? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任蚁吝,我火速辦了婚禮,結(jié)果婚禮上舀射,老公的妹妹穿的比我還像新娘窘茁。我一直安慰自己,他們只是感情好脆烟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布山林。 她就那樣靜靜地躺著,像睡著了一般邢羔。 火紅的嫁衣襯著肌膚如雪驼抹。 梳的紋絲不亂的頭發(fā)上桑孩,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音砂蔽,去河邊找鬼洼怔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛左驾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播极谊,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼诡右,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了轻猖?” 一聲冷哼從身側(cè)響起帆吻,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咙边,沒(méi)想到半個(gè)月后猜煮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡败许,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年王带,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片市殷。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愕撰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出醋寝,到底是詐尸還是另有隱情搞挣,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布音羞,位于F島的核電站囱桨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅绰。R本人自食惡果不足惜舍肠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望办陷。 院中可真熱鬧貌夕,春花似錦、人聲如沸民镜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)制圈。三九已至们童,卻和暖如春畔况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慧库。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工跷跪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人齐板。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓吵瞻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親甘磨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橡羞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容