[Genetic algorithm raster graph path planning python]

Just say a few words

  1. Just to say a few words, please read other articles for the basic logic of the algorithm. There are many, so I won’t introduce them. This article aims to provide a python code for future learners to understand and learn GA with more information, and at the same time provide an easy way for those who only need to simply use GA to solve path planning.
  2. Note that 99.99% of the code in this article cannot run if it is not a path planning or raster graph model!
  3. Considering that the effect of the randomly generated first-generation path solution is very, very poor, it is difficult to generate a path that looks like that. Therefore, the first-generation solution of this GA comes from the first-generation solution of the basic ACO algorithm.
  4. In theory, the GA provided in this article can run successfully as long as ACO can generate the first-generation solution, regardless of the map size. Of course, the stability of the effect is not guaranteed.
  5. Generally speaking, the quality of the first-generation solution will directly affect the iteration speed and convergence solution of GA. Therefore, the effect of GA in this article is not equivalent to the effect of the real GA algorithm.
  6. In this article, the GA selection operator directly uses the competition selection method; the crossover operator uses the single-point crossover method, that is, disconnecting and crossing at any same intersection point of the two paths; the mutation operator mainly uses the random position of The node is deleted, and the new path is judged to be optimal, and the best is retained.

python code

0. Pre-installed libraries

pip install numpy pandas pygame matplotlib   #四个库
pip install lddya==3.0.4     #作者的自用库,里面有一些工具下文会用到(本文写作时的版本3.0.4)

1. Call the template

from lddya.Algorithm import ACO,GA    #导入ACO,GA算法
from lddya.Draw import ShanGeTu,IterationGraph    #导入栅格图、迭代图的绘制模块
from lddya.Map import Map   #导入地图文件读取模块
m = Map()    
m.load_map_file('map.txt')    # 读取地图文件
aco = ACO(map_data=m.data,start=[0,0],end=[19,19],max_iter = 1)    #设置迭代次数为1,以获得ACO生成的初代解
aco.run()                     # ACO迭代运行
ga = GA(map_data=m.data
             ,chroms_list=aco.success_way_list.copy()  #将蚁群算法得到的初代路径赋值给遗传算法的初代
             ,population = 100
             ,max_iter=50
            )                  #实例化一个GA算法
ga.run()                       #GA迭代运行
sfig = ShanGeTu(map_data = m.data)       # 初始化栅格图绘制模块
sfig.draw_way(ga.way_data_best)   # 绘制路径信息,路径数据由ga.way_data_best提供。
sfig.save('123.jpg')                   #保存栅格图数据为'123.jpg'
dfig = IterationGraph(data_list= [ga.generation_aver,ga.generation_best],   #绘制数据: 每代平均、每代最优路径信息
                    style_list=['--r','-.g'],    # 线型 (规则同plt.plot()中的线型规则)
                    legend_list=['每代平均','每代最优'],  # 图例 (可选参数,可以不写)
                    xlabel='迭代次数',           # x轴标签,默认“x”
                    ylabel= '路径长度'           # y轴标签,默认“y”
                    )                 # 初始化迭代图绘制模块        
dfig.save('321.jpg')                     #迭代图保存为321.jpg

2.Map file

The following is the content of a map file. The numerical positions correspond to the black and white grids of the raster map one-to-one.

PS: Note that Map.data is essentially a 0-1 matrix based on numpy.

00000000000000000000
00000000000000000000
00000000000000000000
00010010000000000000
00001100011001100000
00010100110000000000
00000000110000000000
00000000000000000000
00010010000010110000
00010010000110110000
00000010000100100000
00100000000000100000
01100111000100000000
01000111111100000000
00000100000110001001
00000000000100001001
00000000011000001101
00001111000000000100
00000100000000000000
00110000001100000000

3. Raster chart + iterative chart

The raster image shown in the figure below is the image corresponding to the raster data file above. The raster coordinates in the upper left corner are [0,0], y increases downward and x increases toward the right. So the lower right corner is [19,19].
Insert image description here

The figure below is the iteration diagram of the algorithm.
Insert image description here

3.GA category

If you just want to use GA to solve the problem, there is no need to read the following content. If the effect is not good, just adjust the parameters or improve it. Considering that there are friends who only want to read the code, I attach the GA code here for learning reference.

