Bonjour tout le monde,
J'essaie de résoudre le problème du voyageur, autrement dit soit un ensemble de villes, passer par chacune d'entre elles une et une seule fois, le tout en minimisant la distance totale parcourue. Pour l'instant, je fais ça avec un algorithme de recuit simulé basique (sans redémarrages etc).
Je stocke les distances dans une matrice de taille N*N avec N le nombre de villes, en considérant que la distance entre A et B est la même qu'entre B et A (j'aurais pu utiliser juste une matrice triangulaire mais bon, c'est pas le plus important là). Pour savoir la distance entre la ville $i$ et la ville $j$, je regarde l'élément d'abscisse $i$ et d'ordonnée $j$ de la matrice. D'ailleurs, cette matrice est stockée au format CSV. La voilà:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 0,29,82,46,68,52,72,42,51,55,29,74,23,72,46 29,0,55,46,42,43,43,23,23,31,41,51,11,52,21 82,55,0,68,46,55,23,43,41,29,79,21,64,31,51 46,46,68,0,82,15,72,31,62,42,21,51,51,43,64 68,42,46,82,0,74,23,52,21,46,82,58,46,65,23 52,43,55,15,74,0,61,23,55,31,33,37,51,29,59 72,43,23,72,23,61,0,42,23,31,77,37,51,46,33 42,23,43,31,52,23,42,0,33,15,37,33,33,31,37 51,23,41,62,21,55,23,33,0,29,62,46,29,51,11 55,31,29,42,46,31,31,15,29,0,51,21,41,23,37 29,41,79,21,82,33,77,37,62,51,0,65,42,59,61 74,51,21,51,58,37,37,33,46,21,65,0,61,11,55 23,11,64,51,46,51,51,33,29,41,42,61,0,62,23 72,52,31,43,65,29,46,31,51,23,59,11,62,0,59 46,21,51,64,23,59,33,37,11,37,61,55,23,59,0 |
Analyse des solutions
J'ai fait quelques tests en laissant tourner l'algo en boucle pendant la nuit jusqu'à trouver de bonnes solutions. La meilleure que j'ai obtenue était [9, 1, 12, 0, 10, 3, 5, 7, 13, 11, 2, 6, 4, 8, 14]
(ce sont les indices des villes, comptés à partir de 0) ce qui donne 294 km. Ca a mis vraiment longtemps avant de trouver cette solution, du coup je me suis demandé quelle était l'efficacité moyenne de cet algo. Pour cela, je fais 100 calculs de solution et je moyenne les distances obtenues. Il en est ressorti que plus la température initiale est élevée (j'avais mis $10^{50}$) ou plus le taux de refroidissement est faible, et meilleures sont les solutions, ce qui n'a rien de surprenant.
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #!/usr/bin/python2 -u # The -u flag is very important, as it prevents output buffering import random, math, csv, sys, copy, time class TSP: def __init__(self, distancematrix): """ distancematrix is a matrix of size N*N, giving the distance between two cities A matrix allows us to have different distances from A to B than from B to A, but that wouldn't |make sense. Hence we'll use an upper triangular matrix, the rest of the values are undefined and we don't care |about them """ self.distances = distancematrix self.size = len(self.distances[0]) def stillaccept(self, rdist,sdist,t): if random.random() < math.exp((rdist - sdist)/t): return True else: return False def simulated_annealing(self, init_temp, coolrate): s = Solution(self.size) # Initial derpy random solution but that guarantees that all cities are |visited best = s bestdistance = best.totaldistance(self.distances) sdist = bestdistance t = init_temp while t > 1: r = copy.deepcopy(s).tweak() rdist = r.totaldistance(self.distances) if rdist < sdist or self.stillaccept(rdist,sdist,t): s = r sdist = rdist t *= 1-coolrate # Cool system if rdist < bestdistance: best = r bestdistance = rdist return best, bestdistance class Solution: def __init__(self,size): self.sol = [i for i in range(0,size)] random.shuffle(self.sol) def tweak(self): """ Swap two random cities """ i,j = random.randrange(0,len(self.sol)), random.randrange(0,len(self.sol)) temp = self.sol[i] self.sol[i] = self.sol[j] self.sol[j] = temp return self def totaldistance(self,distancematrix): distance = 0 for i in range(0, len(self.sol)-1): distance += distancematrix[self.sol[i]][self.sol[i+1]] return distance def __repr__(self): return self.sol.__repr__() strmat = list(csv.reader(open(sys.argv[1], "r"), delimiter=',')) matrix = [ map(float, strmat[i]) for i in range (0, len(strmat)) ] tsp = TSP(matrix) mean = 0.0 for i in range(0,100): s, d = tsp.simulated_annealing(10**float(sys.argv[2]), float(sys.argv[3])) mean += d / 100.0 print("Average: {}".format(mean)) #bestsol, bestdist = tsp.simulated_annealing(10**float(sys.argv[2]), float(sys.argv[3])) #while True: # newsol, newdist = tsp.simulated_annealing(float(sys.argv[2]), float(sys.argv[3])) # if newdist < bestdist: # bestsol = newsol # bestdist = newdist # print("[{}] Best distance so far: {} for {}".format(time.strftime("%H:%M"),bestdist, bestsol)) |
Pour faire tourner ce code, il faut faire ./tsp.py <fichier csv> <exposant de la température> <taux de refroidissement>
(j'ai mis respectivement 50 et 0.1 pour les tests je crois). Si vous voulez que j'explique plus comment j'ai procédé, dites-le vu que je commente très peu.
Maintenant, je voudrais améliorer encore les performances, autrement qu'en modifiant température et taux de refroidissement, car la valeur moyenne de 433 km est franchement éloignée des 300 km presque optimaux. La page Wikipédia suggère de faire des redémarrages, mais qu'est-ce que je peux faire d'autre pour améliorer l'algo ou le code ?
Merci d'avance