使用后向与中心有限差分近似

计算科学 有限差分 Python 波传播
2021-12-23 21:19:36

我正在求解简单的二阶波动方程:

2Et2=c22Ez2

在(以 SI 单位)的域上:

z=[0,L=5] m, s, m/st=[0,tmax=5]c=1

和边界/初始条件:

E(z=0)=E(z=L)=0

E(t=0)=E(t=tmax)=sin(πzL)

我知道解析解,但我试图用数值求解。(数值和解析解与测试精度进行了比较。)我有几个问题:

1)当我通过对时间的二阶导数应用 2 阶中心差分近似来求解波动方程时,代码工作得非常好。尽管当我及时对二阶导数应用更高阶的反向有限差分近似时,我的解决方案会发散。为什么使用反向差分近似比同阶的中心差分近似更差,有什么特别的原因吗?我的印象是使用更高阶的反向差分方法会给我更高的准确性,但它似乎根本不起作用。如果需要,我可以提供代码,尽管它是一个相当基本的实现。

m/s 时,我能够求解上述方程,但如果我使用 m/s,则解会发散。这是有道理的,因为 Courant 数 ( ) 远大于一。,是否有任何可能的有限差分方案可用于数值求解方程c=1c=3×108Co=cΔtΔxCo>>1? 我已经阅读了一些关于 BDF 方法来帮助解决刚性方程的内容,但在这种情况下,唯一的问题是需要更精确的网格,对吗?我也听说过光谱方法,但我仍在尝试学习如何实现它们,以及如果波动方程中包含其他(非线性)项,它们是否足够适用/准确。

编辑:

经过进一步测试,即使没有后向差异,输出实际上也略有偏差。代码:

import matplotlib
matplotlib.use('pdf')
import os 
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize

c = 1.#3.*(10.**8.)

#z space
Ngridz = 400 #number of intervals in z-axis
zmax = L = 5.
dz = zmax/Ngridz 
z = np.linspace(0.0,zmax,Ngridz+1) 

#time
Ngridt = 400 #number of intervals in t-axis
tmax = L/c
dt = tmax/Ngridt
t = np.linspace(0.0,tmax,Ngridt+1)  

#def dt2(X,k,i,z,t,kX,TYPE): 
#    """Approximation for second time derivative""" - BACKWARD DIFFERENCE
#    ht = t[1]-t[0]
#    if TYPE == 'CONJ':
#        if i == 0:
#            kTT = np.conj(X[k,i]-2.*X[k,i-1]+X[k,i-2])/(ht**2.)
#        elif i == 1:
#            kTT = np.conj(X[k,i-1]-2.*X[k,i]-X[k,i+1])/(ht**2.)
#        elif i == 2:
#            kTT = np.conj(-(X[k,i])+2.*(X[k,i-1])-(X[k,i-2]))/(ht**2.)
#        elif i == 3:
#            kTT = np.conj(-2.*(X[k,i])+5.*(X[k,i-1])-
#            4.*(X[k,i-2])+(X[k,i-3]))/(ht**2.)
#        elif i == 4:
#            kTT = np.conj((-35/12.)*(X[k,i])+(26./3.)*(X[k,i-1])-
#            (19./2.)*(X[k,i-2])+(14./3.)*(X[k,i-3])-
#            (11./12.)*(X[k,i-4]))/(ht**2.)
#        elif i == 5:
#            kTT = np.conj((-15./4.)*(X[k,i])+(77./6.)*(X[k,i-1])-
#            (107./6.)*(X[k,i-2])+13.*(X[k,i-3])-
#            (61./12.)*(X[k,i-4])+(5./6.)*(X[k,i-5]))/(ht**2.)
#        else:
#            kTT = np.conj((-203./45.)*(X[k,i])+(87./5.)*(X[k,i-1])-
#            (117./4.)*(X[k,i-2])+(254./9.)*(X[k,i-3])-
#            (33./2.)*(X[k,i-4])+(27./5.)*(X[k,i-5])-
#            (137./180.)*(X[k,i-6]))/(ht**2.)       
#    else: 
#        if i == 0:
#            kTT = (X[k,i]-2.*X[k,i-1]+X[k,i-2])/(ht**2.)
#        elif i == 1:
#            kTT = (X[k,i-1]-2.*X[k,i]-X[k,i+1])/(ht**2.)
#        elif i == 2:
#            kTT = (-(X[k,i])+2.*(X[k,i-1])-(X[k,i-2]))/(ht**2.)
#        elif i == 3:
#            kTT = (-2.*(X[k,i])+5.*(X[k,i-1])-
#            4.*(X[k,i-2])+(X[k,i-3]))/(ht**2.)
#        elif i == 4:
#            kTT = ((-35/12.)*(X[k,i])+(26./3.)*(X[k,i-1])-
#            (19./2.)*(X[k,i-2])+(14./3.)*(X[k,i-3])-
#            (11./12.)*(X[k,i-4]))/(ht**2.)
#        elif i == 5:
#            kTT = ((-15./4.)*(X[k,i])+(77./6.)*(X[k,i-1])-
#            (107./6.)*(X[k,i-2])+13.*(X[k,i-3])-
#            (61./12.)*(X[k,i-4])+(5./6.)*(X[k,i-5]))/(ht**2.)
#        else:
#            kTT = ((-203./45.)*(X[k,i])+(87./5.)*(X[k,i-1])-
#            (117./4.)*(X[k,i-2])+(254./9.)*(X[k,i-3])-
#            (33./2.)*(X[k,i-4])+(27./5.)*(X[k,i-5])-
#            (137./180.)*(X[k,i-6]))/(ht**2.)      
#    return kTT

