【CAD算法】【遗传算法】采用遗传算法优化Coons Patch曲面程序(python实现)[4]

1.内容回顾

关于Coons Patch的定义和生成程序,请参考https://blog.csdn.net/iwanderu/article/details/103624398, 本节内容会部分沿用上一节代码。

关于遗传算法,看到很多高赞的文章,都很清晰的讲解了遗传算法的逻辑和思路,但是很少看到可以直接参考的代码。实际上,遗传算法它的可移植性很高,它的杂交部分,基因突变部分还有循环部分基本上所有的遗传算法都是类似的,解决不同问题的代码部分的区别主要在fitness函数的定义上还有染色体(字符串)的生成逻辑上。

遗传算法可以用以下的伪代码进行抽象:

在这里插入图片描述
在第4部分,遗传算法实现的具体代码将会依次按步骤讲解。由于这是解决Coons Patch表面优化,所以在第3部分会讲解相关的背景代码,相关内容请参考https://blog.csdn.net/iwanderu/article/details/103624398。

2.题目描述

In this project you will use Genetic Algorithm to find the blending functions alpha(u) and beta(v) of Coons patch to construct a minimal-area Coons patch of four curves. The specifics are as follows.
(1) The four input curves are Bezier curves of degree 5 (6 control points).
(2) The alpha(u) (also beta(v)) can be modeled as a Bezier curve of degree 3 with its first and fourth control point on (0, 1) and (1, 0) respectively. Also, let the u of the 2nd and 3rd control point be 0.333333 and 0.666667 respectively. Then, the four control points of alpha(u) are (0, 1), (0.333333, alpha1), (0.666667, alpha2), and (1, 0); alpha1 and alpha2 are therefore the optimization variables (so are beta1 and beta2 for beta(v) ).
(3) You need to numerically calculate the surface area of any Coons patch. Refer to Lab 3.(3).
(4) You now have four variables alpha1, alpha2, beta1, beta2} to search and the optimization objective is the surface area of the corresponding Coons patch. For simplification, you can assume that the range for alpha1, alpha2, beta1, beta2} is [0, 2].
(5) In addition to the surface area, you can also try other types of optimization objectives, e.g., to minimize the maximum magnitude of the principle curvatures |κ1| and |κ2| of the surface (you need to numerically calculate κ1 and κ2).
要求如下,给定一个Coons Patch曲面,其中它的四个边界曲线是已知的(用户输入),在确定的边界曲线下,Coons曲面仍然有多种可能的形状,这个形状是通过Blending function alpha(u)和beta(v)来控制的。这两个Blending function都受4个控制点约束,其中有两个控制点的x坐标是未知的,分别是alpha1和alpha2。因此,对于这两个函数而言,一共是有4个未知量。那么需要通过遗传算法来确定一个最佳的alpha1/2和beta1/2使得这个Coons曲面的面积最小。很明显,优化方向给定了,优化变量给定了,那么这是一个简单的少变量遗传算法问题。根据定义,这四个待优化的数取值范围都是在0-1之间的。

3.Coons曲面的获取

3.1头文件的引用和变量定义

头文件如下

import numpy as np
import matplotlib.pyplot as plt
import math
from mpl_toolkits.mplot3d import Axes3D
import random

所需要的变量如下

#parameters
N = 10 #mesh size of coons patch
step = 0.1#distance between neighbor mesh
value_type = np.float64
f = np.array([],dtype=value_type) #temp: shape (-1,3) all the boundary points
alpha_u = np.array([],dtype=value_type) #blending function: shape (N,1)
beta_v = np.array([],dtype=value_type)#blending function: shape (N,1)
mash_grid = np.array([],dtype=value_type) #mesh of coons patch
surface = np.array([],dtype=value_type) #coons patch

注意这个N,我们希望用100个三角形的mesh来描述这个曲面,所以在一个方向上的采样是10.

3.2Blending Function的定义

# the following function is to determine alpha(u) and beta(v)
def buildBlendingFunction(control_point,u):
    #u is current step
    #control_point is a np.array,the shape should be (2,2)=>2 points, x-y(or called u-alpha) coordinates
    #return value is a scaler => alpha(u)
    P = np.array([0,1],dtype=value_type)
    P = np.append(P,control_point)
    P = np.append(P,[1,0])
    P = P.reshape((-1,2))
    alpha = np.array([],dtype=value_type) #shape should be (1,2)
    alpha = (1-u)**3 * P[0] + 3 * (1-u)**2 * u * P[1] + 3 * (1-u) * u**2 * P[2] + u**3 * P[3]
    return alpha[0]

