我正在积分一个偏微分方程,其中我在$ x $中有一个四阶偏导数,而时间上的数值积分给了我荒谬的错误。我认为,问题的原因是四阶导数出现很大的错误。为了说明这一点,我采用函数$ y(x)= 1- \ cos(2 \ pi x)$的数值导数。下面,我在域$(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的明显增加的边界处通过算法减少这种大误差?因为它是融合 四阶导数的不均匀。是否有可能做到这一点 制服?也许,在边界处使用外推法即可。
答案 0 :(得分:3)
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)
,计算 that 的导数,并取中间的部分,截断消除任何边界效应。
不是对四阶导数应用四次有限差分公式,而是对四阶导数应用一次。边界值的问题仍然存在,我的最佳想法是最简单的,恒定的外推法。那就是:
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度样条,然后<解析>取其四阶导数。这需要SciPy,并且可能比直接方法要慢得多。
from scipy.interpolate import InterpolatedUnivariateSpline
spl = InterpolatedUnivariateSpline(X, Y, k=5)
Y_xxxx = spl.derivative(4)(X)
结果:
在统一网格上进行插值通常会导致边界处的精度下降,因此我们应该期望使用Chebyshev节点进行改进。在这里:
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的InterpolatedUnivariateSpline
。现在绘制四阶导数本身不会显示数值和分析之间的可见差异,因此这里是Y_xxxx - analytics
的图:
该错误最多为3,并且在边界处不再最大。均匀网格的最大误差约为33。
我还探讨了将内插条件施加到插值样条曲线上的可能性,以进一步提高其精度。可以想象,make_interp_spline
可以这样做
l, r = [(0, 0), (1, 0)], [(0, 0), (1, 0)]
spl = make_interp_spline(X, Y, k=5, bc_type=(l, r))
但是任何一种节点都有错误:“配置矩阵是奇异的”。我认为其边界条件的处理针对三次样条。
答案 1 :(得分:1)
在这里,我使用numpy polyfit方法在边界处使用外推法。
# 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])
此外,我在边界处有一个较小的网格,以便减少那里的数值导数(Y_xxxx)的误差,而不必在整个积分域中均匀地增加网格点的数量。
# 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])
由于积分步骤不是恒定的,所以我继续使用numpy梯度法,因为在数值计算导数时可以考虑该变化。
这是我使用的不是很Python的代码,用于比较带有或不带有可变网格的结果:
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()
这里是两种方法的比较(两种方法都在边界处进行外推), 其中一个步骤可变。
此处错误(x)= log_10 | Y_xxxx(x)+(2 * pi)** 4 * cos(kx)|