TSP con eliminazione di sottocicli Un commesso viaggiatore deve visitare 7 clienti in modo da minimizzare la distanza percorsa. Le distanze (in Km) tra ognuno dei clienti sono come segue: 7-8 9 7 9-8 79 9-7 7 7-9 9-8 9-8 (la matrice delle distanze è simmetrica). Formulare un modello di PLI e proporre un programma in AMPL che risolva il problema. Approfondimento (opzionale) Si parli di un possibile approccio euristico per la soluzione del TSP, considerando che sulla matrice delle distanze vale la disuguaglianza triangolare. Documento preparato da Leo Liberti
Soluzione Formalizziamo il problema come un grafo diretto completo G(V, A) dove V sono i clienti e A sono tutti gli archi possibili tra i nodi in V. I pesi sugli archi sono le distanze tra i clienti. Si deve cercare un cammino di peso minimo che tocchi tutti i nodi una e una sola volta. Formulazione Parametri:. sia V l insieme dei clienti da visitare;. per ogni i, j V sia d ij la distanza tra il cliente i e il cliente j. Variabili: per ogni i j V, sia x ij = se il commesso viaggia direttamente tra i e j, e altrimenti (x ij {, ). Funzione obiettivo: min i j V d ij x ij. Vincoli: x ij = i V (solo un successore) j V,j i i V,i j i S,j V \S x ij = j V (solo un predecessore) x ij S V (nessun sottociclo) x ij {, i j V. L ultimo vincolo dice che per ogni partizione dei vertici, ci dev essere almeno un arco nel taglio corrispondente. Questo impedisce la formazione di sottocicli non triviali (che ovviamente definirebbero una partizione dei vertici con un taglio associato vuoto). Dato che il numero dei sottoinsiemi propri S di un insieme V è un numero esponenziale nella cardinalità di V, la dimensione di un istanza del problema esposto sopra è esponenziale (e dunque non accettabile). Bisogna dunque adottare un opportuna strategia di soluzione. Inizialmente, il problema viene rilassato cancellando completamente il vincolo nessun sottociclo (in tal modo la dimensione dell istanza è polinomiale). Il rilassamento ottenuto viene risolto tramite le comuni tecniche di PLI. La soluzione trovata può non essere ammissibile per il vincolo nessun sottociclo : ovvero, potrebbero esserci dei sottocicli non banali. Cerchiamo quindi il sottociclo più piccolo, inseriamo nel problema rilassato un solo vincolo della classe di vincoli nessun sottociclo in modo che il sottociclo trovato venga rotto, e ri-ottimizziamo il problema. Si continua iterativamente finché la soluzione non presenta più alcun sottociclo. A quel punto la soluzione trovata è quella ottima. Documento preparato da Leo Liberti
Tentiamo prima un approccio manuale alla generazione dei cicli da rompere. Risolviamo prima il modello seguente. Modello AMPL # modello per tsp param n >, integer; set V :=..n; param d{v,v >= ; param numerocicli >=, integer, default ; set ciclo{..numerocicli; var x{v,v binary; minimize costociclo : sum{i in V, j in V : i!= j d[i,j]*x[i,j]; subject to successore {i in V : sum{j in V : i!= j x[i,j] = ; subject to predecessore {j in V : sum{i in V : i!= j x[i,j] = ; subject to nocicli {k in..numerocicli : sum{i in ciclo[k], j in V diff ciclo[k] x[i,j] >= ; Dati AMPL # tsp.dat param n := 7; param d : 7 := 8 9 7 9 8 79 9 7 7 7 9 9 8 9 8 7 ; Per risolverlo, usiamo il seguente file.run, in cui si inizializza la matrice delle distanze in modo che sia simmetrica. Algoritmo AMPL: file tsp-simple.run # leggi modello e dati model tsp.mod; data tsp.dat; # rendi simmetrica la matrice delle distanze for {i in V, j in V : i > j { let d[i,j] := d[j,i]; # risolvi option solver cplex; solve; # stampa display costociclo; display x; Si noti che siccome ciclo e inizializzato a una lista di insiemi vuoti, i vincoli di rottura dei sottocicli di fatto non sono stati inseriti. Otteniamo la soluzione seguente, che corrisponde infatti a tre cicli distinti: (,,, ), (,, ) e (, 7, ). Soluzione CPLEX (non ottima) Documento preparato da Leo Liberti
costociclo = 7 x [*,*] : 7 := 7 ; In pratica, abbiamo la situazione come nella figura qui sotto. 7 Aggiungiamo perciò un vincolo di rottura del sottociclo (,,, ) al modello: per S = {,, si impone i S,j V \S x ij. In AMPL, è sufficiente aggiungere le istruzioni seguenti nel file tsp-simple.run, prima dell istruzione solve;, e risolvere nuovamente il modello lanciando ampl < tsp-simple.run. let numerocicli := ; let ciclo[] := {,, ; Si ottiene la soluzione seguente, che essendo un ciclo Hamiltoniano, è ottima. Soluzione CPLEX (ottima) costociclo = x [*,*] : 7 := 7 ; La ciclo Hamiltoniano ottimo è (,,, 7,,,, ), raffigurato sotto. In generale, la soluzione non è unica (possono esistere più cicli Hamiltoniani con lo stesso costo), quindi si potrebbe trovare una soluzione diversa da questa. 7 Documento preparato da Leo Liberti
Descriviamo ora un algoritmo in AMPL per la generazione automatica dei sottocicli da rompere (per eseguirlo, usare il comando ampl < tsp.run). Algoritmo AMPL: file tsp.run # tsp.run - algoritmo per la rottura dei sottocicli # usa cplex e non stampare i messaggi del solutore numerico option solver cplex; option solver_msg ; # leggi modello e dati model tsp.mod; data tsp.dat; let numerocicli := ; # strutture dati per l algoritmo param nodosuccessore{v >=, integer; param nodocorrente >=, integer; # rendi simmetrica la matrice delle distanze for {i in V, j in V : i > j { let d[i,j] := d[j,i]; # algoritmo: risolvi il modello senza vincoli di rottura # dei sottocicli, trova un sottociclo, aggiungi il vincolo # corrispondente, e quando non esistono piu sottocicli propri, esci param termination binary; let termination := ; repeat while (termination = ) { # risolvi il problema solve; let numerocicli := numerocicli + ; # trova i successori di ogni nodo for {i in V { let nodosuccessore[i] := sum{j in V : j!= i j * x[i,j]; # trova un sottociclo let nodocorrente := ; let ciclo[numerocicli] := {; repeat { let ciclo[numerocicli] := ciclo[numerocicli] union {nodocorrente; let nodocorrente := nodosuccessore[nodocorrente]; until (nodocorrente = ); # stampa il sottociclo che vogliamo rompere printf "ciclo: ("; for {i in ciclo[numerocicli] { printf "%d, ", i ; printf ")\n"; # verifica se si puo terminare if (card(ciclo[numerocicli]) >= n) then { # se il sottociclo include tutti i nodi e Hamiltoniano, esci let termination := ; Documento preparato da Leo Liberti
printf "costo del ciclo hamiltoniano minimo: %d\n", costociclo; Soluzione numerica ciclo: (,,, ) ciclo: (,,, 7,,,, ) costo del ciclo hamiltoniano minimo: Documento preparato da Leo Liberti
Approfondimento: Soluzione Euristica Per la soluzione euristica, si consideri l euristica -approssimata per il TSP Euclideo (cioè la cui matrice delle distanze rispetta la disuguaglianza triangolare) ideata da Christofides. L euristica costruisce un ciclo Hamiltoniano nel modo seguente.. Si costruisce l albero di supporto di costo minimo T nel grafo G.. Si costruisce il matching M di costo minimo tra i vertici del grafo con cardinalità dispari.. Si forma un ciclo Euleriano che consiste dell unione di T e M. Si noti che ogni nodo ha un numero pari di lati adiacenti.. Per ogni vertice v tale che δ(v) (T M) > (ovvero per ogni v da cui si dipartono più di lati di T e M), si contraggono tutte le coppie di lati adiacenti a v tranne una. L operazione di contrazione di una coppia di lati {u, v, {v, w consiste nel sostituire questi lati con il lato {u, w (l operazione è sempre possibile perché il grafo è completo). Effettuando quest operazione per tutte le coppie di lati adiacenti a v tranne una, si rispettano i vincoli di predecessore e successore, perché v a quel punto avrà esattamente lati adiacenti. Applichiamo l euristica descritta all istanza in questione. Le figure sotto rappresentano: il grafo originale, l albero di supporto di costo minimo, il matching di costo minimo tra vertici di stella con cardinalità dispari, e l unione dei due a formare un ciclo Euleriano. Si noti tuttavia che in questa particolare istanza tutti i nodi hanno già grado, quindi il ciclo è già Hamiltoniano. Il costo del ciclo ottenuto con quest euristica è, e quindi in questo caso l euristica trova un ciclo Hamiltoniano di costo minimo. / / 9 original graph / 9 / 7 / 7 7 / / 8 / 8 / 8 / 9 / 7 / 9 / 7 / 9 8 / 8 9 / 9 / 8 9 / / / Figure : Il grafo originale. Documento preparato da Leo Liberti 7
/ / 9 sptree cost = 8 / 9 / 7 / 7 7 / / 8 / 8 / 8 / 9 / 7 / 9 / 7 / 9 8 / 8 9 / 9 / 8 9 / / / Figure : L albero di costo minimo. / / 9 matching cost: 9 / 9 / 7 / 7 7 / / 8 / 8 / 8 / 9 / 7 / 9 / 7 / 9 8 / 8 9 / 9 / 8 9 / / / Figure : Il matching di costo minimo tra vertici di grado dispari. Documento preparato da Leo Liberti 8
/ / 9 tour cost: / 9 / 7 / 7 7 / / 8 / 8 / 8 / 9 / 7 / 9 / 7 / 9 8 / 8 9 / 9 / 8 9 / / / Figure : Unione di albero e matching (soluzione approssimata). / / 9 optimal tour cost: / 9 / 7 / 7 7 / / 8 / 8 / 8 / 9 / 7 / 9 / 7 / 9 8 / 8 9 / 9 / 8 9 / / / Figure : La soluzione ottimale (uguale, in questo caso, a quella approssimata). Documento preparato da Leo Liberti 9