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