3.3 Coons曲面的获取

严格根据定义来确定,注意不要弄混Q1Q2P1P2.在这里,我是按照下图的顺序定义各个边界曲线的。
在这里插入图片描述

#define coons patch
def coonsPatch(Q1,Q2,P1,P2,alpha_u,beta_v,N):
    surface_item = np.array([],dtype=value_type)
    Q00 = P1[0]
    Q01 = Q2[0]
    Q10 = P2[0]
    Q11 = P2[N]
    surface = np.array([],dtype=value_type)
    for u in range(N+1):
        alpha  = alpha_u[u]
        for v in range(N+1):
            beta = beta_v[v]
            surface_item = alpha*Q1[v]+(1-alpha)*Q2[v] + beta*P1[u]+(1-beta)*P2[u] - (beta*(alpha*Q00+(1-alpha)*Q01)+(1-beta)*(alpha*Q10+(1-alpha)*Q11))
            surface = np.append(surface,surface_item)
    surface = surface.reshape((-1,3))
    return surface

3.4获取Bezier曲线

四个边界曲线是按照Bezier曲线来定义的,因此它的代码如下:

# get 4 boundary bazier curves 
def getCurve(closedOrOpen,pointsNumber,point,point_index,u):
    C = []
    n = pointsNumber - 1 # n is fewer in numbers than the total control points number. According to definition.
    point_show = np.array([],dtype=np.float64)
    for i in range(n+1):
        point_show = np.append(point_show,point[point_index + i])  
    if (closedOrOpen == 0): # if it is closed, means the oth and nth control points are the same.
        n += 1
        point_show = np.append(point_show,point[point_index])
    elif (closedOrOpen == 1):
        pass
    point_show = point_show.reshape((-1,3))
    if ((n+1) % 2 == 0):
        for i in range((n+1) / 2):
            up = 1
            down = 1
            j = n
            while (j > i):
                up *= j
                j = j - 1
            j = n - i
            while (j > 0):
                down *= j
                j = j - 1
            C.append(up / down)
    elif ((n+1) % 2 == 1):
        for i in range(n / 2):
            up = 1
            down = 1
            j = n
            while (j > i):
                up *= j
                j = j - 1
            j = n - i
            while (j > 0):
                down *= j
                j = j - 1
            C.append(up / down)
        up = 1
        down = 1
        j = n
        while (j > n/2):
            up *= j
            j = j - 1
        j = n/2
        while (j > 0):
            down *= j
            j = j - 1
        C.append(up / down)
    if (n%2 == 1):
        for i in range(int((n+1)/2)):
            C.append(C[int(n/2-i)])
    if (n%2 == 0):
        for i in range(int((n+1)/2)):
            C.append(C[int(n/2-i-1)])
    global f
    fx = 0
    fy = 0 #not this place!!
    fz = 0
    for i in range(n+1):
        fx += C[i] * u**i * (1-u)**(n-i) * point_show[i][0]
        fy += C[i] * u**i * (1-u)**(n-i) * point_show[i][1]
        fz += C[i] * u**i * (1-u)**(n-i) * point_show[i][2]
    list = []
    list.append(fx)
    list.append(fy) 
    list.append(fz)
    array_list = np.array([list],dtype=np.float64) 
    f = np.append(f,array_list)

3.5 可视化

#visualize the coons patch
def visualize(alpha1,alpha2,beta1,beta2,N,show_flag):
    # if show_flag == 1, show the coons patch
    fig = plt.figure()
    ax = fig.gca(projection='3d')

