B 样条基函数

一、B 样条基函数的定义和性质

U = { u 0 , u 1 , ⋯   , u m } U=\{u_0,u_1,\cdots,u_m\} U={u0,u1,,um} 是一个单调不减的实数序列,即 u i ≤ u i + 1 , i = 0 , 1 , ⋯   , m − 1 u_i\leq u_{i+1},i=0,1,\cdots,m-1 uiui+1,i=0,1,,m1。其中, u i u_i ui 称为节点, U U U 称为节点矢量,用 N i , p ( u ) N_{i,p}(u) Ni,p(u) 表示第 i i i p p p 次( p + 1 p+1 p+1 阶)B 样条基函数,其定义为(也称为Cox-deBoor 公式
N i , 0 ( u ) = { 1 , u i ≤ u < u i + 1 , 0 , e l s e N i , p ( u ) = u − u i u i + p − u i N i , p − 1 ( u ) + u i + p + 1 − u u i + p + 1 − u i + 1 N i + 1 , p − 1 ( u ) (1) \begin{aligned} N_{i,0}(u)&=\begin{cases}1,\quad &u_i\leq u< u_{i+1},\\ 0,&else \end{cases}\\[3ex] N_{i,p}(u)&=\frac{u-u_i}{u_{i+p}-u_i}N_{i,p-1}(u)+\frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\tag{1} \end{aligned} Ni,0(u)Ni,p(u)={1,0,uiu<ui+1,else=ui+puiuuiNi,p1(u)+ui+p+1ui+1ui+p+1uNi+1,p1(u)(1)

由此可知:

  1. N i , 0 ( u ) N_{i,0}(u) Ni,0(u) 是一个阶梯函数,它在半开区间 u ∈ [ u i , u i + 1 ) u\in [u_i,u_{i+1}) u[ui,ui+1) 外都为零;
  2. p > 0 p>0 p>0 时, N i , p ( u ) N_{i,p}(u) Ni,p(u) 是两个 p − 1 p-1 p1 次基函数的线性组合;
  3. 计算一组基函数时需要事先指定节点矢量 U U U 和次数 p p p
  4. (1)式中可能出现 0 / 0 0/0 0/0,我们规定 0 / 0 = 0 0/0=0 0/0=0
  5. 半开区间 u ∈ [ u i , u i + 1 ) u\in [u_i,u_{i+1}) u[ui,ui+1) 称为第 i i i 个节点区间,它的长度可以为零,因为相邻节点可以是相同的。

例1. 令 U = { u 0 = 0 , u 1 = 0 , u 2 = 0 , u 3 = 1 , u 4 = 1 , u 5 = 1 } , p = 2 U=\{u_0=0,u_1=0,u_2=0,u_3=1,u_4=1,u_5=1\},p=2 U={u0=0,u1=0,u2=0,u3=1,u4=1,u5=1},p=2,我们分别计算 0 次、1 次和 2 次的 B 样条基函数。
N 0 , 0 = N 1 , 0 = 0 , − ∞ < u < ∞ N 2 , 0 = { 1 , 0 ≤ u < 1 0 , 其他 N 3 , 0 = N 4 , 0 = 0 , − ∞ < u < ∞ N 0 , 1 = u − 0 0 − 0 N 0 , 0 + 0 − u 0 − 0 N 1 , 0 = 0 , − ∞ < u < ∞ N 1 , 1 = u − 0 0 − 0 N 1 , 0 + 1 − u 1 − 0 N 2 , 0 = { 1 − u , 0 ≤ u < 1 0 , 其他 N 2 , 1 = u − 0 1 − 0 N 2 , 0 + 1 − u 1 − 1 N 3 , 0 = { u , 0 ≤ u < 1 0 , 其他 N 3 , 1 = u − 1 1 − 1 N 3 , 0 + 1 − u 1 − 1 N 4 , 0 = 0 , − ∞ < u < ∞ N 0 , 2 = u − 0 0 − 0 N 0 , 1 + 1 − u 1 − 0 N 1 , 1 = { ( 1 − u ) 2 , 0 ≤ u < 1 0 , 其他 N 1 , 2 = u − 0 1 − 0 N 1 , 1 + 1 − u 1 − 0 N 2 , 1 = { 2 u ( 1 − u ) , 0 ≤ u < 1 0 , 其他 N 2 , 2 = u − 0 1 − 0 N 2 , 1 + 1 − u 1 − 1 N 3 , 1 = { u 2 , 0 ≤ u < 1 0 , 其他 \begin{aligned} &N_{0,0}=N_{1,0}=0,\quad -\infty<u<\infty\\[2ex] &N_{2,0}=\begin{cases}1,\quad &0\leq u<1\\[2ex] 0,&其他 \end{cases}\\[2ex] &N_{3,0}=N_{4,0}=0,\quad -\infty<u<\infty\\[2ex] &N_{0,1}=\frac{u-0}{0-0}N_{0,0}+\frac{0-u}{0-0}N_{1,0}=0,\quad -\infty<u<\infty\\[2ex] &N_{1,1}=\frac{u-0}{0-0}N_{1,0}+\frac{1-u}{1-0}N_{2,0}=\begin{cases}1-u,\quad &0\leq u<1\\[2ex]0,&其他\end{cases}\\[2ex] &N_{2,1}=\frac{u-0}{1-0}N_{2,0}+\frac{1-u}{1-1}N_{3,0}=\begin{cases}u,\quad &0\leq u<1\\[2ex]0,&其他\end{cases}\\[2ex] &N_{3,1}=\frac{u-1}{1-1}N_{3,0}+\frac{1-u}{1-1}N_{4,0}=0,\quad -\infty<u<\infty\\[2ex] &N_{0,2}=\frac{u-0}{0-0}N_{0,1}+\frac{1-u}{1-0}N_{1,1}=\begin{cases}(1-u)^2,\quad &0\leq u<1\\[2ex]0,&其他\end{cases}\\[2ex] &N_{1,2}=\frac{u-0}{1-0}N_{1,1}+\frac{1-u}{1-0}N_{2,1}=\begin{cases}2u(1-u),\quad &0\leq u<1\\[2ex]0,&其他\end{cases}\\[2ex] &N_{2,2}=\frac{u-0}{1-0}N_{2,1}+\frac{1-u}{1-1}N_{3,1}=\begin{cases}u^2,\quad &0\leq u<1\\[2ex]0,&其他\end{cases}\\[2ex] \end{aligned} N0,0=N1,0=0,<u<N2,0= 1,0,0u<1其他N3,0=N4,0=0,<u<N0,1=00u0N0,0+000uN1,0=0,<u<N1,1=00u0N1,0+101uN2,0= 1u,0,0u<1其他N2,1=10u0N2,0+111uN3,0= u,0,0u<1其他N3,1=11u1N3,0+111uN4,0=0,<u<N0,2=00u0N0,1+101uN1,1= (1u)2,0,0u<1其他N1,2=10u0N1,1+101uN2,1= 2u(1u),0,0u<1其他N2,2=10u0N2,1+111uN3,1= u2,0,0u<1其他

二、B 样条基函数的导数

基函数的求导公式为
N i , p ′ = p u i + p − u i N i , p − 1 ( u ) + p u i + p + 1 − u i + 1 N i + 1 , p − 1 ( u ) (2) N^\prime_{i,p}=\frac{p}{u_{i+p}-u_i}N_{i,p-1}(u)+\frac{p}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}(u)\tag{2} Ni,p=ui+puipNi,p1(u)+ui+p+1ui+1pNi+1,p1(u)(2)

一般求导公式为
N i , p ( k ) = p ( N i , p − 1 ( k − 1 ) u i + p − u i − N i + 1 , p − 1 ( k − 1 ) u i + p + 1 − u i + 1 ) (3) N^{(k)}_{i,p}=p\Big(\frac{N_{i,p-1}^{(k-1)}}{u_{i+p}-u_i}-\frac{N_{i+1,p-1}^{(k-1)}}{u_{i+p+1}-u_{i+1}}\Big)\tag{3} Ni,p(k)=p(ui+puiNi,p1(k1)ui+p+1ui+1Ni+1,p1(k1))(3)

(4)式是(2)式的另一个推广,它根据基函数 N i , p − k , ⋯   , N i + k , p − k N_{i,p-k},\cdots,N_{i+k,p-k} Ni,pk,,Ni+k,pk 来计算 N i , p ( u ) N_{i,p}(u) Ni,p(u) k k k 阶导数:
N i , p ( k ) = p ! ( p − k ) ! ∑ j = 0 k a k , j N i + j , p − k (4) N^{(k)}_{i,p}=\frac{p!}{(p-k)!}\sum_{j=0}^ka_{k,j}N_{i+j,p-k}\tag{4} Ni,p(k)=(pk)!p!j=0kak,jNi+j,pk(4)

其中,
a 0 , 0 = 1 a k , 0 = a k − 1 , 0 u i + p − k + 1 − u i a k , j = a k − 1 , j − a k − 1 , j − 1 u i + p + j − k + 1 − u i + j , j = 1 , 2 , ⋯   , k − 1 a k , k = − a k − 1 , k − 1 u i + p + 1 − u i + k \begin{aligned} &a_{0,0}=1\\[2ex] &a_{k,0}=\frac{a_{k-1,0}}{u_{i+p-k+1}-u_i}\\[2ex] &a_{k,j}=\frac{a_{k-1,j}-a_{k-1,j-1}}{u_{i+p+j-k+1}-u_{i+j}},\quad j=1,2,\cdots,k-1\\[2ex] &a_{k,k}=\frac{-a_{k-1,k-1}}{u_{i+p+1}-u_{i+k}} \end{aligned} a0,0=1ak,0=ui+pk+1uiak1,0ak,j=ui+p+jk+1ui+jak1,jak1,j1,j=1,2,,k1ak,k=ui+p+1ui+kak1,k1

这里给出另一个计算 B 样条基函数各阶导数的公式
N i , p ( k ) = p p − k ( u − u i u i + p − u i N i , p − 1 ( k ) − u i + p + 1 − u u i + p + 1 − u i + 1 N i + 1 , p − 1 ( k ) ) , k = 0 , 1 , ⋯   , p − 1 (5) N_{i,p}^{(k)}=\frac{p}{p-k}\Big(\frac{u-u_i}{u_{i+p}-u_i}N_{i,p-1}^{(k)}-\frac{u_{i+p+1}-u}{u_{i+p+1}-u_{i+1}}N_{i+1,p-1}^{(k)}\Big),\quad k=0,1,\cdots,p-1\tag{5} Ni,p(k)=pkp(ui+puiuuiNi,p1(k)ui+p+1ui+1ui+p+1uNi+1,p1(k)),k=0,1,,p1(5)

一旦次数已给定,则节点矢量完全决定了基函数 N i , p ( u ) N_{i,p}(u) Ni,p(u)。节点矢量的类型有几种,本文中我们只考虑非周期节点矢量,它们具有以下形式:
U = { a , ⋯   , a ⏟ p + 1 , u p + 1 , ⋯   , u m − p − 1 , b , ⋯   , b ⏟ p + 1 } (6) U=\{\underbrace{a,\cdots,a}_{p+1},u_{p+1},\cdots,u_{m-p-1},\underbrace{b,\cdots,b}_{p+1}\}\tag{6} U={p+1 a,,a,up+1,,ump1,p+1 b,,b}(6)

即第一个和最后一个节点具有复杂度 p + 1 p+1 p+1

三、B 样条基函数的计算

U = { u 0 , u 1 , ⋯   , u m } U=\{u_0,u_1,\cdots,u_m\} U={u0,u1,,um} 为形如(6)式的节点矢量,假设我们感兴趣的是 p p p 次基函数,并假设 u u u 是固定的, u ∈ [ u i , u i + 1 ) u\in[u_i,u_{i+1}) u[ui,ui+1)。我们给出以下五个算法,分别用来计算:

  • 节点区间的下标 i i i
  • N i − p , p , ⋯   , N i , p N_{i-p,p},\cdots,N_{i,p} Nip,p,,Ni,p ( \big( ( 基于(1)式 ) \big) )
  • N i − p , p ( k ) ( u ) , ⋯   , N i , p ( k ) ( u ) , k = 0 , 1 , ⋯   , p N_{i-p,p}^{(k)}(u),\cdots,N_{i,p}^{(k)}(u),k=0,1,\cdots,p Nip,p(k)(u),,Ni,p(k)(u),k=0,1,,p;当 k > p k>p k>p 时导数为 0 ( \big( ( 这个算法基于(4)式 ) \big) )
  • 单个基函数 N j , p ( u ) , j = 0 , 1 , ⋯   , m − p − 1 N_{j,p}(u),j=0,1,\cdots,m-p-1 Nj,p(u),j=0,1,,mp1
  • 单个基函数的导数 N j , p ( k ) ( u ) , j = 0 , 1 , ⋯   , m − p − 1 , k = 0 , 1 , ⋯   , p N_{j,p}^{(k)}(u),j=0,1,\cdots,m-p-1,k=0,1,\cdots,p Nj,p(k)(u),j=0,1,,mp1,k=0,1,,p ( \big( ( 基于(3)式 ) \big) )

基函数和其导数计算的第一步就是确定 u u u 所属的节点区间,这可以通过对节点矢量使用线性搜索或二分法搜索得到。这里我们利用二分搜索法。由于我们使用的区间形式为 u ∈ [ u i , u i + 1 ) u\in[u_i,u_{i+1}) u[ui,ui+1),在计算基函数时需要考虑的一个情况是 u = u m u=u_m u=um 的情形,这时,我们直接将节点区间的下标设置为 m − p − 1 m-p-1 mp1,因此,在这种情况下 u ∈ ( u m − p − 1 , u m − p ] u\in(u_{m-p-1},u_{m-p}] u(ump1,ump]。下面的 FindSpan 算法返回 u u u 所在的节点区间的下标 i i i

def FindSpan(p, u, U):
    """
    确定参数 u 所在的节点区间的下标
    :param p: 基函数次数
    :param u: 固定点
    :param U: 节点矢量
    :return: 节点区间的下标
    """
    start, end = 0, len(U) - 1
    if u == U[-1]:
        return len(U) - p - 2
    if u == U[0]:
        return p
    while end != start + 1 and end != start:
        mid_index = (start + end) // 2
        if U[mid_index] == u:
            return mid_index
        if u > U[mid_index]:
            start = mid_index
        else:
            end = mid_index
    return start

现在我们考虑第二个算法,假设 u u u 在第 i i i 个节点区间内,计算所有非零 B 样条基函数。
例2. 设 p = 2 , U = { 0 , 0 , 0 , 1 , 2 , 3 , 4 , 4 , 5 , 5 , 5 } , u = 5 2 p=2,U=\{0,0,0,1,2,3,4,4,5,5,5\},u=\dfrac{5}{2} p=2,U={0,0,0,1,2,3,4,4,5,5,5},u=25。因为 u ∈ [ u 4 , u 5 ) u\in [u_4,u_5) u[u4,u5),因此 i = 4 i=4 i=4,所以我们需要计算
N 2 , 2 ( 5 2 ) N 3 , 1 ( 5 2 ) N 4 , 0 ( 5 2 ) N 3 , 2 ( 5 2 ) N 4 , 1 ( 5 2 ) N 4 , 2 ( 5 2 ) \quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad N_{2,2}\Big(\dfrac{5}{2}\Big)\\[2ex] N_{3,1}\Big(\dfrac{5}{2}\Big)\\[2ex] N_{4,0}\Big(\dfrac{5}{2}\Big)\quad\quad\quad\quad\quad\quad\quad\quad\quad N_{3,2}\Big(\dfrac{5}{2}\Big)\\[2ex] N_{4,1}\Big(\dfrac{5}{2}\Big)\\[2ex] \quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad N_{4,2}\Big(\dfrac{5}{2}\Big) N2,2(25)N3,1(25)N4,0(25)N3,2(25)N4,1(25)N4,2(25)

u = 5 2 u=\dfrac{5}{2} u=25 代入(1)式得到
N 4 , 0 ( 5 2 ) = 1 N 3 , 1 ( 5 2 ) = 1 2 N 4 , 1 ( 5 2 ) = 1 2 N 2 , 2 ( 5 2 ) = 1 8 N 3 , 2 ( 5 2 ) = 6 8 N 4 , 2 ( 5 2 ) = 1 8 \begin{aligned} &N_{4,0}\Big(\dfrac{5}{2}\Big)=1\\[3ex] &N_{3,1}\Big(\dfrac{5}{2}\Big)=\dfrac{1}{2}\quad N_{4,1}\Big(\dfrac{5}{2}\Big)=\dfrac{1}{2}\\[3ex] &N_{2,2}\Big(\dfrac{5}{2}\Big)=\dfrac{1}{8}\quad N_{3,2}\Big(\dfrac{5}{2}\Big)=\dfrac{6}{8}\quad N_{4,2}\Big(\dfrac{5}{2}\Big)=\dfrac{1}{8} \end{aligned} N4,0(25)=1N3,1(25)=21N4,1(25)=21N2,2(25)=81N3,2(25)=86N4,2(25)=81

在实际进行上述计算过程中,很容易发现计算时存在大量冗余的计算,从而设计 BasisFuns() 算法来计算所有非零 B 样条基函数并把它们存储在数组 N 中。

def BasisFuns(i, u, p, U):
    """
    计算所有非零 B 样条基函数的值
    :param i: 所在的节点区间
    :param u: 固定值
    :param p: 基函数次数
    :param U: 节点矢量
    :return: 基函数值数组
    """
    N = np.zeros(p+1)
    N[0] = 1.0
    left = np.zeros(p+1)
    right = np.zeros(p+1)
    for j in range(1, p+1):
        left[j] = u - U[i+1-j]
        right[j] = U[i+j] - u
        saved = 0.0
        for r in range(j):
            temp = N[r] / (right[r+1] + left[j-r])
            N[r] = saved + right[r+1] * temp
            saved = left[j-r] * temp
        N[j] = saved
    return N

现在考虑第三个算法,用来计算所有的基函数及其导数 N r , p ( k ) ( u ) , r = i − p , ⋯   , i , k = 0 , 1 , ⋯   , n N_{r,p}^{(k)}(u),r=i-p,\cdots,i,k=0,1,\cdots,n Nr,p(k)(u),r=ip,,i,k=0,1,,n

def DersBasisFuns(i, u, p, n, U):
    """
    计算非零 B 样条基函数及其导数
    :param i: 所在的节点区间
    :param u: 固定值
    :param p: 基函数次数
    :param n: n 阶导数
    :param U: 节点矢量
    :return: ders
    """

    "第一部分是对算法 BasisFuns() 进行修改,将函数值和节点差存到数组中"
    ndu = np.zeros((p+1, p+1))
    ders = np.zeros((n+1, p+1))
    a = np.zeros((2, p+1))
    ndu[0, 0] = 1.0
    left = np.zeros(p + 1)
    right = np.zeros(p + 1)
    for j in range(1, p+1):
        left[j] = u - U[i+1-j]
        right[j] = U[i+j] - u
        saved = 0.0
        for r in range(j):
            "下三角"
            ndu[j, r] = right[r+1] + left[j-r]
            temp = ndu[r, j-1] / ndu[j, r]
            "上三角"
            ndu[r, j] = saved + right[r+1] * temp
            saved = left[j-r] * temp
        ndu[j, j] = saved

    for j in range(p+1):
        "载入基函数的值"
        ders[0, j] = ndu[j, p]
    "下面计算导数"
    for r in range(p+1):
        s1 = 0
        s2 = 1
        a[0, 0] = 1.0
        "循环计算 k 阶导数,k=1,2,…,n"
        for k in range(1, n+1):
            d = 0.0
            rk = r - k
            pk = p - k
            if r >= k:
                a[s2, 0] = a[s1, 0] / ndu[pk+1, rk]
                d = a[s2, 0] * ndu[rk, pk]
            if rk >= -1:
                j1 = 1
            else:
                j1 = -rk
            if r-1 <= pk:
                j2 = k - 1
            else:
                j2 = p - r
            for j in range(j1, j2 + 1):
                a[s2, j] = (a[s1, j] - a[s1, j-1]) / ndu[pk+1, rk+j]
                d += a[s2, j] * ndu[rk+j, pk]
            if r <= pk:
                a[s2, k] = - a[s1, k-1] / ndu[pk+1, r]
                d += a[s2, k] * ndu[r, pk]
            ders[k, r] = d
            j = s1
            s1 = s2
            s2 = j
    "对结果乘以正确的因子"
    r = p
    for k in range(1, n+1):
        for j in range(p+1):
            ders[k, j] *= r
            r *= (p - k)
    return ders
  • 20
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值