def dt2(X,k,i,z,t,kX,TYPE): 
    """Approximation for second time derivative""" - CENTRAL DIFFERENCE
    ht = t[1]-t[0]
    if TYPE == 'CONJ':
        if i == 0:
            kTT = np.conj(X[k,i]-2.*X[k,i+1]+X[k,i+2])/(ht**2.)
        else:
            kTT = np.conj(X[k,i-1]-2.*X[k,i]-X[k,i+1])/(ht**2.) 
    else: 
        if i == 0:
            kTT = (X[k,i]-2.*X[k,i+1]+X[k,i+2])/(ht**2.) 
        else: 
            kTT = (X[k,i-1]-2.*X[k,i]+X[k,i+1])/(ht**2.) 
    return kTT

Ep = np.zeros((len(z),len(t)),dtype=np.complex_)  
TEST = np.zeros((len(z),len(t)),dtype=np.complex_)  

progress = tqdm(total=100.) #this provides a progress bar
total = 100.
progressz = (total)/(len(z))

k = 0
while k < (len(z) - 1):
    progress.update(progressz)

    EpM = Ep

    hz = z[k+1]-z[k] #hz is positive

    i = 0
    while i < (len(t) - 1):
        ht = t[i+1] - t[i]

        EpM[0,i] = 0. #at z = 0 
#        EpM[Ngridz-1,:] = 0. 
        EpM[k,0] = np.sin(np.pi*z[k]) 
#        EpM[k+1,0] = np.sin(np.pi*z[k+1])
        EpM[k,Ngridt-1] = -np.sin(np.pi*z[k])    

        EpM[k+1,i] = (-EpM[k,i-1] + 2.*EpM[k,i] + ((hz/(c))**2.)*dt2(EpM,k,i,z,t,0.,'x'))
#((hz/(c*ht))**2.)*(EpM[k,i+1] - 2.*EpM[k,i] + EpM[k,i-1]))


#        print 'k='+str(k)+',i='+str(i)+',dE='+str(EpM[k+1,i]-EpM[k,i])

        EpM[0,i] = 0. #at z = 0 
#        EpM[Ngridz-1,:] = 0. 
        EpM[k,0] = np.sin(np.pi*z[k]) 
#        EpM[k+1,0] = np.sin(np.pi*z[k+1])
        EpM[k,Ngridt-1] = -np.sin(np.pi*z[k])   

        TEST[k,i] = np.sin(np.pi*z[k])*np.cos(np.pi*t[i])

        i = i + 1 

        Ep[k,:] = EpM[k,:]

    k = k + 1

    if k == (len(z)-1):  
        progress.update(progressz) 

Ereal = (Ep).real

newpath = r'test_wave_equation'
if not os.path.exists(newpath):
    os.makedirs(newpath)

