Numpy 区间端点附近四阶数值导数的误差

Numpy 区间端点附近四阶数值导数的误差,numpy,numeric,numerical-methods,Numpy,Numeric,Numerical Methods,我在积分一个偏微分方程,其中有一个四阶偏导数,在$x$,时间上的数值积分给了我荒谬的错误。我认为问题的原因是,我在四阶导数中得到了很大的误差。为了说明,我取函数$y(x)=1-\cos(2\pix)$的数值导数。下面我绘制了域$(0,1.0)$中的导数$y{xx}(x)$和$y{xxxx}(x)$。见下图: 可以看出,误差主要发生在边界附近 数值导数采用numpy梯度法进行。 python代码如下: import numpy as np import matplotlib.pyplot as

我在积分一个偏微分方程,其中有一个四阶偏导数,在$x$,时间上的数值积分给了我荒谬的错误。我认为问题的原因是,我在四阶导数中得到了很大的误差。为了说明,我取函数$y(x)=1-\cos(2\pix)$的数值导数。下面我绘制了域$(0,1.0)$中的导数$y{xx}(x)$和$y{xxxx}(x)$。见下图:

可以看出,误差主要发生在边界附近

数值导数采用numpy梯度法进行。 python代码如下:

import numpy as np
import matplotlib.pyplot as plt
N = 64
X = np.linspace(0.0, 1.0, N, endpoint = True)
dx = 1.0/(N-1)
Y= 1.0-np.cos(2*np.pi*X)
Y_x = np.gradient(Y, X, edge_order = 2)
Y_xx = np.gradient(Y_x, X, edge_order = 2)
plt.figure()
plt.title("$y(x)=1-\cos(2\pi x)$")
plt.xlabel("$x$")
plt.ylabel("$y_{xx}(x)$")
plt.plot(X, ((2*np.pi)**2)*np.cos(2*np.pi*X), 'r-', label="analytics")
plt.plot(X, Y_xx, 'b-', label="numerics")
plt.legend()
plt.grid()
plt.savefig("y_xx.png")
Y_xxx = np.gradient(Y_xx, X, edge_order = 2)
Y_xxxx = np.gradient(Y_xxx, X, edge_order = 2)
plt.figure()
plt.title("$y(x)=1-\cos(2\pi x)$")
plt.xlabel("$x$")
plt.ylabel("$y_{xxxx}(x)$")
plt.plot(X, -((2*np.pi)**4)*np.cos(2*np.pi*X), 'r-', label="analytics")
plt.plot(X, Y_xxxx, 'b-', label="numerics")
plt.legend()
plt.grid()
plt.savefig("y_xxxx.png")
plt.show()
我的问题是,如何从算法上减少边界上的这个大误差,使其超过N的明显增加?因为它是收敛的 四阶导数的性质是不一致的。有可能吗 制服也许,在边界处使用外推就可以了。

潜在的数学问题
np.gradient
使用的一阶导数公式有错误
O(h**2)
(其中h是dx)。当你继续求导时,这可能是不好的,因为改变一个函数的量
z
可以改变它的导数
z/h
。通常不会发生这种情况,因为误差来自函数的高阶导数,其本身变化平稳;因此,随后的微分“消除”了上一步的大部分错误,而不是将其放大
1/h

然而,边界附近的情况不同,我们必须从一个有限差分公式(中心差分)切换到另一个。边界公式也有
O(h**2)
错误,但它是不同的
O(h**2)
。现在我们在后续的差异化步骤中确实遇到了问题,每个步骤都会产生
1/h
。四阶导数的最坏情况是
O(h**2)*(1/h**3)
,幸运的是,这在这里没有实现,但仍然会得到非常糟糕的边界效应。我提供了两种不同的解决方案,它们的性能大致相同(第二种稍好一些,但更昂贵)。但首先,让我们强调沃伦·韦克瑟(Warren Weckesser)在评论中所说的话的含义:

如果函数是周期性的,则可以通过周期性来扩展它,例如,
np.tile(Y,3)
,计算其导数,取中间部分,截断任何边界效果

四阶导数的直接计算 不要对一阶导数应用四次有限差分公式,而是对四阶导数应用一次。仍然会有边界值的问题,对于这个问题,我最好的想法是最简单的,常数外推。即:

Y_xxxx = (Y[4:] - 4*Y[3:-1] + 6*Y[2:-2] - 4*Y[1:-3] + Y[:-4])/(dx**4)
Y_xxxx = np.concatenate((2*[Y_xxxx[0]], Y_xxxx, 2*[Y_xxxx[-1]]))
结果

如果您不喜欢侧边的小扁平位,请使用较小的
dx
(它们的大小为
2*dx

微分插值样条曲线 将5次样条曲线拟合到数据,然后解析地取其4次导数。这需要SciPy,并且可能比直接方法慢得多

from scipy.interpolate import InterpolatedUnivariateSpline
spl = InterpolatedUnivariateSpline(X, Y, k=5)
Y_xxxx = spl.derivative(4)(X)
结果:

非均匀网格 对于均匀网格上的插值,边界处的精度损失是典型的,因此我们应该期望使用切比雪夫节点来进行改进。这是:

def cheb_nodes(a, b, N):
    jj = 2.*np.arange(N) + 1
    x = np.cos(np.pi * jj / 2 / N)[::-1]
    x = (a + b + (b - a)*x)/2
    return x

N = 64
X = cheb_nodes(0, 1, N)
其余的继续进行,使用5次的
插值单变量样条线
。绘制四阶导数本身现在显示数值和分析之间没有明显的区别,因此这里是
Y_xxxx-分析的曲线图

误差最大为3,在边界处不再是最大值。均匀网格的最大误差约为33

我还探讨了对插值样条曲线施加夹紧条件以进一步提高其精度的可能性。可以想象,我们可以用

l, r = [(0, 0), (1, 0)], [(0, 0), (1, 0)]
spl = make_interp_spline(X, Y, k=5, bc_type=(l, r))
但这两种节点都存在错误:“配置矩阵是奇异的”。我认为它对边界条件的处理是针对三次样条曲线的

潜在的数学问题
np.gradient
使用的一阶导数公式有错误
O(h**2)
(其中h是dx)。当你继续求导时,这可能是不好的,因为改变一个函数的量
z
可以改变它的导数
z/h
。通常不会发生这种情况,因为误差来自函数的高阶导数,其本身变化平稳;因此,随后的微分“消除”了上一步的大部分错误,而不是将其放大
1/h

然而,边界附近的情况不同,我们必须从一个有限差分公式(中心差分)切换到另一个。边界公式也有
O(h**2)
错误,但它是不同的
O(h**2)
。现在我们在后续的差异化步骤中确实遇到了问题,每个步骤都会产生
1/h
。四阶导数的最坏情况是
O(h**2)*(1/h**3)
,幸运的是,这在这里没有实现,但仍然会得到非常糟糕的边界效应。我提供了两种不同的解决方案,它们的性能大致相同(第二种稍好一些,但更昂贵)。但首先,让我们强调沃伦·韦克瑟(Warren Weckesser)在评论中所说的话的含义:

如果函数是周期性的,则可以通过周期性来扩展它,例如,
np.tile(Y,3)
,计算其导数,取中间部分,截断任何边界效果

四阶导数的直接计算 不要对一阶导数应用四次有限差分公式,而是对四阶导数应用一次。仍然会有边界的问题
# Quadratic extrapolation of the left end-points of Y_xxxx
x = X[2:5]
y = Y_xxxx[2:5]
z = np.polyfit(x, y, 2)
p = np.poly1d(z)
for i in range(2):
    Y_xxxx[i] = p(X[i])
# grid with variable spacing
dx = 1.0/N
X1 = np.linspace(0.0, 4*dx, 16)
X2 = np.linspace(4*dx, 1.0-4*dx, N)
X3 = np.linspace(1.0-4*dx, 1.0, 16)
X= np.concatenate([X1, X2, X3])
import numpy as np
import matplotlib.pyplot as plt
N = 512
X = np.linspace(0.0, 1.0, N, endpoint = True)
Y= 1.0-np.cos(2*np.pi*X)
Y_x = np.gradient(Y, X, edge_order = 2)
Y_xx = np.gradient(Y_x, X, edge_order = 2)
# Quadratic extrapolation of the left end-points of Y_xx
x = X[2:5]

y = Y_xx[2:5]
z = np.polyfit(x, y, 2)
print "z=", z
p = np.poly1d(z)
Y_xx[0] = p(X[0])
Y_xx[1] = p(X[1])

# Quadratic extrapolation of the right end-points of Y_xx
x = X[-5:-2]
y = Y_xx[-5:-2]
z = np.polyfit(x, y, 2)
p = np.poly1d(z)
Y_xx[-1] = p(X[-1])
Y_xx[-2] = p(X[-2])

Y_xxx = np.gradient(Y_xx, X, edge_order = 2)
Y_xxxx = np.gradient(Y_xxx, X, edge_order = 2)
# Quadratic extrapolation of the left end-points of Y_xxxx
x = X[2:5]
y = Y_xxxx[2:5]

z = np.polyfit(x, y, 2)
print z
p = np.poly1d(z)
for i in range(2):
    Y_xxxx[i] = p(X[i])
# Quadratic extrapolation of the right end-points of Y_xxxx
x = X[-5:-2]
y = Y_xxxx[-5:-2]

z = np.polyfit(x, y, 2)
#print z
p = np.poly1d(z)
for i in [-1, -2]:
    Y_xxxx[i] = p(X[i])
plt.figure()

plt.title("$y(x)=1-\cos(2\pi x)$")
plt.xlabel("$x$")
plt.ylabel("$y_{xxxx}(x)$")
plt.plot(X, -((2*np.pi)**4)*np.cos(2*np.pi*X), 'r-', label="analytics")
plt.plot(X, Y_xxxx, 'b-', label="numerics")
plt.legend()
plt.grid()

plt.savefig("y_xxxx.png")
plt.figure()
diff = Y_xxxx+((2*np.pi)**4)*np.cos(2*np.pi*X)
logErr = 0.5*np.log10(diff**2)
plt.plot(X, logErr, 'b-', label = "Fixed spacing")

# grid with variable spacing
dx = 1.0/N
X1 = np.linspace(0.0, 4*dx, 16)
X2 = np.linspace(4*dx, 1.0-4*dx, N)
X3 = np.linspace(1.0-4*dx, 1.0, 16)
X= np.concatenate([X1, X2, X3])

Y= 1.0-np.cos(2*np.pi*X)
Y_x = np.gradient(Y, X, edge_order = 2)
Y_xx = np.gradient(Y_x, X, edge_order = 2)
# Quadratic extrapolation of the left end-points of Y_xx
x = X[2:5]
y = Y_xx[2:5]

z = np.polyfit(x, y, 2)
print "z=", z
p = np.poly1d(z)
Y_xx[0] = p(X[0])
Y_xx[1] = p(X[1])
# Quadratic extrapolation of the right end-points of Y_xx

x = X[-5:-2]
y = Y_xx[-5:-2]
z = np.polyfit(x, y, 2)
p = np.poly1d(z)
Y_xx[-1] = p(X[-1])
Y_xx[-2] = p(X[-2])

Y_xxx = np.gradient(Y_xx, X, edge_order = 2)
Y_xxxx = np.gradient(Y_xxx, X, edge_order = 2)
# Quadratic extrapolation of the left end-points of Y_xxxx
x = X[2:5]
y = Y_xxxx[2:5]
z = np.polyfit(x, y, 2)
p = np.poly1d(z)
for i in range(2):
    Y_xxxx[i] = p(X[i])
# Quadratic extrapolation of the right end-points of Y_xxxx
x = X[-5:-2]
y = Y_xxxx[-5:-2]
z = np.polyfit(x, y, 2)
#print z
p = np.poly1d(z)
for i in [-1, -2]:
    Y_xxxx[i] = p(X[i])
diff = Y_xxxx+((2*np.pi)**4)*np.cos(2*np.pi*X)
logErr = 0.5*np.log10(diff**2)
plt.plot(X, logErr, 'r.-', label = "variable spacing")
plt.title("$log_{10}(|Error(x)|)$, N=%d" % (N))
plt.xlabel("$x$")
plt.xlim(0., 1.)
plt.legend()
plt.grid()
figure = "varStepLogErr.png"
print figure
plt.savefig(figure)
plt.show()