【CAD算法】基于最速下降法(梯度下降)的最短路径实现(python实现)[5]

1.内容回顾

关于Bezier曲线内容,请参考https://blog.csdn.net/iwanderu/article/details/103604863
优化问题有很多解决方式,例如最速下降,批量梯度下降,随机梯度下降,LM算法,GN算法。梯度下降法是最简单的一种优化方法,缺点是在极值点附近会有zigzag。所有算法的核心就是Jacobian矩阵的确定。

2.题目描述

Refer to the figure for an example: there are m = 7 curves and points A and B, the red polygon is a trace of the curves which goes through in the order C1, C2, …, C7; this curve is said to be minimal if its length is the shortest.
在这里插入图片描述
You will read in Ci from a text file (in the (b) format of Lab 1) and key in the XYZ coordinates of points A and B. Write a computer program that will use the Steepest Ascend method to compute the minimum trace. Note that the length of the trace is a function D(u1, u2, …, u7) of 7 variables, where ui is the parameter of curve Ci with range [0, 1]. Again, you should display the intermediate result after each iteration.

首先有7个Bezier曲线,上面各取一个点,还有2个端点,求所有点的连线最短时各个点的位置。

3.Coons曲面的获取

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

import os
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import pandas as pd

np.set_printoptions(suppress = True)
#database = []
m = np.array([],dtype = np.uint8) # the number of bazier curves
pointsNumber = np.array([],dtype = np.uint8) #for the ith curve, the points number of it
closedOrOpen = np.array([],dtype = np.uint8) #for the ith curve, 0 means it is open, 1 means it is closed
point = np.array([],dtype = np.float64) #the control points for all the curves
f = np.array([],dtype=np.float64)
u_i = np.array([],dtype = np.float64)#u_i is the current u for current line for current iteration
bezierLine = np.array([],dtype=np.float64) #list of points of path
pointA = np.array([],dtype=np.float64) 
pointB = np.array([],dtype=np.float64)
D = np.array([],dtype=np.float64) #the history of path length
min_D = 10000000 #make sure that D is smaller after each iteration

3.2 实现从txt文件输入Beizer曲线的控制点坐标

关于Bezier曲线内容,请参考https://blog.csdn.net/iwanderu/article/details/103604863。

pointsNumber_str = ""
closedOrOpen_str = ""
point_str = ""
line_zero = ""
filepath = ""

def inputFromText():
  global filepath
  filepath = os.path.abspath('.') + "/" + raw_input("please input the file name(eg:data.txt):")

  with open (filepath) as data:
    line_zero = data.readline()
    line_zero_list = list(line_zero.split())
    global m
    m = np.array(line_zero_list, dtype=np.uint8)

    for i in range(m[0]): 
      pointsNumber_str = data.readline()
      pointsNumber_list = list(pointsNumber_str.split())
      temp1 = np.array(pointsNumber_list,dtype=np.uint8)
      global pointsNumber
      pointsNumber = np.append(pointsNumber,temp1) # the number of control points of the ith Bezier curve

      closedOrOpen_str = data.readline()
      closedOrOpen_list = list(closedOrOpen_str.split())
      temp2 = np.array(closedOrOpen_list,dtype=np.uint8)
      global closedOrOpen
      closedOrOpen = np.append(closedOrOpen,temp2) #0 means the ith curve is closed, 1 means it is open
      
      global point
      for j in range(temp1[0]):
      #u += 1
        point_str = data.readline()
        point_list = list(point_str.split())
        temp3 = np.array(point_list,dtype=np.float64)
        point = np.append(point,temp3) # control points for the ith Bezier Curve

    global pointA
    pointA_str = data.readline()
    pointA_list = list(pointA_str.split())
    temp4 = np.array(pointA_list,dtype=np.float64)
    pointA = np.append(pointA,temp4) # point A 3*1
    global pointB
    pointB_str = data.readline()
    pointB_list = list(pointB_str.split())
    temp5 = np.array(pointB_list,dtype=np.float64)
    pointB = np.append(pointB,temp5) # point B 3*1 
    
    global bezierLine
    bezierLine = np.append(bezierLine,pointA)
    #bezierLine = bezierLine.reshape((-1,3))
  
  point = np.reshape(point, (-1,3))

3.3 获取Bezier曲线,并实现绘图,和计算Jacobian

首先是函数定义,下面是这个函数各个传入参数的意义。

def getBezierKurve(closedOrOpen,pointsNumber,point,point_index,u_i,jacobian_flag,drawbezier_flag,iter_flag,jacobiancontrol_flag):
#closedOrOpen: current kurve is closed or open bezier kurve
#pointsNumber: the number of control points of current kurve
#point: the xyz coordinates of control points of total kurve
#point_index: help find which control points belong to current kurve
#u_i: the current u for the current iteration
#jacobian_flag: help decide witch part of Jacobian is non-zero

