Università di Modena e Reggio Emilia
Facoltà di Ingegneria di Reggio Emilia
Metodi di Ottimizzazione per la Logistica e la Produzione
Euristici per il Capacitated Vehicle Routing Problem
Metodo delle due fasi:
• Cluster First – Route Second
• Route First – Cluster Second
Tesina di Monica Onfiani e Francesco Rovesti
Introduzione
Gli euristici che utilizzano il metodo delle due fasi cercano di risolvere il
Capacitated Vehicle Routing Problem, nel quale devono essere
create le route necessarie a servire n-1 clienti i, ciascuno con domanda
q(i), utilizzando veicoli con capacità Q, che partono da un unico
deposito capace di soddisfare le domande di tutti i clienti. L’obiettivo è
quello di minimizzare i costi di servizio, che nel problema base
corrispondono ai costi per percorrere le route (minimizzare la distanza
percorsa).
Istanze utilizzate:
‘1_n16_Q90.dat’
‘2_n32_Q100.dat’
‘3_n80_Q100.dat’
‘4_n150_Q200.dat’
Fonti:
1
1
1
1
deposito
deposito
deposito
deposito
e
e
e
e
15 clienti, capacità veicoli 90
31 clienti, capacità veicoli 100
79 clienti, capacità veicoli 100
149 clienti, capacità veicoli 200
http://www.or.deis.unibo.it/research_pages/ORinstances/VRPLIB/VRPLIB.html
Cluster First – Route Second (1)
L’algoritmo CFRS risolve un Bin Packing Problem assegnando i vertici
clienti alle route (bin) “aperte” incrementalmente secondo l’algoritmo First
Fit Decreasing, che inserisce i vertici in ordine decrescente di domanda.
Successivamente, viene risolto un TSP su ogni gruppo di clienti utilizzando
l’algoritmo Closest Neighbor.
### BPP_FirstFitDecreasing + TSP_ClosestNeighbor ###
Bubble_Sort
! procedura che ordina i clienti in
! ordine decrescente di domanda
BPP_First_Fit_Decreasing
Create_Routeline
TSP_Routeline
Abbiamo applicato una Ricerca Locale alla soluzione trovata facendo
scambi di tipo 1-1 (swap) tra vertici intra-route ed inter-route.
### BPP_FFD + TSP_CN + SWAP ###
LS_swap
Cluster First – Route Second (2)
L’ordinamento dei vertici in input al BPP First Fit Decreasing potrebbe essere
effettuato non solo in base alla domanda, ma anche in base ad altri
parametri quali ad esempio la distanza dal deposito o la distanza media di un
vertice da tutti gli altri vertici.
Essendo numerosi i possibili criteri di ordinamento, abbiamo proposto un
algoritmo iterativo che introduce una randomizzazione sull’ordinamento.
### ITERATIVO 100n: BPP_FFD_random + TSP_CN + SWAP ###
zbest:=1000000000000
forall (t in 1..100*n) do
Bubble_Sort_random
BPP_First_Fit_Decreasing
Create_Routeline
TSP_Routeline
LS_swap
if (zswap<zbest) then
zbest:=zswap
forall (j in 0..2*n-1)
routeline_best(j):=routeline(j)
end-if
end-do
Route First – Cluster Second (1)
L’algoritmo RFCS crea una Big Route contenente tutti i vertici, dove la
sequenza di visita dei vertici è determinata utilizzando l’algoritmo Closest
Neighbor. Successivamente, la Big Route viene clusterizzata in più
route: scorrendo la Big Route, ogni volta che la domanda dei vertici della
route corrente eccede la capacità del veicolo, si torna al deposito e poi si
apre una nuova route che parte dal primo vertice non visitato della Big
Route.
### TSP_ClosestNeighbor + CutRoutes: TSP da vertice 1
TSP_closest_neighbor(1)
! Big Route che parte dal vertice 1
VRP_cut_routes
### TSP_CN + CutRoutes. Iterativo: TSP da tutti i vertici
zbest:=10000000000
forall (k in vertici | k>=1) do
TSP_closest_neighbor(k) ) ! Big Route che parte da k
VRP_cut_routes
if (zcr<zbest) then
zbest := zcr
end-do
Route First – Cluster Second (2)
Infine, abbiamo applicato una Ricerca Locale sulla soluzione trovata facendo
scambi 1-1 (swap) tra vertici intra-route ed inter-route.
### TSP_CN + CutRoutes + SWAP sulla Best Solution
LS_swap
Un’alternativa rispetto all’algoritmo RFCS appena descritto è quella di
creare la Big Route iniziale visitando i vertici secondo l’algoritmo Farthest
Insertion: inizialmente viene creata una route contenente il vertice più
lontano dal deposito, poi ad ogni iterazione, viene inserito nella route il
vertice non visitato più lontano da uno dei vertici della route. La posizione in
cui viene inserito è quella che determina l’incremento minimo della funzione
obiettivo.
### ALTERNATIVA ###
### TSP_FarthestInsertion + CutRoutes + Swap
TSP_farthest_insertion
VRP_cut_routes
LS_swap
Esecuzione modello VRP-TI
con aggiunta automatica di Subtour Elimination Constraints
N-Q
MODELLO VRP TI
z / LB
t
16 - 90
z = 278.73
90.4 s
32 – 100
LB = 785.1
dopo più di 3 ore
Soluzione ottima
dell’istanza con 16 vertici
CFRS – Risultati ottenuti
N-Q
BPP_FF + TSP_CN (1)
BPP_FF + TSP_CN +
SWAP (2)
Iterativo 100n:
BPP_FF (ordinamento random)
+ TSP_CN + SWAP (3)
z
t
z (% *)
t
z (%)
t
16 - 90
448.04
0.063 s
354.38 (20.9%)
0.063 s
305.78 (13.71%)
1.856 s
32 – 100
1644.44
0.031 s
1539.08 (6.4 %)
0.047 s
1158.83 (24.7 %)
14.851 s
80 - 100
4384.48
0.094 s
3253.94 (25.8%)
0.125 s
2919.06 (10.3%)
246.246 s
150 - 200
5289.34
0.203 s
3848.75 (27.24%)
0.312 s
3440.03 (10.6%)
1064.75 s
*: variazione % rispetto al valore della f. o. della colonna precedente
CFRS - Migliori soluzioni euristiche trovate per le
diverse istanze
Tutte le soluzioni migliori sono state ottenute con l’algoritmo (3)
RFCS – Risultati ottenuti
N-Q
TSP_CN(1) +
CutRoutes (4)
TSP_CN(N) +
CutRoutes (5)
TSP_CN(N) + CutRoutes
+ SWAP (6)
TSP_Farthest + CutRoutes
+ SWAP (7)
z
t
z (% *)
t
z (%)
t
z (%)
t
16 - 90
324.89
0.06 s
286.32
(20.5%)
0.06 s
286.32 (0%)
0.07 s
303.83 (-6.1%)
0s
32 – 100
1050.57
0.036 s
906.08 (7.1%)
0.055 s
871.25 (3.8%)
0.06 s
823.51 (5.5%)
0.014 s
80 - 100
2308.67
0.08 s
2045.33 (5.9%)
0.33 s
2038.47 (0.3%)
0.35 s
2007.4 (1.5%)
0.14 s
150 - 200
2021.35
0.12 s
1900.11 (1.5%)
1.375 s
1873.85 (1.4%)
1.46 s
1968.8 (-5.1%)
0.697 s
*: variazione % rispetto al valore della f. obiettivo della colonna precedente
CFRS - Migliori soluzioni euristiche trovate per le
diverse istanze
Soluzioni migliori ottenute rispettivamente con gli algoritmi (5), (7), (7), (6)
Confronto tra le migliori f. o. per tutte le istanze
n = 16
n =32
n = 80
n = 150
z best CFRS
305.78 (Alg. 3) (* )
1158.83 (Alg. 3)
2919.06 (Alg. 3)
3440.03 (Alg. 3)
z best RFCS
286.32 (Alg. 5) (* *)
823.51 (Alg. 7)
2007.4 (Alg. 7)
1873.85 (Alg. 6)
Confronto tra il valore ottimo della f. o. dell’istanza
a 16 vertici e i risultati degli algoritmi
448.04
354.38
305.78
278.73
Modello
esatto
324.89
286.32
286.32
303.83
Algoritmo (1) Algoritmo (2) Algoritmo (3) Algoritmo (4) Algoritmo (5) Algoritmo (6) Algoritmo (7)
CFRS
* è superiore del 9,7% rispetto al valore ottimo
** è superiore del 2,7% rispetto al valore ottimo
RFCS
Osservazioni finali
CFRS:
• Gli euristici CFRS sembrano essere peggiori dei RFCS: il CFRS cerca di
ottimizzare il numero delle route “riempiendole” il più possibile in base
alle domande, mentre la distanza tra i vertici viene valutata solo
all’interno delle singole route; vertici vicini potrebbero quindi essere
posizionati in route diverse. Questo limite è in parte attenuato dalla
ricerca locale swap, che però facendo scambi 1-1 non può alterare la
cardinalità di ogni route.
• l’algoritmo (3) consente di trovare una soluzione migliore rispetto a (2),
però essendo risolto 100*n volte, per istanze grandi richiede una quantità
di tempo incrementale da tenere in considerazione.
RFCS:
• l’algoritmo (5) ottiene sempre una soluzione migliore dell’algoritmo (4) in
tempi molto rapidi, soluzione che può essere ulteriormente migliorata
dallo swap (algoritmo (6)).
• ci saremmo aspettati che l’algoritmo (7) desse risultati migliori
dell’algoritmo (6) per ogni istanza, ma dai risultati ottenuti non possiamo
decretare questa superiorità ((7) ha la best performance nel 50% dei
casi)).
Fine
BPP_First Fit Decreasing
Input: clienti ordinati per domanda decrescente, domande dei clienti.
Output: vettore che per ogni cliente indica a che route è assegnato.
Complessità: O(nm) *
procedure BPP_First_Fit_Decreasing
nroute:= 1,
capres(1):= Q
forall (j in vertici) do
o(vert_dec(j)) := -1
forall (i in 1..nroute)
if (q(vert_dec(j)) <= capres(i)) then
o(vert_dec(j)):= i
capres(i):= capres(i) q(vert_dec(j))
break
end-if
if (o(vert_dec(j))=-1) then
nroute:= nroute+1
o(vert_dec(j)):= nroute
capres(nroute):= Q - q(vert_dec(j))
end-if
end-do
o(0):=0
BACK
end-procedure
* m è il massimo numero di route, quindi nroute<=m
Create Routeline
Input: vettore che per ogni cliente indica a che route è assegnato.
Esempio: (0)0 (1)1 (2)1 (3)3 (4)3 (5)2 (6)3 (7)2 (8)1 (9)3 (10)2 (11)3 (12)1 (13)2 (14)2 (15)3
Output: vettore monodimensionale che riporta una dopo l’altra le route con i
clienti visitati. Le route sono divise da uno 0 (ritorno al deposito).
Esempio: 0 12 2 1 8 0 5 14 7 13 10 0 6 11 4 3 9 15 0
Complessità: O(nm) *
procedure Create_Routeline
forall(t in 0..2*n-1)
routeline(t):=-1
routeline(0):=0
t:=1
forall (j in 1..nroute) do
forall (k in vertici) do
if (o(k)=j) then
routeline(t):=k,
end-if
end-do
routeline(t):=0
t:=t+1
end-do
end-procedure
* m è il massimo numero di route, quindi nroute<=m
t:=t+1
BACK
TSP Routeline
Input: vettore monodimensionale che riporta una dopo l’altra le route con i
clienti visitati. Le route sono divise da uno 0 (ritorno al deposito).
Output: vettore di input con i clienti di ogni route visitati secondo il criterio
dell’euristico TSP Closest Neighbor, costo della soluzione.
Complessità: O(n3), considerando che il TSP_CN ha complessità O(n2)
procedure TSP_Routeline
forall (j in 0..2*n-1) vis(j):= -1
k:=0, a:=0, b:= -1
while (k<>2*n-1 and routeline(k)<>-1 and routeline(k+1)<>-1) do
vis(routeline(k)):=0
i:=a+1
while (routeline(i)<>0)
i:=i+1
b:=i-1
! b individua la posizione del vertice precedente allo 0
successivo
TSP_closest_neighbor(a,b)
k:=b+1
! aggiorno k per trovare l'intervallo della route successiva
a:=b+1
b:=-1
end-do
k:=0, zeur:=0
while (k<>2*n-1 and routeline(k)<>-1 and routeline(k+1)<>-1)
zeur:=zeur+costi(routeline(k),routeline(k+1))

