Julia实现经典的插值算法

插值法

设函数 y = f ( x ) y=f(x) y=f(x)在区间 [ a , b ] [a,b] [a,b]上有定义,且在点 a ⩽ x 0 ⩽ x 1 … ⩽ x n ⩽ b a\leqslant x_0\leqslant x_1\ldots\leqslant x_n\leqslant b ax0x1xnb上的值 y 0 , y 1 … y n y_0, y_1 \ldots y_n y0,y1yn之间存在一个函数 P ( x ) P(x) P(x),使得

P ( x i ) = y i ( i = 0 , 1 , … , n ) P(x_i)=y_i (i=0,1,\ldots, n) P(xi)=yi(i=0,1,,n)

成立,则称 P ( x ) P(x) P(x) f ( x ) f(x) f(x)的插值函数,在 x 0 , x 1 , … , x n x_0, x_1,\ldots,x_n x0,x1,,xn称为插值节点,包含节点的区间称为插值区间。如果 P ( x ) P(x) P(x)是次数不超过 n n n的代数多项式,则称之为插值多项式。

Lagrange插值

数学原理

若我们有 n + 1 n+1 n+1组一一对应的 ( x , y ) (x,y) (x,y)点对,假设存在一多项式 L n ( x ) L_n(x) Ln(x),使之满足 L n ( x j ) = y j , j = 0 , 1 , … , n L_n(x_j)=y_j,j=0,1,\ldots,n Ln(xj)=yj,j=0,1,,n,即