#because this function conbine too many meanings(not good) so we need flags to control which one is on:
#drawbezier_flag: if true, it will draw bezier kurves
#iter_flag: if true, it will draw the current trace points and lines
#jacobiancontrol_flag: if true, it will calculate the partial derivative

第一步,处理从txt传入的数据,获得Bezier曲线

  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)])
  #print("C",C) 

第二步,绘制Bezier曲线

  if (drawbezier_flag==1):
    global f
    #f = np.array([],dtype=np.float64)
    #the following steps is to draw the kurve,not so important
  #  print("point_show\n",point_show)
  #  print("n+1",n+1)
    u = 0
    for j in range(1000): 
      fx = 0
      fy = 0  #do not forget return 0!!!!!!!!!!!!
      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) 
      u += 0.001
      f = np.append(f,array_list)

    f = f.reshape((-1,3))
    #ax.plot(f_show[:,0],f_show[:,1],f_show[:,2],'b.',markersize=1,label='open bazier curve')
    #ax.plot(point_show[:,0],point_show[:,1],point_show[:,2],'-',markersize=1, label='control line')
    #ax.plot(point_show[:,0],point_show[:,1],point_show[:,2],'r.',markersize=8, label='control points')

第三步,绘制迭代过程中某一步的效果曲线

  if (drawbezier_flag==1):
    global f
    #f = np.array([],dtype=np.float64)
    #the following steps is to draw the kurve,not so important
  #  print("point_show\n",point_show)
  #  print("n+1",n+1)
    u = 0
    for j in range(1000): 
      fx = 0
      fy = 0  #do not forget return 0!!!!!!!!!!!!
      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) 
      u += 0.001
      f = np.append(f,array_list)

    f = f.reshape((-1,3))
    #ax.plot(f_show[:,0],f_show[:,1],f_show[:,2],'b.',markersize=1,label='open bazier curve')
    #ax.plot(point_show[:,0],point_show[:,1],point_show[:,2],'-',markersize=1, label='control line')
    #ax.plot(point_show[:,0],point_show[:,1],point_show[:,2],'r.',markersize=8, label='control points')

第四步,计算当前时刻Bezier曲线对参数u的导数。

  if (jacobiancontrol_flag==1):
  #the following step is to get the current line for current iteration
    fx = 0
    fy = 0  #do not forget return 0!!!!!!!!!!!
    fz = 0
    for i in range(n+1):
      fx += C[i] * u_i**i * (1-u_i)**(n-i) * point_show[i][0]
      fy += C[i] * u_i**i * (1-u_i)**(n-i) * point_show[i][1]
      fz += C[i] * u_i**i * (1-u_i)**(n-i) * point_show[i][2]
    #u_i is the current u for current line for current iteration  
    _list = []
    _list.append(fx)
    _list.append(fy) 
    _list.append(fz)
    _array_list = np.array([_list],dtype=np.float64)
    global bezierLine #for current u, find the corresponding points on each line
    bezierLine = np.append(bezierLine,_array_list)
    #bezierLine = bezierLine.reshape((-1,3))
    #print("bezierLine")
    #print(bezierLine)

    # calculate the partial derivative
    global partial_derivative
    #partial_derivative = np.zeros(3*m[0]) 
    partial_derivative = np.zeros(3)
    dfx = 0
    dfy = 0  #do not forget return 0!!!!!!!!!!!
    dfz = 0
    for i in range(n+1):
      if (i==0):
        dfx -= C[i] * (n-i)*(1-u_i)**(n-i-1) * point_show[i][0]
        dfy -= C[i] * (n-i)*(1-u_i)**(n-i-1) * point_show[i][1]
        dfz -= C[i] * (n-i)*(1-u_i)**(n-i-1) * point_show[i][2] 
      elif (n==i):
        dfx += C[i] * i*u_i**(i-1) * point_show[i][0]
        dfy += C[i] * i*u_i**(i-1) * point_show[i][1]
        dfz += C[i] * i*u_i**(i-1) * point_show[i][2]
      else: 
        dfx += C[i] * (i*u_i**(i-1)*(1-u_i)**(n-i)-u_i**i*(n-i)*(1-u_i)**(n-i-1)) * point_show[i][0]
        dfy += C[i] * (i*u_i**(i-1)*(1-u_i)**(n-i)-u_i**i*(n-i)*(1-u_i)**(n-i-1)) * point_show[i][1]
        dfz += C[i] * (i*u_i**(i-1)*(1-u_i)**(n-i)-u_i**i*(n-i)*(1-u_i)**(n-i-1)) * point_show[i][2]
    #partial_derivative[jacobian_flag*3] = dfx
    #partial_derivative[jacobian_flag*3+1] = dfy
    #partial_derivative[jacobian_flag*3+2] = dfz
    partial_derivative[0] = dfx
    partial_derivative[1] = dfy
    partial_derivative[2] = dfz
    #print("partial_derivative")
    #print(partial_derivative)

3.4 最速下降优化
损失函数是整个路径的长度,也就是每一段线段两端端点坐标平方和开根号。其实为了化简运算,可以不开这个根号。