class GA():
    def __init__(self, map_data,chroms_list,population=50, max_iter = 100, cross_pro=0.95, mut_pro=0.15):
        '''
            Function
            --------
            初始化一个基本GA用于解决路径规划。

            Params
            ------
            map_data     : n*n-np.array  -> 尺寸为n*n的地图数据。\n
            chroms_list  : 
            population   : int -> 种群大小,默认为50。\n
            max_iter     : int -> 最大迭代次数,默认为50。\n
            cross_pro    : float -> 交叉概率,默认0.95。\n
            mut_pro      : float -> 变异概率,默认0.15。\n

            Return
            ------
            返回一个用于解决路径规划的GA实例。
            
        '''
        self.map_data = map_data   
        self.population = population   
        self.max_iter = max_iter
        self.cross_pro = cross_pro
        self.mut_pro = mut_pro
        self.chroms_list = chroms_list  # 初代染色体信息库, 初始化由外部提供
        self.child_list = []    #子代染色体信息库
        self.generation_aver = []   #记录每代评估值的平均值的列表
        self.generation_best = []   #记录每代评估值的最优值的列表
        self.global_best_value = 999  #全局已知最优评估值,默认为一个很大的数,999一般够用。
        self.way_data_best = []       #全局已知最优评估值对应的路径信息
        self.temporary = []   #临时变量,用来保存ACO生成的初代路径


    def eval_fun(self,way):
        '''
            Function
            --------
            染色体评估函数。

            Params
            ------
            way : n-list -> 一条包含n个节点信息的路径列表。如: [[0,0],[0,1],[1,2]...]

            Return
            ------
            length   : float -> 路径长度
        '''
        length = 0
        for j1 in range(len(way)-1):
            a1 = abs(way[j1][0]-way[j1+1][0])+abs(way[j1][1]-way[j1+1][1])
            if a1 == 2:
                length += 1.41421
            else:
                length += 1
        return length
    
    def run(self):
        '''
            Function
            --------
            函数运行主流程
        '''
        for i in range(self.max_iter):
            # Step 1:执行种群选择
            self.child_list = self.selection()
            self.evalution()
            # Step 2: 交叉 
            self.cross()
            # Step 3: 变异
            self.mutation()
            # Step 4: 子代并入父代中
            self.chroms_list = self.child_list.copy()
            print('iter ',i,':','平均值:',self.generation_aver[-1],'最优值:',self.generation_best[-1])
    
    def selection(self):
        '''
            Function:
            ---------
                选择算子。选择法则为竞标赛选择法。即每次随机选择3个个体出来竞争,最优秀的那个个体的染色体信息继承到下一代。
            
            Params:
            --------
                None

            Return:
            -------
                child_1:    list-list
                    子代的染色体信息
        '''
        chroms_1 = self.chroms_list.copy()
        child_1 = []
        for i in range(self.population):
            a_1 = []    # 3个选手
            b_1 = []    # 3个选手的成绩
            for j in range(3):
                a_1.append(np.random.randint(0,len(chroms_1)))
            for j in a_1:
                b_1.append(self.eval_fun(chroms_1[j]))
            c_1 = b_1.index(min(b_1))  # 最好的是第几个
            child_1.append(chroms_1[a_1[c_1]])  #最好者进入下一代
        return child_1


    def evalution(self):
        '''
            Function
            --------
            对child_list中的染色体进行评估
        '''
        e = []
        for i in self.child_list:
            e.append(self.eval_fun(i))
        self.generation_aver.append(sum(e)/len(e))
        self.generation_best.append(min(e))
        if min(e)<=self.global_best_value:
            self.global_best_value = min(e)
            k = e.index(min(e))
            self.way_data_best = self.child_list[k]

    def cross(self):
        '''
            Function
            --------
            交叉算子
        '''
        child_1 = []   # 参与交叉的个体
        for i in self.child_list:  #依据交叉概率挑选个体
            if np.random.random()<self.cross_pro:
                child_1.append(i)
        if len(child_1)%2 != 0:    #如果不是双数
            child_1.append(child_1[np.random.randint(0,len(child_1))])  #随机复制一个个体
        for i_1 in range(0,len(child_1),2):
            child_2 = child_1[i_1]       #交叉的第一个个体
            child_3 = child_1[i_1+1]     #交叉的第二个个体
            sames = []     #两条路径中,相同的节点的各自所在位置,放这里,留待后面交叉
            for k,i in enumerate(child_2[1:-1]):          # 找相同节点(除起点与终点)
                if i in child_3:
                    sames.append([k+1,child_3.index(i)])   # [index in child_2, index in child_3  ]
            if len(sames) != 0:
                a_1 = np.random.randint(0,len(sames))
                cut_1 = sames[a_1][0]
                cut_2 = sames[a_1][1]
                child_1[i_1] = child_2[:cut_1]+child_3[cut_2:]            #新的覆盖原染色体信息
                child_1[i_1+1] = child_3[:cut_2]+child_2[cut_1:]
        for i in child_1:                     #交叉后的染色体个体加入子代群集中
            self.child_list.append(i)

    def mutation(self):        
        '''
            Function
            --------
            变异算子
        '''
        child_1 = []   # 参与变异的个体
        for i in self.child_list:  #依据变异概率挑选个体
            if np.random.random()<self.mut_pro:
                child_1.append(i)
        for i in range(0,len(child_1)):
            child_2 = np.array(child_1[i])
            index = np.random.randint(low=1,high=len(child_2)-1)    #随机选择一个中间节点
            point_a,point_b,point_c = child_2[index-1:index+1+1]
            v1 = point_b-point_a
            v2 = point_c-point_b
            dot_product = np.dot(v1, v2)  #计算这两个向量的内积
            if dot_product <0 :          #如果两个向量之间的夹角为锐角,则必产生冗余
                del child_1[i][index]    #删除被选中的节点
            elif dot_product == 0:       #如果两个向量之间的夹角为直角,则需要进一步判断
                if 0 in v1:               #如果第一个向量是水平或者垂直的,则必存在冗余
                    del child_1[i][index]    #删除被选中的节点
                elif self.map_data[(point_a[0]+point_c[0])//2,(point_a[1]+point_c[1])//2]==0:                     #如果第一个向量是对角的,且point_a与point_b之间的节点为可通行,则可优化
                    child_1[i][index]  = [(point_a[0]+point_c[0])//2,(point_a[1]+point_c[1])//2]  #修改被选中的节点为中间节点

        for i in child_1:                     #交叉后的染色体个体加入子代群集中
            self.child_list.append(i)

Finally, I wish you all the best in your future studies!

Guess you like

Origin blog.csdn.net/qq_33531262/article/details/132716714