Using genetic algorithm and Python code to solve the Tsp problem

Tsp issue

Traveling salesman issues, namely TSP The Traveling Salesman Problem, also translated as the Traveling Salesman Problem or the Salesman Problem, is one of the famous problems in the field of mathematics. Assuming a traveling salesman wants to visit n cities, he must choose the path he wants to take. The limit of the path is that each city can only be visited once, and he must eventually return to the original city of departure. The goal of path selection is to require the path distance to be the minimum value among all paths

Introduction to Genetic Algorithm

Application of Genetic Algorithm in TSP Problems

Code implementation

I won't go into other detailed details, as genetic algorithms are generally relatively easy to implement. I directly pasted the Python code, and if there are any unfamiliar ones, after reading the code, I basically understand it.

Node Generation

Firstly, we need to randomly generate some nodes to simulate the cities in the tsp problem:

def tsp_port(port_num=50, x_range=2500, y_range=2500):
return np.random.randint(0, x_range, port_num), np.random.randint(0, y_range, port_num)

GA

Then, we can write the genetic algorithm:

class Gant:
def __init__(self, port_num=50, x_range=2500, y_range=2500, gants_num=1000):
  print('init...')
  self.x, self.y = tsp_port(port_num, x_range, y_range)           #randomly generated city coordinates 
  self.port_num = port_num                                        #number of nodes 
  self.x_range = x_range                                          #map range 
  self.y_range = y_range
  self.gants_num = gants_num                                      #population size 
  self.gants = []
  self.get_new_gants()                                            #randomly initialize population genes 
def get_new_gants(self):
  for i in range(self.gants_num):
      self.gants.append(np.random.permutation(self.port_num))
def get_distance(self, i):
  distance = 0
  gant = self.gants[i]
  for j in range(1, self.port_num):
      distance += pow(self.x[gant[j]]-self.x[gant[j-1]], 2) + pow(self.y[gant[j]]-self.y[gant[j-1]], 2)
  return distance
def get_distances(self):
  dists = []
  for i in range(self.gants_num):
      dists.append([self.get_distance(i), i])
  return sorted(dists)
def select(self):
  res = []
  dists = self.get_distances()
  strong = int(self.gants_num/5)
  for i in range(strong):          #take out the strong part, the strong can directly choose 
      res.append(dists[i])
  sele = np.random.permutation(strong*4)
  for i in range(strong):          #extract the lucky group 
      res.append(dists[i+strong])
  return res