k:=k+1
end-procedure
TSP Closest Neighbor
Input: posizione di cliente iniziale e finale di una route non ancora ordinata.
Output: route ordinata secondo il criterio dell’euristico Closest Neighbor.
Complessità: O(n2)
procedure TSP_closest_neighbor (a,b: integer)
length := b-a+1
forall (j in a..b) vis(routeline(j)):=0
v_from:=routeline(a),
vis(v_from):=1,
ocn(0):=0
forall (i in 1..length-1) do
cmin := 1000000000000000
forall (j in a..b)
if (costi(routeline(v_from),routeline(j))<cmin and
vis(routeline(j))=0)
then
cmin:=costi(routeline(v_from),routeline(j))
v_to:=routeline(j)
end-if
ocn(i) := v_to,
vis(v_to):=1,
v_from:=v_to
end-do
forall (i in 1..length-1)
routeline(a+i):=ocn(i)
BACK
end-procedure
Local Search Swap
Input: vettore con i clienti di ogni route visitati secondo il criterio
dell’euristico TSP Closest Neighbor, costo della soluzione.
Output: vettore ottimizzato con ricerca locale swap (intra ed inter-route).
Complessità: O(n2) *
procedure LS_swap
improved:=true
Carico_route ! Carico_route aggiorna un array che per ogni vertice indica il
carico totale della route a cui appartiene
while (improved=true) do
improved:=false
forall (i in 1..2*n-2 | routeline(i)<>-1) do
forall (j in 1..2*n-1 | j>=i+2 and routeline(j)<>-1) do
if (routeline(i)<>0 and routeline(j)<>0) then Calcolo_delta
if (delta<0 and soluzione_rimane_ammissibile) then
scambio dei vertici nella routeline
aggiornamento z
aggiornamento route a cui appartengono i vertici
aggiornamento carichi di ogni route: Carico_route
improved:=true, break
chiusura degli if e dei cicli for
if (improved=true) then
break
end-do
end-procedure
BACK
* la complessità è data dai due cicli for innestati, che si ripetono fino a ché non ci sono più miglioramenti. Il while
esterno per semplicità lo consideriamo una costante moltiplicativa k della complessità, anche se il numero di
scambi dipende in modo probabilistico da n.
Bubble Sort Random
Input: domande dei clienti (vettore q)
Output: vettore contenente i clienti ordinati in modo random
Complessità: O(n2)
procedure Bubble_Sort_random
forall(i in vertici)
vert_dec(i) := i
k := n-2
while (k>0) do
forall (i in 0..k) do
if (random*q(vert_dec(i))<random*q(vert_dec(i+1))) then
help := vert_dec(i+1)
vert_dec(i+1) := vert_dec(i)
vert_dec(i) := help
end-if
end-do
k:=k-1
end-do
end-procedure
BACK
Cut Routes
Input: vettore contenente l’ordine di visita di tutti i vertici ottenuto in
seguito all’applicazione dell’euristico Closest Neighbor (Big Route).
Output: vettore contenente le route ammissibili, ottenute inserendo un
ritorno al deposito ogni volta che si supera la capacità del veicolo.
Complessità: O(n)
procedure VRP_cut_routes
carico := 0
forall (j in 0..2*n-1)
routeline(j):=0
M:=1,
zcr := zcn ! inizializzo al costo di della big route
i:= 1,
j:=0
while (i<n) do
j:=j+1
if (carico + q(ocn(i)) <= Q) then
carico:=carico+q(ocn(i)), routeline(j):=ocn(i), i:=i+1
else
M:=M+1
routeline(j) := 0, carico := 0
zcr := zcr + costi(ocn(i-1),0) - costi(ocn(i-1),ocn(i))
+
costi(0,ocn(i))
end-if
end-do
BACK
zcr := zcr + costi(ocn(n-1),0)
end-procedure
TSP Farthest Insertion
Input: deposito, vertici e costi/distanze tra i vertici.
Output: route creata con l’euristico costruttivo Farthest Insertion.
Complessità: O(n3)
procedure TSP_farthest_insertion
vis(0):=1,
da:=0,
ocn(0):=0,
cmax:=0
forall (k in vertici | vis(k)=0)
if (costi(0,k)>cmax) then
cmax:=costi(0,k) , kstar:=k
vis(kstar):=1 , ocn(1):=kstar !kstar vertice più lontano dal dep.
forall (t in vertici | t>=2) do
cmax:=0
! cerco il vertice più lontano da uno dei vertici del tour
forall (j in vertici | vis(j)=1)
forall (k in vertici | vis(k)=0)
if (costi(j,k)>cmax) then cmax:=costi(j,k) , kstar:=k
cinsmin := 1000000000000000
forall (i in 0..t-1)
if (costo di inserimento di k tra ocn(i) e ocn(i+1) < cinsmin) then
h:=i
cinsmin := costi di inserimento di k
Inserisco kstar in ocn (aggiornamento di ocn non riportato)
vis(kstar):=1
end-do
zcn := sum(i in 0..n-2) costi(ocn(i),ocn(i+1))
BACK
end-procedure