plt.figure(1)
fig, ax = plt.subplots(figsize=(20, 20))
plt.subplot(221)
plt.plot(t,Ereal[0,:],'k:',linewidth = 1.5)
plt.ylabel('Normalized E.real')
plt.subplot(222)
plt.plot(t,Ereal[int(Ngridz*0.33),:],'k:',linewidth = 1.5)
plt.subplot(223)
plt.plot(t,Ereal[int(Ngridz*0.66),:],'k:',linewidth = 1.5)
plt.subplot(224)
plt.plot(t,Ereal[int(Ngridz),:],'k:',linewidth = 1.5)
plt.xlabel(r" t (s)")
plt.savefig(str(newpath)+'/E.real_vs_t.pdf')
#plt.show()

plt.figure(2)
fig, ax = plt.subplots(figsize=(20, 20))
plt.subplot(221)
plt.plot(z,Ereal[:,0],'k:',linewidth = 1.5)
plt.ylabel('Normalized E.real')
plt.subplot(222)
plt.plot(z,Ereal[:,int(Ngridt*0.33)],'k:',linewidth = 1.5)
plt.subplot(223)
plt.plot(z,Ereal[:,int(Ngridt*0.66)],'k:',linewidth = 1.5)
plt.subplot(224)
plt.plot(z,Ereal[:,Ngridt],'k:',linewidth = 1.5)
plt.xlabel(r" z (m)")
plt.savefig(str(newpath)+'/E.real_vs_z.pdf') 

plt.figure(3)
fig, ax = plt.subplots(figsize=(20, 20))
plt.subplot(221)
plt.plot(t,TEST[0,:],'k:',linewidth = 1.5)
plt.ylabel('True E')
plt.subplot(222)
plt.plot(t,TEST[int(Ngridz*0.33),:],'k:',linewidth = 1.5)
plt.subplot(223)
plt.plot(t,TEST[int(Ngridz*0.66),:],'k:',linewidth = 1.5)
plt.subplot(224)
plt.plot(t,TEST[int(Ngridz),:],'k:',linewidth = 1.5)
plt.xlabel(r" t (s)")
plt.savefig(str(newpath)+'/E.real_vs_t.pdf')
#plt.show()

plt.figure(4)
fig, ax = plt.subplots(figsize=(20, 20))
plt.subplot(221)
plt.plot(z,TEST[:,0],'k:',linewidth = 1.5)
plt.ylabel('Normalized E.real')
plt.subplot(222)
plt.plot(z,TEST[:,int(Ngridt*0.33)],'k:',linewidth = 1.5)
plt.subplot(223)
plt.plot(z,TEST[:,int(Ngridt*0.66)],'k:',linewidth = 1.5)
plt.subplot(224)
plt.plot(z,TEST[:,Ngridt],'k:',linewidth = 1.5)
plt.xlabel(r" z (m)")
plt.savefig(str(newpath)+'/E.real_vs_z.pdf') 

输出:http: //imgur.com/a/GwPCt

后向差分近似的输出:http: //imgur.com/a/66jem

如您所见,我的输出似乎与解析解不匹配,并且这种差异仅在使用中心差分近似时才出现。随着时间和 z 分量的推进,我的解决方案似乎确实收敛到了正确的答案。我已经尝试增加网格点的数量(即减小间隔大小)和调试,但我似乎没有看到任何明显的错误。关于可能导致问题的任何想法?我已经注释掉了用于二阶时间导数的后向差分近似的函数,但是当我取消注释并使用它而不是 dt2 的中心差分函数时,解决方案会发散并且原始问题仍然存在。

1个回答

高阶方法通常具有较小的收敛半径,即它们需要较小的时间步长。在您的上下文中,这意味着它们需要更小的 CFL 编号,通常远小于 1。您是否考虑到这一点?

至于是否有方法可以处理远大于 1 的 CFL 数字的问题:这真的没有任何意义。CFL 数定义为时间步长除以信息通过一个像元所用时间的比率。如果你想要 CFL 1,这意味着在一个时间步内,信息传播的远不止一个细胞。因为在这个时间间隔内,信号可能会与边界或自身相互作用,如果您的 CFL 数量很大,您不能指望您可以准确地解决解决方案。换句话说,当然有一些方法保持稳定,但如果 CFL 数远大于 1 ,它们将不准确。

换一种说法:如果你将波速提高 10 倍,那么一切都会以十倍的速度发生,然后你必须选择比以前小十倍的时间步长才有意义。