def cover(self, population):                    #breed 
  new_population = []
  rand = np.random.permutation(int(self.gants_num/5))
  for i in range(int(self.gants_num/5)):
      father1 = self.gants[population[rand[i] * 2][1]]
      father2 = self.gants[population[rand[i] * 2 + 1][1]]
      cut_point_low = np.random.randint(self.port_num-1)
      cut_point_high = np.random.randint(cut_point_low, self.port_num)
      cut_gant = father1[cut_point_low:cut_point_high]
      son1 = cp.copy(father1)
      son2 = cp.copy(father2)
      father_to_son1 = []
      k = 0
      for j in range(self.port_num):
          if father2[j] not in cut_gant:
              father_to_son1.append(father2[j])
          else:
              son2[j] = cut_gant[k]
              k += 1
      # print(cut_point_low, cut_point_high, np.shape(father_to_son1))
      # if self.port_num - cut_point_high + cut_point_low != np.shape(father_to_son1)[0]:
      #     print(father_to_son1, cut_gant, father1, father2, len(set(father1)))
      if cut_point_low == 0 and cut_point_high == self.port_num:
          son1 = father2
          son2 = father1
      elif cut_point_low == 0:
          son1[cut_point_high:self.port_num] = np.array(father_to_son1)
      elif cut_point_high == self.port_num:
          son1[0:cut_point_low] = np.array(father_to_son1)
      else:
          son1[0:cut_point_low] = np.array(father_to_son1[0:cut_point_low])
          son1[cut_point_high:self.port_num] = np.array(father_to_son1[cut_point_low:])
      new_population.append(father1)
      new_population.append(father2)
      new_population.append(son1)
      new_population.append(son2)
  #variation 
  rand = np.random.choice(int(self.gants_num/5)*4, int(self.gants_num/5), replace=False)
  for i in rand:
      new_gant = cp.copy(new_population[i])
      rand_mod = np.random.randint(int(self.gants_num/40))
      # if rand_mod == 0:
      for l in range(rand_mod):
          exchange = np.random.choice(self.port_num, 2)
          a = new_gant[exchange[0]]
          new_gant[exchange[0]] = new_gant[exchange[1]]
          new_gant[exchange[1]] = a
      # elif rand_mod == 1:
      #     exchangelong = np.random.randint(1, int(self.port_num/10))
      #     exchangepoint = np.random.randint(exchangelong, self.port_num - exchangelong)
      #     a = new_gant[exchangepoint: exchangepoint + exchangelong]
      #     new_gant[exchangepoint: exchangepoint + exchangelong] = 
      #         new_gant[exchangepoint - exchangelong: exchangepoint]
      #     new_gant[exchangepoint - exchangelong: exchangepoint] = a
      # else:
      #     # randint the random range of is [x,y)
      #     exchangelong = np.random.randint(1, int(self.port_num / 10))
      #     exchangepoint1 = np.random.randint(0, self.port_num - exchangelong*2)
      #     exchangepoint2 = np.random.randint(exchangepoint1, self.port_num - exchangelong)
      #     a = new_gant[exchangepoint1: exchangepoint1 + exchangelong]
      #     new_gant[exchangepoint1: exchangepoint1 + exchangelong] = 
      #         new_gant[exchangepoint2: exchangepoint2 + exchangelong]
      #     new_gant[exchangepoint2: exchangepoint2 + exchangelong] = a
      if len(set(new_gant)) != self.port_num:
          # print(exchangelong, new_gant, rand_mod, len(set(new_gant)))
          exit()
      new_population.append(new_gant)
      # print(np.shape(new_gant), type(new_gant))
  self.gants = new_population
  # for i in range(int(self.gants_num/5)):
  #     print(np.shape(np.random.permutation(self.port_num)), type(np.random.permutation(self.port_num)))
  #     new_population.append(np.random.permutation(self.port_num))
  # self.gants = new_population
def draw_roadline(self):
  dists = self.get_distances()
  shortest_line = dists[0][1]
  plt.scatter(self.x, self.y)
  plt.plot(self.x[self.gants[shortest_line]], self.y[self.gants[shortest_line]])
  plt.show()
  plt.ion()
def train(self, op=1000):
  for i in range(op):
      res = self.select()
      self.cover(res)
      dist = self.get_distances()
      if i%500 == 0:
          self.draw_roadline()
      print("distance:", dist[0][0], "op:", i)
      # print(self.gants, np.shape(self.gants[0]))
def train_op(self, i):
  res = self.select()
  self.cover(res)
  dist = self.get_distances()
  print("distance:", dist[0][0], "op:", i )

Training

I have selected 40 cities here and iterated 1000 times.

gant = Gant(port_num=40)
gant.train(1000)
gant.draw_roadline()

Result

Before training, the randomly generated path is as follows:

After 500 iterations:

After 1000 generations:
It can be seen that due to the gradual stabilization of the species' morphology, the approximate route is almost the same as that of 500 iterations, only some routes have been optimized... I have seen some GA algorithms in the intelligent algorithm library that can achieve very good results, perhaps because my breeding and mutation algorithms are not written well enough

Summary

Overall, the mutation part in the code is not written very well because for the Tsp problem, sometimes the difference between the suboptimal path and the optimal path is very large. If it is not possible to obtain a path that is easily confused between the suboptimal and optimal paths in the early stage of population iteration, it may only regretfully see the suboptimal path at the end and be helpless due to insufficient mutation ability. If the mutation ability is very sufficient, mutation is equivalent to randomly generating new species, and the optimization speed in multi node tsp is extremely slow. I haven't come up with any solution yet. If you are interested, you can discuss it together!