def steepestAscendmethod(ITERATION_TIME):
  D = 0
  unit_step = 0.0005 #learning rate for each iteration
  kurve_jacobian = np.array([],dtype=np.float64) #partial derivative of line df/du
  jacobian_trace = np.zeros((m[0],1)) #jacobian of trace length = [dD/du1 dD/du2 ...  dD/dun]
  #global ax

  fig = plt.figure()
  ax = fig.gca(projection='3d')

  for i in range(ITERATION_TIME):
    point_index = 0
    #key = raw_input("should we continue? yes/no:")
    if (0):#key =="no"):
      #print("OK, you are right.\n")
      #break
      pass
    elif (1):#key =="yes"):
      #print("let's continue:\n")
      point_index = 0

      for i in range(m[0]):
        getBezierKurve(closedOrOpen[i],pointsNumber[i],point,point_index,0,0,1,0,0) #draw curves only
        point_index += pointsNumber[i]

      point_index = 0
      for j in range(m[0]):
        #print(j)
        #print("*******************************")
        getBezierKurve(closedOrOpen[j],pointsNumber[j],point,point_index,u_i[j],0,0,0,1)
        point_index += pointsNumber[j]

        kurve_jacobian = np.append(kurve_jacobian,partial_derivative) # do not forget reshape

      kurve_jacobian = kurve_jacobian.reshape((-1,3))

      global bezierLine
      bezierLine = np.append(bezierLine,pointB)
      bezierLine = bezierLine.reshape((-1,3))
      ax.plot(bezierLine[:,0],bezierLine[:,1],bezierLine[:,2],'-',markersize=1, label='trace')
      ax.plot(f[:,0],f[:,1],f[:,2],'r.',markersize=1)#,label='open bazier curve')
      ax.set_xlabel('X')
      ax.set_ylabel('Y')
      ax.set_zlabel('Z')
      plt.show()

      for i in range(m[0]):
        jacobian_trace[i] = 2*((bezierLine[i+1][0]-bezierLine[i+2][0])-(bezierLine[i][0]-bezierLine[i+1][0]))*kurve_jacobian[i][0] + 2*((bezierLine[i+1][1]-bezierLine[i+2][1])-(bezierLine[i][1]-bezierLine[i+1][1]))*kurve_jacobian[i][1] + 2*((bezierLine[i+1][2]-bezierLine[i+2][2])-(bezierLine[i][2]-bezierLine[i+1][2]))*kurve_jacobian[i][2]
      #print(jacobian_trace)
      
      global D
      D_item = 0
      for i in range(m[0]):
        D_item += ((bezierLine[i+1][0]-bezierLine[i+2][0])-(bezierLine[i][0]-bezierLine[i+1][0]))**2 + ((bezierLine[i+1][1]-bezierLine[i+2][1])-(bezierLine[i][1]-bezierLine[i+1][1]))**2 + ((bezierLine[i+1][2]-bezierLine[i+2][2])-(bezierLine[i][2]-bezierLine[i+1][2]))**2
      D = np.append(D,D_item)
      print("the length of trace")
      #print(D_item)      

      if (D[D.shape[0]-1] <= min_D):      
        for i in range(m[0]):
          step = jacobian_trace[i] * unit_step
          u_i[i] -= step
          while (u_i[i] > 1 or u_i[i] < 0): #make sure that 0<u<1如果当前值超过了u的定义范围,那么下降值再乘以一个非常小的量unit_step
            u_i[i] += step
            step = step * unit_step
            u_i[i] -= step   

      global min_D
      if (min_D > D[D.shape[0]-1]):
        min_D = D[D.shape[0]-1]
      else:
        D[D.shape[0]-1] = min_D
      print(D[D.shape[0]-1])

      kurve_jacobian = np.array([],dtype=np.float64) #return to init station
      bezierLine = np.array([],dtype=np.float64)
      bezierLine = np.append(bezierLine,pointA)
    else:
      print("Error: unknown error")

3.5 main()

inputFromText()
#fig = plt.figure()
#ax = fig.gca(projection='3d')

u_i = np.random.rand(m[0],1)
for i in range(10):
  key = raw_input("should we continue? yes/no:")
  if (key =="no"):
    print("OK, you are right.\n")
    break
  elif (key =="yes"):
    print("let's continue:\n")
    for j in range(10):
      #if (D.shape[0]==0 or D[D.shape[0]-1]<=min_D): 
      steepestAscendmethod(1)

4.实验结果

实际上,初始值对实验结果的影响很大,不好的初始值,可能会让算法陷入局部最小值。所以说,如果使用梯度下降法,被优化函数是凸函数是十分重要的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一张图片各个点是随机生成对,随着迭代的进行,各个点的位置在不断优化,但是仍然发现,收敛时并不是最优位置,因此损失函数不是一个凸函数。下图是损失函数在各个迭代后的数值,在不断减小。
在这里插入图片描述

所有代码的链接如下:https://github.com/iwander-all/CAD.git

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

猜你喜欢

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