第一步,先传入各个参数

    #1.control point for blending function alpha(u) and beta(v)
    control_point1 = np.array([0.333333,alpha1,0.666667,alpha2],dtype=value_type)
    control_point1 = control_point1.reshape((2,2))
    control_point2 = np.array([0.333333,beta1,0.666667,beta2],dtype=value_type)
    control_point2 = control_point1.reshape((2,2))
    #2.control point for boundary curves(they should share 4 edge points!)
    point_curve1 = np.array([-10,10,10,-6,7,9,-2,7,5,2,8,9,6,11,11,10,10,10],dtype=value_type)
    point_curve1 = point_curve1.reshape((-1,3)) #P2
    point_curve2 = np.array([10,-10,10,6,6,13,9,-2,3,7,2,5,13,6,0,10,10,10],dtype=value_type)
    point_curve2 = point_curve2.reshape((-1,3)) #Q2
    point_curve3 = np.array([-10,-10,10,-6,-11,11,-2,-8,9,2,-7,5,6,-7,9,10,-10,10],dtype=value_type)
    point_curve3 = point_curve3.reshape((-1,3)) #P1
    point_curve4 = np.array([-10,-10,10,-13,3,0,-7,-2,-5,-9,2,3,-6,6,9,-10,10,10],dtype=value_type)
    point_curve4 = point_curve4.reshape((-1,3)) #Q1

传入参数后,就可以调用之前定义好的程序,来生成边界曲线,注意,边界曲线是通过N+1个在曲线上的点来描述的,他们都放在array里保存。

    #4.get boundary curves, all the boundary points will array in f repeatedly.  
    global Q1,Q2,P1,P2,f
    Q1 = np.array([],dtype=value_type)
    Q2 = np.array([],dtype=value_type)
    P1 = np.array([],dtype=value_type)
    P2 = np.array([],dtype=value_type)
    f = np.array([],dtype=value_type)
    u = 0
    for i in range(N+1):
        getCurve(1,6,point_curve1,0,u)
        u += step
    P2 = np.append(P2,f)
    f = np.array([],dtype=value_type)
    u = 0
    for i in range(N+1):
        getCurve(1,6,point_curve2,0,u)
        u += step
    Q2 = np.append(Q2,f)
    f = np.array([],dtype=value_type)
    u = 0
    for i in range(N+1):
        getCurve(1,6,point_curve3,0,u)
        u += step
    P1 = np.append(P1,f)
    f = np.array([],dtype=value_type)
    u = 0
    for i in range(N+1):
        getCurve(1,6,point_curve4,0,u)
        u += step
    Q1 = np.append(Q1,f)
    f = np.array([],dtype=value_type)
    Q1 = Q1.reshape((-1,3))
    Q2 = Q2.reshape((-1,3))
    P1 = P1.reshape((-1,3))
    P2 = P2.reshape((-1,3))

可以把这四条边界曲线显示出来,代码如下

    plt.plot(Q1[:,0],Q1[:,1],Q1[:,2],'r.')
    plt.plot(Q2[:,0],Q2[:,1],Q2[:,2],'r.')
    plt.plot(P1[:,0],P1[:,1],P1[:,2],'r.')
    plt.plot(P2[:,0],P2[:,1],P2[:,2],'r.')
    plt.plot(Q1[:,0],Q1[:,1],Q1[:,2],'-')
    plt.plot(Q2[:,0],Q2[:,1],Q2[:,2],'-')
    plt.plot(P1[:,0],P1[:,1],P1[:,2],'-')
    plt.plot(P2[:,0],P2[:,1],P2[:,2],'-')

之后,把coonspatch曲面显示出来,

    #6.show the surface
    global surface,mash_grid 
    surface = np.array([],dtype=value_type)
    mash_grid = np.array([],dtype=value_type)
    surface = coonsPatch(Q1,Q2,P1,P2,alpha_u,beta_v,N)
    surface = np.reshape(surface,(-1,3))
    count = N
    for i in range(count):
        for j in range(count+1):
        	mash_grid = np.append(mash_grid,surface[(count+1)*i+j])
        	mash_grid = np.append(mash_grid,surface[(count+1)*(i+1)+j])
    mash_grid = np.reshape(mash_grid,(-1,3))
    for i in range(count):
        plt.plot(surface[(count+1)*i:(count+1)*(i+1),0],surface[(count+1)*i:(count+1)*(i+1),1],surface[(count+1)*i:(count+1)*(i+1),2],'-')
        plt.plot(mash_grid[2*(count+1)*i:2*(count+1)*(i+1),0],mash_grid[2*(count+1)*i:2*(count+1)*(i+1),1],mash_grid[2*(count+1)*i:2*(count+1)*(i+1),2],'-') 
	#pass