{ a 0 + a 1 x 0 + … + a n x 0 n = y 0 a 0 + a 1 x 1 + … + a n x 1 n = y 1 ⋮ a 0 + a 1 x n + … + a n x n n = y n \left\{\begin{aligned} a_0+a_1x_0+\ldots+a_nx_0^n&=&y_0\\ a_0+a_1x_1+\ldots+a_nx_1^n&=&y_1\\ &\vdots&\\ a_0+a_1x_n+\ldots+a_nx_n^n&=&y_n\\ \end{aligned}\right. a0+a1x0++anx0na0+a1x1++anx1na0+a1xn++anxnn===y0y1yn

L n L_n Ln即为该组数据的插值多项式。对于任意 j = 0 , 1 , … , n j=0,1,\ldots,n j=0,1,,n,有 L n ( x j ) = y j L_n(x_j)=y_j Ln(xj)=yj,则可令

L n ( x ) = ∑ k = 0 y k l k ( x ) L_n(x)=\displaystyle\sum_{k=0}y_kl_k(x) Ln(x)=k=0yklk(x)

其中,

l k ( x j ) = δ j k = { 1 , k = j 0 , k ≠ j ( j , k = 0 , 1 , … , n ) l_k(x_j)=\delta_{jk}=\left\{\begin{aligned} 1, k=j\\ 0, k\not=j \end{aligned}\right. (j,k=0,1,\ldots,n) lk(xj)=δjk={1,k=j0,k=j(j,k=0,1,,n)

则可满足 L n ( x j ) = ∑ k = 0 n y k l k ( x j ) = y j L_n(x_j)=\displaystyle\sum_{k=0}^ny_kl_k(x_j)=y_j Ln(xj)=k=0nyklk(xj)=yj,其中 l k l_k lk被称为基函数。根据其所满足的要求,易得基函数的表达式为

l k ( x ) = ( x − x 0 ) … ( x − x k − 1 ) ( x − x k + 1 ) … ( x − x n ) ( x k − x 0 ) … ( x k − x k − 1 ) ( x k − x k + 1 ) … ( x k − x n ) l_k(x)=\frac{(x-x_0)\ldots(x-x_{k-1})(x-x_{k+1})\ldots(x-x_n)}{(x_k-x_0)\ldots(x_k-x_{k-1})(x_k-x_{k+1})\ldots(x_k-x_n)} lk(x)=(xkx0)(xkxk1)(xkxk+1)(xkxn)(xx0)(xxk1)(xxk+1)(xxn)

自此,我们就得到了Lagrange插值多项式。该式简洁明了,其插值基函数规律明显,能使人过目不忘。若记

ω n + 1 ( x ) = ( x − x 0 ) ( x − x 1 ) … ( x − x n ) \omega_{n+1}(x)=(x-x_0)(x-x_1)\ldots(x-x_n) ωn+1(x)=(xx0)(xx1)(xxn)

ω n + 1 ′ ( x k ) = ( x k − x 0 ) … ( x k − x k − 1 ) ( x k − x k + 1 ) … ( x k − x n ) \omega'_{n+1}(x_k)=(x_k-x_0)\ldots(x_k-x_{k-1})(x_k-x_{k+1})\ldots(x_k-x_n) ωn+1(xk)=(xkx0)(xkxk1)(xkxk+1)(xkxn)

那么Lagrange插值公式可写为

L n ( x ) = ∑ k = 0 n y k ω n + 1 ( x ) ( x − x k ) ω n + 1 ′ ( x k ) L_n(x)=\displaystyle\sum_{k=0}^ny_k\frac{\omega_{n+1}(x)}{(x-x_k)\omega'_{n+1}(x_k)} Ln(x)=k=0nyk(xxk)ωn+1(xk)ωn+1(x)

特别地,对于两点而言,Lagrange插值退化为线性插值,即

L 2 ( x ) = y 1 x − x 2 x 1 − x 2 + y 2 x − x 1 x 2 − x 1 L_2(x)=y_1\frac{x-x_2}{x_1-x_2}+y_2\frac{x-x_1}{x_2-x_1} L2(x)=y1x1x2xx2+y2x2x1xx1

算法化

在Lagrange插值公式中, ω n + 1 ( x ) \omega_{n+1}(x) ωn+1(x)为多项式乘法。Julia中虽然对函数式编程有着良好的支持,但如果将函数本身作为输出,则函数实现细节将被隐藏,因此我们并不能得到真正的拟合参数。所以我们需要对多项式进行抽象,即将其简化为数组的形式,其表现形式为[a_0,a_1,...,a_n],代表 a 0 + a 1 x + … + a n x n a_0+a_1x+\ldots+a_nx^n a0+a1x++anxn

那么多项式的乘法可以通过进位的方式实现。对于常数项而言,并不会改变多项式的幂数;对于一阶项来说,乘以多项式数组,则将该数组向高位移一位。其实现方法为

# 多项式乘法,形式为an*x^n,建议a[1]为最低位常数项
function polyMulti(a,b)
    na = length(a); nb = length(b)
    c = zeros(na+nb-1)
    for i = 1:na
        c[i:i+nb-1] += a[i]*b
    end
    return c
end

在Lagrange插值公式中亦有多项式除法,其实现方法与乘法类似,只不过其运算顺序需要从高位开始,并且被除多项式的阶数要不小于除数多项式。
在此需要注意,和许多编程语言类似,Julia中的对象采用地址引用的形式,在传参之后如果直接改变数组内容,将穿透作用域,使得函数之外的数组也发生变化,因此需要对其进行深拷贝。

function polyDiv(a,b)
    a = copy(a)         #此为余数
    na = length(a); nb = length(b)
    nc = na-nb+1
    c = zeros(nc)
    for i = 0:nc-1
        c[nc-i] = a[end-i] ÷ b[end]
        a[nc-i:end-i] += -c[nc-i]*b[1:end]
    end
    return c,a
end

此外,考虑到Lagrange插值公式中包含连乘运算。其中,分子中的连乘为多项式连乘,分母中的多项式为值连乘,所以再建立连乘函数。

# 连乘函数,输入为数组,输出为数组中所有数的乘积
function sumMulti(x)
    if length(x) == 1
        return x
    else
        return x[1]*sumMulti(x[2:end])
    end
end

Lagrange插值公式中的多项式连乘形式单一,相比之下更像是多项式的两种表示形式的转换,所以再建立针对Lagrange插值公式的多项式转化函数

#(x-x1)(x-x2)...(x-xn)化成an*x^n形式,A[1]为最低位(常数项)
#输入为[x1,x2,...,xn]输出为[a1,a2,...,an]
function polySimplify(x)
    if length(x) == 1
        return [-x[1],1]
    else
        return polyMulti([-x[1],1],polySimplify(x[2:end]))
    end
end

至此,可以创建Lagrange插值函数。

# Language插值函数
# x y: 维度相同的列变量
# A: 多项式系数,A[1]为最低位
using LinearAlgebra         #建立单位矩阵需要使用线性代数包
function Lagrange(x,y)
    n = length(x)
    xMat = x .- x' + Matrix{Float64}(I,n,n)
    polyMat = polySimplify(x)
    A = zeros(n)
    for i = 1:n
        quot = polyDiv(polyMat,[-x[i],1])[1]
        A += y[i] * quot ./ sumMulti(xMat[i,:])
    end 
    return A
end

函数中的xMat为矩阵

[ 1 x 1 − x 0 … x n − x 0 x 0 − x 1 1 … x n − x 1 ⋮ x 0 − x n x 1 − x n … 0 ] \begin{bmatrix} 1 & x_1-x_0 & \ldots & x_n-x_0\\ x_0-x_1 & 1 & \ldots & x_n-x_1\\ & & \vdots & \\ x_0-x_n & x_1-x_n & \ldots & 0\\ \end{bmatrix} 1x0x1x0xnx1x01x1xnxnx0xnx10

polyMat为多项式 ω n + 1 ( x ) \omega_{n+1}(x) ωn+1(x)

在命令行中调用得

julia> include("interpolation.jl")
Lagrange (generic function with 1 method)

julia> x = [i for i = 1:9];

julia> y = x.^8 + x.^6 + x.^2 .+ 1;

julia> Lagrange(x,y)
9-element Array{Float64,1}:
  1.0
  4.470348358154297e-8
  0.9999999850988388
 -1.4901161193847656e-8
 -7.450580596923828e-9
  2.7939677238464355e-9
  1.0
  0.0
  1.0

结果表明,其8次项、6次项、2次项和0次项分别为1或者接近于1,其他项分别为0或者接近于0,可见拟合结果尚可。通过Plots包对原始数据和拟合结果进行比较

julia> using Plots
julia> y1 = y;
julia> y2 = [sum([a[i]*x[j]^(i-1) for i=1:9]) for j = 1:9];
julia> plot(x,[y1,y2],st=[:scatter :line])
julia> savefig("Lagrange.png")

在这里插入图片描述

可见拟合结果与原始数据几乎一致。

Newton插值

差商

对于一列 x 0 , x 1 , … , x n x_0,x_1,\ldots,x_n x0,x1,,xn,若与 y 0 , y 1 , … , y n y_0,y_1,\ldots,y_n y0,y1,,yn通过映射 f f f一一对应,则定义比值

f [ x 0 , x 1 ] = f ( x 1 ) − f ( x 0 ) x 1 − x 0 f[x_0,x_1]=\frac{f(x_1)-f(x_0)}{x_1-x_0} f[x0,x1]=x1x0f(x1)f(x0)

f ( x ) f(x) f(x)关于节点 x 0 , x 1 x_0,x_1 x0,x1的一阶差商。

类似地,

f [ x 0 , x 1 , x 2 ] = f [ x 0 , x 2 ] − f [ x 0 , x 1 ] x 2 − x 1 f[x_0,x_1,x_2]=\frac{f[x_0,x_2]-f[x_0,x_1]}{x_2-x_1} f[x0,x1,x2]=x2x1f[x0,x2]f[x0,x1]

为二阶差商,

f [ x 0 , x 1 , … , x k ] = f [ x 0 , x 1 , … , x k − 2 , x k ] − f [ x 0 , x 1 , … , x k − 1 ] x k − x k − 1 f[x_0,x_1,\ldots,x_k]=\frac{f[x_0,x_1,\ldots,x_{k-2},x_k]-f[x_0,x_1,\ldots,x_{k-1}]}{x_k-x_{k-1}} f[x0,x1,,xk]=xkxk1f[x0,x1,,xk2,xk]f[x0,x1,,xk1]

f ( x ) f(x) f(x)关于节点 x 0 , x 1 , … , x k x_0,x_1,\ldots,x_k x0,x1,,xk k k k阶差商。

k k k阶差商可表示为函数值 f ( x 0 ) , f ( x 1 ) , … , f ( x n ) f(x_0),f(x_1),\ldots,f(x_n) f(x0),f(x1),,f(xn)的线性组合,即

f [ x 0 , x 1 , … , x k ] = ∑ j = 0 k f ( x j ) ( x j − x 0 ) … ( x j − x j − 1 ) ( x j − x j + 1 ) … ( x j − x k ) f[x_0,x_1,\ldots,x_k]=\displaystyle\sum_{j=0}^k\frac{f(x_j)}{(x_j-x_0)\ldots(x_j-x_{j-1})(x_j-x_{j+1})\ldots(x_j-x_k)} f[x0,x1,,xk]=j=0k(xjx0)(xjxj1)(xjxj+1)(xjxk)f(xj)

f [ x 0 , x 1 , … , x k ] = f [ x 1 , x 2 , … , x k ] − f [ x 0 , x 1 , … , x k − 1 ] x k − x 0 f[x_0,x_1,\ldots,x_k]=\frac{f[x_1,x_2,\ldots,x_k]-f[x_0,x_1,\ldots,x_{k-1}]}{x_k-x_0} f[x0,x1,,xk]=xkx0f[x1,x2,,xk]f[x0,x1,,xk1]

则可通过递归实现两个数组的差商

# 差商算法,x,y为同等长度的数组
function diffQuotient(x,y)
    if length(x) == 2
        return (y[2]-y[1])/(x[2]-x[1])
    elseif length(x) == 1
        return y[1]
    else
        return (diffQuotient(x[2:end],y[2:end])
               -diffQuotient(x[1:end-1],y[1:end-1]))/(x[end]-x[1])
    end
end

Newton插值

根据差商定义,可得

f ( x ) = f ( x 0 ) + f [ x , x 0 ] ( x − x 0 ) f [ x , x 0 ] = f [ x 0 , x 1 ] + f [ x , x 0 , x 1 ] ( x − x 1 ) ⋮ f [ x , x 0 , … , x n − 1 ] = f [ x 0 , x 1 , … , x n ] + f [ x , x 0 , x 1 , … , x n ] ( x − x n ) \begin{aligned} f(x) &= f(x_0) + f[x,x_0](x-x_0)\\ f[x,x_0] &= f[x_0,x_1] + f[x,x_0,x_1](x-x_1)\\ &\vdots\\ f[x,x_0,\ldots,x_{n-1}] &= f[x_0,x_1,\ldots,x_n] + f[x,x_0,x_1,\ldots,x_n](x-x_n) \end{aligned} f(x)f[x,x0]f[x,x0,,xn1]=f(x0)+f[x,x0](xx0)=f[x0,x1]+f[x,x0,x1](xx1)=f[x0,x1,,xn]+f[x,x0,x1,,xn](xxn)

f ( x ) = f ( x 0 ) + f [ x 0 , x 1 ] ( x − x 0 ) + f [ x 0 , x 1 , x 2 ] ( x − x 0 ) ( x − x 1 ) + … + f [ x 0 , x 1 , … , x n ] ( x − x 0 ) ( x − x 1 ) … ( x − x n − 1 ) + f [ x 0 , x 1 , … , x n ] ω n + 1 ( x ) = N n ( x ) + R n ( x ) \begin{aligned} f(x) =& f(x_0) + f[x_0,x_1](x-x_0)+ f[x_0,x_1,x_2](x-x_0)(x-x_1)\\ &+\ldots+f[x_0,x_1,\ldots,x_n](x-x_0)(x-x_1)\ldots(x-x_{n-1})\\ &+f[x_0,x_1,\ldots,x_n]\omega_{n+1}(x)\\ =&N_n(x)+R_n(x) \end{aligned} f(x)==f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)++f[x0,x1,,xn](xx0)(xx1)(xxn1)+f[x0,x1,,xn]ωn+1(x)Nn(x)+Rn(x)

其中,

N n ( x ) = f ( x 0 ) + f [ x 0 , x 1 ] ( x − x 0 ) + f [ x 0 , x 1 , x 2 ] ( x − x 0 ) ( x − x 1 ) + … + f [ x 0 , x 1 , … , x n ] ( x − x 0 ) ( x − x 1 ) … ( x − x n − 1 ) R n ( x ) = f [ x 0 , x 1 , … , x n ] ω n + 1 ( x ) \begin{aligned} N_n(x) =& f(x_0) + f[x_0,x_1](x-x_0)+ f[x_0,x_1,x_2](x-x_0)(x-x_1)+\\ &\ldots+f[x_0,x_1,\ldots,x_n](x-x_0)(x-x_1)\ldots(x-x_{n-1})\\ R_n(x)=&f[x_0,x_1,\ldots,x_n]\omega_{n+1}(x) \end{aligned} Nn(x)=Rn(x)=f(x0)+f[x0,x1](xx0)+f[x0,x1,x2](xx0)(xx1)++f[x0,x1,,xn](xx0)(xx1)(xxn1)f[x0,x1,,xn]ωn+1(x)

N n ( x ) N_n(x) Nn(x)即为牛顿插值公式,且

N n + 1 ( x ) = N n ( x ) + f [ x 0 , x 1 , … , x n + 1 ] ω n + 1 ( x ) N_{n+1}(x) = N_{n}(x) + f[x_0,x_1,\ldots,x_{n+1}]\omega_{n+1}(x) Nn+1(x)=Nn(x)+f[x0,x1,,xn+1]ωn+1(x)

可快速写为Julia

# Newton插值函数
function Newton(x,y)
    n = length(x)
    A = zeros(n)
    A[1] = y[1]
    for i = 2:n
        p = DiffQuotient(x[1:i],y[1:i])*polySimplify(x[1:i-1])
        A[1:length(p)] += p
    end
    return A
end

当然,无论差商还是连乘运算,都会在运算的过程中产生大量的重复,上述代码中并未有效利用已经计算出的值,所以性能上并不占优。因此,我们对Newton插值进行更改

# Newton插值函数
function Newton(x,y)
    n = length(x)
    A = zeros(n)
    hisQuot = y[1]      #保存差商的值
    hisPoly = [1]
    A[1] = y[1]
    for i = 2:n
        hisQuot = diffQuotient([x[1],x[i]],[hisQuot,diffQuotient(x[2:i],y[2:i])])
        hisPoly = polyMulti(hisPoly,[-x[i-1],1])
        A[1:length(hisPoly)] += hisQuot*hisPoly
    end
    return A
end

调用并验证

julia> include("interpolation.jl");
julia> x = [i for i = 1:9];
julia> y = x.^8 + x.^6 + x.^2 .+ 1;

julia> Newton(x,y)
9-element Array{Float64,1}:
 1.0
 0.0
 1.0
 0.0
 0.0
 0.0
 1.0
 0.0
 1.0

Hermite插值

无论是Newton插值还是Lagrange插值,都只能在数值本身上满足插值函数与数据节点的重合,Hermite插值则要求其导数值相等。

设在节点 a ⩽ x 0 ⩽ x 1 ⩽ … ⩽ x n ⩽ b a\leqslant x_0\leqslant x_1 \leqslant\ldots\leqslant x_n\leqslant b ax0x1xnb上, y j = f ( x j ) , m j = f ′ ( x j ) , j = 0 , 1 , … , n y_j=f(x_j),m_j=f'(x_j),j=0,1,\ldots,n yj=f(xj),mj=f(xj),j=0,1,,n,则插值多项式的条件为

H ( x j ) = y j , H ′ ( x j ) = m j , j = 0 , 1 , … , n H(x_j)=y_j,H'(x_j)=m_j,j=0,1,\ldots,n H(xj)=yj,H(xj)=mj,j=0,1,,n

总共有 2 n + 2 2n+2 2n+2个等式,可唯一确定一个次数不大于 2 n + 1 2n+1 2n+1的多项式 H 2 n + 1 ( x ) H_{2n+1}(x) H2n+1(x),设其表达式可通过基函数 α j , β j \alpha_j,\beta_j αj,βj写成

H 2 n + 1 ( x ) = ∑ j = 0 n [ y j α j + m j β j ] H_{2n+1}(x)=\displaystyle\sum_{j=0}^n[y_j\alpha_j+m_j\beta_j] H2n+1(x)=j=0n[yjαj+mjβj]

α j , β j \alpha_j,\beta_j αj,βj需要满足

{ α j ( x k ) = δ j k α j ′ ( x k ) = 0 β j ( x k ) = 0 β j ′ ( x k ) = δ j k \left\{\begin{aligned} \alpha_j(x_k) &= \delta_{jk}\\ \alpha_j'(x_k)&= 0\\ \beta_j(x_k) &= 0\\ \beta_j'(x_k) & = \delta_{jk} \end{aligned}\right. αj(xk)αj(xk)βj(xk)βj(xk)=δjk=0=0=δjk

两个基函数的条件与Lagrange插值中的基函数相似,由于我们的目标函数为 2 n + 1 2n+1 2n+1次,所以假设

{ α j = ( a x + b ) l j 2 ( x ) β j = ( c x + d ) l j 2 ( x ) \left\{\begin{aligned} \alpha_j = (ax+b)l_j^2(x)\\ \beta_j = (cx+d)l_j^2(x) \end{aligned}\right. {αj=(ax+b)lj2(x)βj=(cx+d)lj2(x)

结合二者所满足的基函数条件,可得

{ α j ( x j ) = ( a x j + b ) l j 2 ( x j ) = 1 α j ′ ( x j ) = l j ( x j ) [ a l j ( x j ) + 2 ( a x j + b ) l j ′ ( x j ) ] = 0 β j ( x j ) = ( c x j + d ) l j 2 ( x j ) = 0 β j ′ ( x j ) = l j ( x j ) [ c l j ( x j ) + 2 ( c x j + d ) l j ′ ( x j ) ] = 1 \left\{\begin{aligned} \alpha_j(x_j)&=(ax_j+b)l_j^2(x_j)=1\\ \alpha_j'(x_j)&=l_j(x_j)[al_j(x_j)+2(ax_j+b)l_j'(x_j)]=0\\ \beta_j(x_j)&=(cx_j+d)l_j^2(x_j)=0\\ \beta_j'(x_j)&=l_j(x_j)[cl_j(x_j)+2(cx_j+d)l_j'(x_j)]=1 \end{aligned}\right. αj(xj)αj(xj)βj(xj)βj(xj)=(axj+b)lj2(xj)=1=lj(xj)[alj(xj)+2(axj+b)lj(xj)]=0=(cxj+d)lj2(xj)=0=lj(xj)[clj(xj)+2(cxj+d)lj(xj)]=1

解得

{ α j = [ 1 − 2 ( x − x j ) ∑ k = 0 , k ≠ j n 1 x j − x k ] l j 2 ( x ) β j = ( x − x j ) l j 2 ( x ) \left\{\begin{aligned} \alpha_j &= [1-2(x-x_j)\displaystyle\sum_{k=0,k\not=j}^n\frac{1}{x_j-x_k}]l_j^2(x)\\ \beta_j &= (x-x_j)l_j^2(x) \end{aligned}\right. αjβj=[12xxj)k=0,k=jnxjxk1]lj2(x)=(xxj)lj2(x)

s j = ∑ k = 0 , k ≠ j 1 x j − x k s_j=\displaystyle\sum_{k=0,k\not=j}\frac{1}{x_j-x_k} sj=k=0,k=jxjxk1,则Hermite插值公式可写为

H 2 n + 1 ( x ) = ∑ j = 0 n { [ y j + 2 s j y j x j − x j m j ] ⋅ l j 2 ( x ) + [ − 2 s j y j + m j ] ⋅ x l j 2 ( x ) } H_{2n+1}(x)=\displaystyle\sum_{j=0}^n\{ [y_j+2s_jy_jx_j-x_jm_j]\cdot l_j^2(x) +[-2s_jy_j+m_j]\cdot xl_j^2(x) \} H2n+1(x)=j=0n{[yj+2sjyjxjxjmj]lj2(x)+[2sjyj+mj]xlj2(x)}

其代码为

# Hermite插值函数
# x,y分别为自变量、因变量,m为导数
function Hermite(x,y,m)
    n = length(x)
    unitMat = Matrix{Float64}(I,n,n)
    xMat = x .- x' + unitMat
    xInverse = 1./xMat - unitMat
    A = zeros(n*2+1)
    for i = 1:n
        quot = polyDiv(polyMat,[-x[i],1])[1]
        lag = quot ./ sumMulti(xMat[i,:])  #Lagrange基函数
        lag = polyMulti(lag,lag)
        sumInverse = sum(xInverse[i,:])
        a0 = (1+2*x[i]*sumInverse)*y[i]-x[i]*m[i]
        a1 = -2*y[i]+m[i]
        α = 1-2
        A += polyMulti(lag,[a0,a1])
    end
    return A
end

验证

julia> include("interpolation.jl");
julia> x = [i for i = 1:9];
julia> y = x.^8 + x.^6 + x.^2 .+ 1;
julia> m = [rand() for i = 1:9] 
julia> a = Hermite(x,y,m);

julia> y1 = y
julia> y2 = [sum([a[i]*x[j]^(i-1) for i=1:18]) for j = 1:9]
julia> plot(x,[y1,y2],st=[:scatter :line])
julia> savefig("hermite.png")

在这里插入图片描述

当然,实际上由于我们引入了一组随即变量作为每一点的导数,我们所拟合出的多项式函数自然与输入的多项式不同

julia> f(x) = sum([a[i]*x^(i-1) for i=1:18])
julia> plot(f,xlims=(1,9))
julia> savefig("realfig.png")

在这里插入图片描述

样条插值

高阶多项式拟合误差

由于在Hermite多项式拟合的过程中,在采用多项式函数生成 y y y后,简单地通过随机值的方式生成了节点处的导数值,所以很难评价其拟合结果。

现我们通过多项式对函数 f ( x ) = 1 1 + x 2 f(x)=\frac{1}{1+x^2} f(x)=1+x21进行拟合

julia> x = [i for i = -5:5];
julia> f(x) = 1/(1+x^2);
julia> df(x) = -2*x/(1+x^2)^2       #f(x)的导函数
julia> y = f.(x);                   #由于x为向量,所以函数括号前加.
julia> m = df.(x);
julia> a = Hermite(x,y,m)           #Hermite插值
julia> fHermite(x) = sum([a[i]*x^(i-1) for i=1:22])
julia> plot([f,fHermite])           #绘制函数f和fHermite
julia> savefig("errFit.png")        #保存图片

在这里插入图片描述

如图可知,高阶多项式函数在对某些数据进行插值的过程中可能会在局部拟合完美,而在另外的部分具有较大误差,这是Runge发现的,所以被称为Runge现象。如果能够在不同的位置通过分段的形式对数据进行拟合,或许能够克服这一问题。

三弯矩法

若函数 S ( x ) S(x) S(x) [ a , b ] [a,b] [a,b]区间内二阶可导,且在每个小区间 [ x k , x k + 1 [x_k,x_{k+1} [xk,xk+1上是三次多项式,其中 a ⩽ x 0 ⩽ x 1 ⩽ … ⩽ x n ⩽ b a\leqslant x_0\leqslant x_1 \leqslant\ldots\leqslant x_n\leqslant b ax0x1xnb为给定节点,则称 S ( x ) S(x) S(x)是节点上的三次样条函数,若在节点 x j x_j xj上给定函数值 y j = f ( x j ) , j = 0 , 1 , … , n y_j=f(x_j),j=0,1,\ldots,n yj=f(xj),j=0,1,,n,且 S ( x j ) = y j S(x_j)=y_j S(xj)=yj,则称 S ( x ) S(x) S(x)为三次样条插值函数。

对于任意一个小区间来说,三次插值需要有四个待定系数,除了端点处所对应的函数值 S j ( x j − 1 ) = y j − 1 , S j ( x j ) = y j S_j(x_{j-1})=y_{j-1},S_j(x_j)=y_j Sj(xj1)=yj1,Sj(xj)=yj之外,还需要有两个等式共同构成边界条件。常见的边界条件有三种:

  1. 已知两端的一阶导数
  2. 已知两端的二阶导数
  3. 周期边界条件

现假设我们已知任意点的二阶导数,记 S ′ ′ ( x j ) = M j , h j = x j − x j − 1 , j = 0 , 1 , … , n S''(x_j)=M_j,h_j=x_j-x_{j-1},j=0,1,\ldots,n S(xj)=Mj,hj=xjxj1,j=0,1,,n,则 S ′ ′ ( x ) S''(x) S(x)在区间 [ x j − 1 , x j ] [x_{j-1},x_j] [xj1,xj]之间是 x x x的线性函数,且有 S j ′ ′ ( x j − 1 ) = M j − 1 , S j ′ ′ ( x j ) = M j S''_j(x_{j-1})=M_{j-1},S''_j(x_j)=M_j Sj(xj1)=Mj1,Sj(xj)=Mj,通过线性插值,可将其表示为

S j ′ ′ ( x ) = x j − x h j M j − 1 + x − x j − 1 h j M j S''_j(x)=\frac{x_j-x}{h_j}M_{j-1}+\frac{x-x_{j-1}}{h_j}M_j Sj(x)=hjxjxMj1+hjxxj1Mj

对其积分可得

S j ( x ) = ( x j − x ) 3 6 h j M j − 1 + ( x − x j − 1 ) 3 6 h j M j + C 1 x + C 2 S_j(x) = \frac{(x_j-x)^3}{6h_j}M_{j-1}+\frac{(x-x_{j-1})^3}{6h_j}M_j+C_1x+C_2 Sj(x)=6hj(xjx)3Mj1+6hj(xxj1)3Mj+C1x+C2

S j ( x j − 1 ) = y j − 1 , S j ( x j ) = y j S_j(x_{j-1})=y_{j-1},S_j(x_j)=y_j Sj(xj1)=yj1,Sj(xj)=yj代入,整理可得

S j ( x ) = ( x j − x ) 3 6 h j M j − 1 + ( x − x j − 1 ) 3 6 h j M j + ( y j − 1 − M j − 1 h j 2 6 ) x j − x h j + ( y j − M j h j 2 6 ) x − x j − 1 h j S_j(x) = \frac{(x_j-x)^3}{6h_j}M_{j-1}+\frac{(x-x_{j-1})^3}{6h_j}M_j+(y_{j-1}-\frac{M_{j-1}h_j^2}{6})\frac{x_j-x}{h_j}+(y_j-\frac{M_jh_j^2}{6})\frac{x-x_{j-1}}{h_j} Sj(x)=6hj(xjx)3Mj1+6hj(xxj1)3Mj+(yj16Mj1hj2)hjxjx+(yj6Mjhj2)hjxxj1

这个表达式是天然的差分形式,可以直接写出相应的代码,在此不再列出。一般来说,任意一点的二阶导数对于已知数据而言仍旧是一个未知量,需要我们通过分段函数的连续特性进行求解,即要求 S ′ ( x j − 0 ) = S ′ ( x j + 0 ) S'(x_j-0)=S'(x_j+0) S(xj0)=S(xj+0),求导可得

S j ′ ( x ) = − ( x j − x ) 2 2 h j M j − 1 + ( x − x j − 1 ) 2 2 h j M j + y j − y j − 1 h j − ( M j − M j − 1 ) h j 6 S'_j(x)=-\frac{(x_j-x)^2}{2h_j}M_{j-1}+\frac{(x-x_{j-1})^2}{2h_j}M_j+\frac{y_j-y_{j-1}}{h_j}-\frac{(M_j-M_{j-1})h_j}{6} Sj(x)=2hj(xjx)2Mj1+2hj(xxj1)2Mj+hjyjyj16(MjMj1)hj

其左右端点为

{ S j ′ ( x j − 0 ) = h j 2 M j + y j − y j − 1 h j − ( M j − M j − 1 ) h j 6 S j ′ ( x j − 1 + 0 ) = − h j 2 M j − 1 + y j − y j − 1 h j − ( M j − M j − 1 ) h j 6 \left\{\begin{aligned} S'_j(x_j-0)&=\frac{h_j}{2}M_j+\frac{y_j-y_{j-1}}{h_j}-\frac{(M_j-M_{j-1})h_j}{6}\\ S'_j(x_{j-1}+0)&=-\frac{h_j}{2}M_{j-1}+\frac{y_j-y_{j-1}}{h_j}-\frac{(M_j-M_{j-1})h_j}{6} \end{aligned}\right. Sj(xj0)Sj(xj1+0)=2hjMj+hjyjyj16(MjMj1)hj=2hjMj1+hjyjyj16(MjMj1)hj

→ { S j ′ ( x j − 0 ) = h j M j − 1 6 + h j 3 M j + y j − y j − 1 h j S j + 1 ′ ( x j + 0 ) = − h j + 1 M j 3 − h j + 1 6 M j + 1 + y j + 1 − y j h j + 1 \to\left\{\begin{aligned} S'_j(x_j-0)&=\frac{h_jM_{j-1}}{6}+\frac{h_j}{3}M_j+\frac{y_j-y_{j-1}}{h_j}\\ S'_{j+1}(x_j+0)&=-\frac{h_{j+1}M_j}{3}-\frac{h_{j+1}}{6}M_{j+1}+\frac{y_{j+1}-y_j}{h_{j+1}}\\ \end{aligned}\right. Sj(xj0)Sj+1(xj+0)=6hjMj1+3hjMj+hjyjyj1=3hj+1Mj6hj+1Mj+1+hj+1yj+1yj

根据连续性条件,上面两个等式应该处处相等,则该等式可整理为

μ j M j − 1 + 2 M j + λ j M j + 1 = d j , j = 1 , 2 , … , n − 1 \mu_jM_{j-1}+2M_j+\lambda_jM_{j+1}=d_j, \quad j=1,2,\ldots,n-1 μjMj1+2Mj+λjMj+1=dj,j=1,2,,n1

各参数分别为

{ λ j = h j + 1 h j + h j + 1 μ j = 1 − λ j = h j h j + h j + 1 d j = 6 f [ x j − 1 , x j , x j + 1 ] \left\{\begin{aligned} \lambda_j &=\frac{h_{j+1}}{h_j+h_{j+1}}\\ \mu_j &=1-\lambda_j=\frac{h_j}{h_j+h_{j+1}}\\ d_j &=6f[x{j-1,x_j,x_{j+1}}] \end{aligned}\right. λjμjdj=hj+hj+1hj+1=1λj=hj+hj+1hj=6f[xj1,xj,xj+1]

由于在力学上 M M M被解释为梁在截面上的弯矩,所以上式被称为三弯矩方程。三弯矩方程再加上此前提到的两个边界条件,即可求出 M M M所对应的唯一解。

第一类边界条件

例如,若给定第一类边界条件,即 S ′ ( x 0 ) = y 0 ′ , S ′ ( x n ) = y n ′ S'(x_0)=y_0',S'(x_n)=y_n' S(x0)=y0,S(xn)=yn,则可得

{ S n ′ ( x n ) = h n M n − 1 6 + h n 3 M n + y n − y n − 1 h n = y n ′ S 1 ′ ( x 0 ) = − h 1 M 0 3 − h 1 6 M 1 + y 1 − y 0 h 1 = y 0 ′ \left\{\begin{aligned} S'_n(x_n)&=\frac{h_nM_{n-1}}{6}+\frac{h_n}{3}M_n+\frac{y_n-y_{n-1}}{h_n}=y_n'\\ S'_1(x_0)&=-\frac{h_1M_0}{3}-\frac{h_1}{6}M_1+\frac{y_1-y_0}{h_1}=y_0'\\ \end{aligned}\right. Sn(xn)S1(x0)=6hnMn1+3hnMn+hnynyn1=yn=3h1M06h1M1+h1y1y0=y0

→ { M n − 1 + 2 M n = 6 h n ( y n ′ − y n − y n − 1 h n ) → d n 2 M 0 + M 1 = 6 h 1 ( − y 0 ′ + y 1 − y 0 h 1 ) → d 0 \to\left\{\begin{aligned} M_{n-1}+2M_n&=\frac{6}{h_n}(y_n'-\frac{y_n-y_{n-1}}{h_n})&\to d_n\\ 2M_0+M_1&=\frac{6}{h_1}(-y_0'+\frac{y_1-y_0}{h_1})&\to d_0 \end{aligned}\right. Mn1+2Mn2M0+M1=hn6(ynhnynyn1)=h16(y0+h1y1y0)dnd0

将边界条件与三弯矩方程联立在一起,可得到确定 n + 1 n+1 n+1个参数的方程组,写成矩阵形式为

[ 2 1 μ 1 2 λ 1 μ 2 2 λ 2 ⋱ ⋱ μ n − 1 2 λ n − 1 1 2 ] [ M 0 M 1 ⋮ M n − 1 M n ] = [ d 0 d 1 ⋮ d n − 1 d n ] \begin{bmatrix} 2&1&&&&\\ \mu_1&2&\lambda_1&&&\\ &\mu_2&2&\lambda_2&&\\ &&\ddots&\ddots&\\ &&&\mu_{n-1}&2&\lambda_{n-1}\\ &&&&1&2 \end{bmatrix} \begin{bmatrix} M_0\\M_1\\\vdots\\M_{n-1}\\M_{n} \end{bmatrix}= \begin{bmatrix} d_0\\d_1\\\vdots\\d_{n-1}\\d_{n} \end{bmatrix} 2μ112μ2λ12λ2μn121λn12M0M1Mn1Mn=d0d1dn1dn

第二类边界条件

第二类边界条件为 S ′ ′ ( x 0 ) = y 0 ′ ′ , S ′ ′ ( x n ) = y n ′ ′ S''(x_0)=y_0'',S''(x_n)=y_n'' S(x0)=y0,S(xn)=yn,其矩阵方程可表示为

[ 2 λ 1 μ 2 2 λ 2 ⋱ ⋱ μ n − 2 2 λ n − 2 μ n − 1 2 ] [ M 1 M 2 ⋮ M n − 2 M n − 1 ] = [ d 1 − μ 1 y 0 ′ ′ d 2 ⋮ d n − 2 d n − 1 − λ n − 1 y n ′ ′ ] \begin{bmatrix} 2&\lambda_1&&&\\ \mu_2&2&\lambda_2&&\\ &\ddots&\ddots&\\ &&\mu_{n-2}&2&\lambda_{n-2}\\ &&&\mu_{n-1}&2 \end{bmatrix} \begin{bmatrix} M_1\\M_2\\\vdots\\M_{n-2}\\M_{n-1} \end{bmatrix}= \begin{bmatrix} d_1-\mu_1y_0''\\d_2\\\vdots\\d_{n-2}\\d_{n-1}-\lambda_{n-1}y_n'' \end{bmatrix} 2μ2λ12λ2μn22μn1λn22M1M2Mn2Mn1=d1μ1y0d2dn2dn1λn1yn

第三类边界条件

第三类边界条件为周期条件,

S ′ ′ ( x 0 + 0 ) = S ′ ′ ( x n − 0 ) , S ′ ( x 0 + 0 ) = S ′ ( x n − 0 ) S''(x_0+0)=S''(x_n-0),S'(x_0+0)=S'(x_n-0) S(x0+0)=S(xn0),S(x0+0)=S(xn0)

→ M 0 = M n , λ n M 1 + μ n M n − 1 + 2 M n = d n \to M_0=M_n,\quad \lambda_nM_1+\mu_nM_{n-1}+2M_n=d_n M0=Mn,λnM1+μnMn1+2Mn=dn

其中,

λ n = h 1 h 1 + h n , μ n = 1 − λ n = h n h 1 + h n \lambda_n=\frac{h_1}{h_1+h_n},\mu_n=1-\lambda_n=\frac{h_n}{h_1+h_n} λn=h1+hnh1,μn=1λn=h1+hnhn

d n = 6 h 1 + h 2 ( y 1 − y 0 h 1 − y n − y n − 1 h n ) d_n=\frac{6}{h_1+h_2}(\frac{y_1-y_0}{h_1}-\frac{y_n-y_{n-1}}{h_n}) dn=h1+h26(h1y1y0hnynyn1)

其矩阵方程为

[ 2 λ 1 μ 1 μ 2 2 λ 2 ⋱ ⋱ μ n − 1 2 λ n − 1 λ n μ n 2 ] [ M 1 M 2 ⋮ M n − 1 M n ] = [ d 1 d 2 ⋮ d n − 1 d n ] \begin{bmatrix} 2&\lambda_1&&&\mu_1 \\ \mu_2&2&\lambda_2&&\\ &\ddots&\ddots&\\ &&\mu_{n-1}&2&\lambda_{n-1}\\ \lambda_n&&&\mu_n&2 \end{bmatrix} \begin{bmatrix} M_1\\M_2\\\vdots\\M_{n-1}\\M_n \end{bmatrix}= \begin{bmatrix} d_1\\d_2\\\vdots\\d_{n-1}\\d_n \end{bmatrix} 2μ2λnλ12λ2μn12μnμ1λn12M1M2Mn1Mn=d1d2dn1dn

求解过程代码如下

# 得到三弯矩法中的弯矩值
# flag表示三类边界条件,需要注意,二类为周期条件,三类为
# para为三类边界条件输入的边界参数
function getMoment(x,y,flag,para=[0,0])
    n = length(x)
    h = x[2:end]-x[1:end-1]
    λ = h[2:end] ./ (h[1:end-1]+h[1:end-1])
    μ = 1 .- λ
    d = [6 * diffQuotient(x[i:i+2],y[i:i+2]) 
            for i in 1:n-2]
    
    nMatrix = Int(n-(-1.5*flag^2+6.5*flag-5))
    M = Matrix{Float64}(I,nMatrix,nMatrix)*2

    # 确定d,λ,μ的值
    if flag == 1
        d0 = 6/h[1]*((y[2]-y[1])/h[1]-para[1])
        dn = 6/h[end]*(-(y[end]-y[end-1])/h[end]+para[2])
        d = vcat(d0,d,dn)
        λ = vcat(1,λ)
        μ = vcat(μ,1)
    elseif flag == 2
        d[1] -= μ[1]*para[1]
        d[end] -= λ[end]*para[2]
        μ = μ[2:end]
    else
        dn = 6/(h[1]+h[end])*((y[2]-y[1])/h[1]-(y[end]-y[end-1])/h[end])
        d = vcat(d,dn)
        M[1,end] = μ[1]
        M[end,1] = λ[end]
        λ = vcat(λ,h[1]/(h[1]+h[end]))
        μ = vcat(μ[2:end],h[end]/(h[1]+h[end]))

    end

    # 生成M矩阵
    for i in 1:nMatrix-1
        M[i,i+1] = λ[i]
        M[i+1,i] = μ[i]
    end

    return M,d
end

参考资料

  1. 李庆杨.数值分析第四版
  2. 颜庆津.数值分析第三版
  3. Kincaid.数值分析第三版
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微小冷

请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值