效果如下,用mesh的方法来描述曲面。注意,mesh也是CAD的一个重要算法,对物体合理的划分,是进行有限元分析的前提条件。
在这里插入图片描述
在这里插入图片描述
最后,还有一个是否显示曲线的判断,因为遗传算法它是有很多循环的,没有必要每次都显示出曲线,因此需要一个判断,这些代码组成了getCurve()函数。

    if show_flag == 1:
        ax.legend()
        ax.set_xlabel('X')
        ax.set_ylabel('Y')
        ax.set_zlabel('Z')
        plt.show()

最后一个是Coons曲面表面积计算方法了。这里,体现了mesh网格的作用了。根据有限元的思想,一个曲面可以分割为若干个三角形来近似表示,那这些三角形面积的和就是Coons曲面的面积了。所以这个算法的核心就是确定三角形的顶点坐标和三角形面积的计算公式。

#calculate the area of coons patch
def countTriangleArea(surface,N):
    area = 0
    for i in range(N):
        for j in range(N):
            x1,y1,z1 = surface[(N+1)*i+j][0],surface[(N+1)*i+j][1],surface[(N+1)*i+j][2]
            x2,y2,z2 = surface[(N+1)*(i+1)+j][0],surface[(N+1)*(i+1)+j][1],surface[(N+1)*(i+1)+j][2]
            x3,y3,z3 = surface[(N+1)*i+j+1][0],surface[(N+1)*i+j+1][1],surface[(N+1)*i+j+1][2]
            sides0 = ((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)**0.5
            sides1 = ((x1 - x3)**2 + (y1 - y3)**2 + (z1 - z3)**2)**0.5
            sides2 = ((x3 - x2)**2 + (y3 - y2)**2 + (z3 - z2)**2)**0.5 	        
            p = (sides0 + sides1 + sides2) / 2
            area += (p * (p - sides0) * (p - sides1) * (p - sides2))**0.5
            
            x1,y1,z1 = surface[(N+1)*(i+1)+1+j][0],surface[(N+1)*(i+1)+1+j][1],surface[(N+1)*(i+1)+1+j][2] 
            sides0 = ((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)**0.5
            sides1 = ((x1 - x3)**2 + (y1 - y3)**2 + (z1 - z3)**2)**0.5
            sides2 = ((x3 - x2)**2 + (y3 - y2)**2 + (z3 - z2)**2)**0.5 	        
            p = (sides0 + sides1 + sides2) / 2
            area += (p * (p - sides0) * (p - sides1) * (p - sides2))**0.5
    return area

绘图的时候,每个曲线用N+1个点来描述,那么就在这个方向上把曲面分成了N份,两个方向上就是N^2份,每一份有2个mesh三角形,所以Coons曲面一共有2N**2个三角形。你可以数一下,这里N取10.
在这里插入图片描述

4.遗传算法

接下来,就是激动人心的遗传算法部分了,不着急,一步步来。

4.1变量

##########PART II: GENETIC ALGORITHM##########
#parameters
S = [] #initial population S
S_next = [] #next population S
fitness = [] #the fitness of each individual in S
fitness_percentage = [] #the percentage of the total fitness for each s
POPULATION = 10 #the size n of the population, and POPULATION % 2 == 0
ITERATION  = 10 #the maximum running time not exceeded
S_LENGTH = 32 #length of the solution X by a binary string s, and S_LENGTH % 4 == 0

遗传算法需要一组染色体,每一个染色体是用二进制01组成的string来描述的。大S是一个列表,存放着随机生成的初始化的所有染色体的集合,染色体数量定义为POPULATION。经过一次迭代后,下一代染色体存放在S_next中。fitness_percentage是每次筛选的重要原则,它依次存放着每个染色体被选中的概率。在本题中,一共有四个变量,而且这四个变量都是在0-1之间的,这里用8个二进制来表示其中一个变量,那么四个就需要32位二进制数,其中1-8位表示alpha1,9-16位表示alpha2,以此类推。据一个例子,如果此时alpha的字符串(101101101)2进制,那么它表示的alpha的值就是101101101除以11111111对应的值,通过这种方法,可以把染色体字符串解码为十进制的数值,也可以把十进制的结果编码为对应的字符串。类似于下面PPT的方法。
在这里插入图片描述

4.2染色体的解码和编码

首先涉及到二进制和十进制之间的转换,代码如下,参考了https://www.cnblogs.com/ddpeng/p/11302368.html ,表示感谢。

#Decimal system->Binary system
def decToBin(num):
    arry = []   #
    while True:
        arry.append(str(num % 2))  #
        num = num // 2   
        if num == 0:    
            break
    return "".join(arry[::-1]) 

#Binary system->Decimal system
def binToDec(binary):
    result = 0   #
    for i in range(len(binary)):   
        result += int(binary[-(i + 1)]) * pow(2, i)
    return result

因为也涉及到alpha的值就是101101101除以11111111的计算,所以需要定义一个函数,输入二进制的位数,能够输出对应二进制数最大值的十进制数。具一个例子,输入3,那么最大的二进制数就是111,返回它的十进制就是7。

#find the decimal number for full binary number(e.g. input 3->111->7)
def fullBinToDec(length):
    sum = 0
    for i in range(length):
        sum = sum * 2 + 1
    return sum

下一步,根据之前介绍的那样,进行编码和解码。

 #recover from s to alpha1,alpha2,beta1,beta2
def decoding(s,length):
     #s->each individual in S
     #length-> length of each s
     #alpha1,alpha2,beta1,beta2 belongs to [0,1]
     alpha1 =  float(binToDec(s[0:length/4])) / float(fullBinToDec(length/4))
     alpha2 =  float(binToDec(s[length/4:length/2])) / float(fullBinToDec(length/4))
     beta1 =  float(binToDec(s[length/2:length*3/4])) / float(fullBinToDec(length/4))
     beta2 =  float(binToDec(s[length*3/4:length])) / float(fullBinToDec(length/4))
     return alpha1,alpha2,beta1,beta2

#code alpha1,alpha2,beta1,beta2 to s
def coding(alpha1,alpha2,beta1,beta2,length):
     alpha1 = int(alpha1*fullBinToDec(length/4))
     alpha2 = int(alpha2*fullBinToDec(length/4)) 
     beta1 = int(beta1*fullBinToDec(length/4))
     beta2 = int(beta2*fullBinToDec(length/4))    
     return decToBin(alpha1)+decToBin(alpha2)+decToBin(beta1)+decToBin(beta2)

4.3第一代染色体的生成

到这里开始,就是遗传算法的核心部分了。第一代染色体首先一定要是随机生成的,因为知道它染色体的位数是32位,那么32个1对应的二进制对应的十进制数,设它为M,那么取0-M的随机数,然后转为二进制,就随机初始化了第一代染色体。

 #generate S
def generateInitialS(length,size):
    #length-> length of each s
    #size->size of the population
    global S
    for i in range(size):
        rand = random.randint(0,fullBinToDec(length))
        rand_bi = decToBin(rand)
        temp = ""
        for j in range(length-len(rand_bi)):
            temp += "0"
        rand_bi = temp + rand_bi
        S.append(rand_bi)

4.4 fitness函数的构建

当然了,进化的方向就是自然选择的方向。在这里,我们希望表面积最小,那么可以构造一个fitness = 1 / Area作为fitness函数,通过优化这个函数来达到自然选择,也就是优化的目的。

#Calculate the fitness of each individual in S
def  fitnessCalculator(S,length,N,show_flag):
    #population S: list
    #length-> length of each s
    #N->mesh size of coons patch
    # if show_flag == 1, show the coons patch
    fitness = []
    fitness_percentage = []
    fitness_sum = 0
    for i in range(len(S)):
        #recover from s to alpha1,alpha2,beta1,beta2
        alpha1,alpha2,beta1,beta2 = decoding(S[i],length)
        #print(S[i])
        print("alpha1:",alpha1)
        print("alpha2:",alpha2)
        print("beta1:",beta1)
        print("beta2:",beta2)
        #get area of coons patch
        visualize(alpha1,alpha2,beta1,beta2,N,show_flag)
        show_flag = 0 #only once
        #calculate the area of coons patch
        area = countTriangleArea(surface,N)
        print("area is ",area) 
        #calculate fitness
        fitness.append(1/area)
        fitness_sum += 1/area   
    for i in range(len(S)):
        fitness_percentage.append(fitness[i] / fitness_sum)
    return fitness,fitness_percentage #list

4.5自然选择

自然选择很自然就是按照轮盘赌的方式进行。根据fitness的计算,我们可以知道不同染色体它适应环境的概率是不一样的,概率大的会更容易被保留下来。这里思路如下,假如说有10个基因,他们的概率分别是0.1,0.1,0.1,…,fitness_percentage_add存放着依次的概率和,也就是0.1,0.2,0.3,…,此时在0-1随机生成一个数,比如说0.11,比0.1大比0.2小,那么此时自然选择了第二个染色体到下一代,以此类推。因此,在fitness中概率大的染色体更容易被保留。

#natural selection 
def naturalSelection(S,fitness,fitness_percentage):
    #Weighted Roulette Wheel
    fitness_percentage_add = []
    S_next = []
    fitness_percentage_add.append(fitness_percentage[0])
    for i in range(len(S)-1):
        fitness_percentage_add.append(fitness_percentage[i+1]+fitness_percentage_add[i])
    for i in range(len(S)):
        rand = random.random() #0-1
        for j in range(len(S)):
            if(rand < fitness_percentage_add[j]):
                S_next.append(S[j])
                break
    return S_next

4.6 交配

在自然中,动物繁殖涉及到父母染色体的随机组合,那么就需要交换染色体。在这里,策略是在染色体的随机一个位置后的染色体全部交换。这个函数封装了一次交换。对于N个群体而言,一共要交换N/2次。

#reproduction
def reproduction(S):
    global S_next
    rand1 = random.randint(0,len(S)-1)
    rand2 = random.randint(0,len(S)-1)
    rand3 = random.randint(0,len(S[0])-1)
    s1_list = list(S[rand1])
    s2_list = list(S[rand2])
    #crossover
    for i in range(len(S[0])-rand3):
        temp = s1_list[rand3+i]
        s1_list[rand3+i] = s2_list[rand3+i]
        s2_list[rand3+i] = temp
    s1 = ""
    s2 = ""
    for i in range(len(s1_list)):
        s1 += s1_list[i]
        s2 += s2_list[i]
    S_next.append(s1)
    S_next.append(s2)

在这里插入图片描述

注意,开始交换的位置是随机的。

4.7基因突变

在自然界中,基因突变广泛存在引入新的性状。在这里,存在0.1%的概率在染色体的随机位置发生基因突变。

#mutation
def mutation():
    global S
    for i in range(len(S)):
        rand = random.random() #0-1
        if rand < 0.001:
            s = list(S[i])
            rand = random.randint(0,len(s)-1)
            if(s[rand] == 0):
                s[rand] = 1
            else:
                s[rand] = 0
            s1 = ""
            for i in range(len(s)):
                s1 += s[i]
            S[i] = s1

4.8遗传算法的主体部分

完成所有功能的函数封装,在这里可以直接调用循环优化了。

def geneticAlgorithm(N,iteration):
    global S,S_next
    show_flag = 0 # not show the figure
    for i in range(iteration):
        print("############The round of iteration is,",i,"###########")
        #calculate fitness
        fitness,fitness_percentage = fitnessCalculator(S,len(S[0]),N,show_flag)
        show_flag = 0 # not show the figure
        #natural selection 
        S = naturalSelection(S,fitness,fitness_percentage)
        #reproduction
        S_next = []
        for j in range(len(S)/2):            
            reproduction(S) 
        S = S_next
        #mutation
        mutation()
        #visualize
        if(i % 5 == 0):
            show_flag = 1 #show the figure
        print("#############################################")

4.9main()函数

generateInitialS(S_LENGTH,POPULATION)
geneticAlgorithm(N,ITERATION)

分别是初始化染色体population,然后执行遗传算法,至此,全部完成。很简单吧。参考文献是香港科技大学机械工程专业CAD/CAE/CAM课程课件。
所有代码的链接如下:https://github.com/iwander-all/CAD.git

参考文献
K. TANG, Fundamental Theories and Algorithms of CAD/CAE/CAM

发布了29 篇原创文章 · 获赞 19 · 访问量 4461

猜你喜欢

转载自blog.csdn.net/iwanderu/article/details/103639677
今日推荐