2024-3S 計算数理演習(東京大学理学部・教養学部) [齊藤宣一] [top] [0] [1] [2] [3] [4] [5] [6] [7] [8] [UTOL]
一様な材質でできた,長さが
針金の温度変化を論じるには針金の端点(境界)での物理的な状況設定が必要である.
もし,この両端が一定の温度,たとえば,
もし,端点で熱の出入りがない状態が保たれている,すなわち,断熱の状態であれば,
境界条件として,
Neumann境界条件
温度変化の考察を
当面,熱方程式(8.1a),Dirichlet境界条件(8.1b),初期条件(8.1c)からなる,熱方程式の初期値境界値問題(8.1a,b,c)を扱う.Neumann境界条件については,次節で,詳しく扱う.
熱方程式の初期値境界値問題(8.1a,b,c)は,Fourierの方法を用いて,解を得ることができる.しかし,ここでは,数値的な方法による近似解法を考えたい.
高等学校で学んだように,滑らかな関数
2階の微分係数
あらためて,Dirichlet境界条件下での熱方程式の初期値境界値問題(8.1a,b,c)を考えよう.
(8.6a,b)は,
注意.
実際に,(8.6a.b.c)を計算してみよう.この節で使う予定のモジュールをすべてインポートしておく.
In [1]: import numpy as np import matplotlib.pyplot as plt from matplotlib import animation, rc from IPython.display import HTML import plotly.graph_objects as go import plotly.offline as pyo
(8.6a,b,c)を計算する関数を作成する.
ただし,時間区間については,
その上で,
以上を行う関数が,In [2]に示した heat_0dbc_ex1 である.
In [2]: #(斉次)熱方程式、零Dirichlet境界条件、陽的スキーム def heat_0dbc_ex1(L, kappa, initial, N, T, lam, num): h = L/(N+1) tau = lam*h*h/kappa nmax = int(T/tau) step=int(max(1, nmax/num)) xgrid=np.linspace(0.0, L, N+2) tgrid=np.array([0.0]) u=np.vectorize(initial)(xgrid) sol=np.copy(u) for n in range(nmax): u[1:N+1] = (1-2*lam)*u[1:N+1]+lam*(u[0:N]+u[2:N+2]) tnow = (n+1)*tau if (n+1)%step==0: sol=np.vstack((sol,u)) tgrid=np.append(tgrid, tnow) return xgrid, tgrid, sol
次に可視化のために,関数を3つ用意する.
In [3]: #図のプロット、一枚の図に、グラフを重ねる def plot_solution(xgrid, uvect): # 描画に関するパラメータの設定 umax = np.max(sol) umin = np.min(sol) ulength = umax - umin umax += 0.05 * ulength umin -= 0.05 * ulength # t = 0 plt.plot(xgrid,uvect[0,:],color='#ff8c00') # t > 0 for n in range(1,uvect.shape[0]): plt.plot(xgrid,uvect[n,:],color='#00bfff') plt.xlabel('x') plt.ylabel('u') plt.ylim(umin, umax) plt.grid('on') plt.show()
In [4]: # xtu空間での曲線の描画 # plotly.graph_objects,plotly.offline モジュールを使う # xgrid.shape <= tgrid.shape でないと上手く描けない def plot_solution3d(xgrid, tgrid, uvect): # 3Dプロットの作成 fig = go.Figure() Nt = tgrid.shape[0] # t = 0 n = 0 tval = tgrid[n]*np.ones(Nt) fig.add_trace(go.Scatter3d(x=xgrid, y=tval, z=uvect[n,:], mode='lines',line=dict(color='#ff8c00', width=2))) # t > 0 for n in range(1, Nt): tval = tgrid[n]*np.ones(Nt) fig.add_trace(go.Scatter3d(x=xgrid, y=tval, z=uvect[n,:], mode='lines',line=dict(color='#00bfff', width=2))) # レイアウトの設定 fig.update_layout(scene=dict(xaxis_title='x', yaxis_title='time', zaxis_title='u')) fig.update_layout(scene=dict(camera=dict(eye=dict(x=1.5, y=-1.5, z=1)))) fig.update_layout(showlegend=False) # プロットの表示 pyo.plot(fig) with open('temp-plot.html', 'r') as file: html_content = file.read() display(HTML(html_content)) # Google Colaboratoryでなければ、上の4行は、以下の1行で置き換えられる(はず) # fig.show()
In [5]: #アニメーションの作成 # IPython.displayモジュールを使う def plot_animation(xgrid, tgrid, uvect): # 描画に関するパラメータの設定 umax = np.max(sol) umin = np.min(sol) xmax = np.max(xgrid) xmin = np.min(xgrid) ulength = umax - umin utop = umax + 0.1*ulength umax += 0.05*ulength umin -= 0.05*ulength xmid = (xmax+xmin)/2 xlength = xmax - xmin xmax += 0.05*xlength xmin -= 0.05*xlength # fig, axオブジェクトを作成 fig, ax = plt.subplots() ims = [] # t = 0 n = 0 im, = ax.plot(xgrid, uvect[n, :], color='#ff8c00') title = ax.text(xmid, utop, f'time={tgrid[n]:.4f}', ha='center', va='center', fontsize=15) ims.append([im, title]) # t > 0 for n in range(1, uvect.shape[0]): im, = ax.plot(xgrid, uvect[n, :], color='#00bfff') title = ax.text(xmid, utop, f'time={tgrid[n]:.4f}', ha='center', va='center', fontsize=15) ims.append([im, title]) # 各軸のラベル ax.set_xlabel(r"$x$", fontsize=15) ax.set_ylabel(r"$u$", fontsize=15) # グラフの範囲を設定 ax.set_xlim([xmin, xmax]) ax.set_ylim([umin, umax]) ax.grid(True) # ArtistAnimationにfigオブジェクトとimsを代入してアニメーションを作成 return animation.ArtistAnimation(fig, ims)
これらを,次のIn [6]ように使う.熱方程式に対する差分法の最初の例としては,初期関数として,
In [6]: # 初期値 def initial1(x): return min(x,1-x) #パラメータの設定 L = 1.0 kappa = 1.0 N = 51 T = 1.0 lam = 0.4 num = 60 #熱方程式の計算 x, tn, sol = heat_0dbc_ex1(L, kappa, initial1, N, T, lam, num) #図の表示 plot_solution(x, sol) #3D図の表示 plot_solution3d(x, tn, sol) #アニメーションを表示 anim = plot_animation(x, tn, sol) #結果をheat.gifに保存(Googleドライブをマウントしておくこと!) anim.save('/content/drive/MyDrive/Colab Notebooks/fig/heat.gif', writer='pillow') rc('animation', html='jshtml') plt.close() anim
![]() |
![]() |
---|
以下では,可視化にはplot_solutionのみを使う.
まずは,
実は,(8.1)の解
この例から,(8.9)と同様の性質は,陽的スキームの解については,一般には成り立たないことがわかる.それでは,どうすれば良いであろうか? 実は,
対応する性質の成立を保証するためには,
In [2]のheat_0dbc_ex1において,
針金に熱の湧き出しや吸収がある場合には,(8.1a)の代わりに,非斉次の熱方程式,
In [7]: #非斉次熱方程式、零Dirichlet境界条件、陽的スキーム def heat_0dbc_ex2(L, kappa, initial, righthand, N, T, lam, num): h = L/(N+1) tau = lam*h*h/kappa nmax = int(T/tau) step=int(max(1, nmax/num)) xgrid=np.linspace(0.0, L, N+2) tgrid=np.array([0.0]) u=np.vectorize(initial)(xgrid) sol=np.copy(u) n=0 tnow=n*tau for n in range(nmax): u[1:N+1] = (1-2*lam)*u[1:N+1]+lam*(u[0:N]+u[2:N+2]) + tau*righthand(xgrid[1:N+1], tnow) tnow = (n+1)*tau if (n+1)%step==0: sol=np.vstack((sol,u)) tgrid=np.append(tgrid, tnow) return xgrid, tgrid, sol
In [8]: # 初期値 a def initial2(x): return x**3*(1.0 - x) # 右辺の関数 f def righthand2(x, t): return np.exp(t)*(-x**4 + x**3 + 12*x**2 - 6*x) #パラメータの設定 L = 1.0 kappa = 1.0 N = 51 T = 1.0 lam = 0.4 num = 30 #熱方程式の計算 x, tn, sol = heat_0dbc_ex2(L, kappa, initial2, righthand2, N, T, lam, num) #図の表示 plot_solution(x, sol)
次に,
In [9]: # 初期値 a def initial3(x): return np.sin(4.0*np.pi*x)*x*(1.0 - x) # 右辺の関数 f def righthand3(x, t): return 4.0*np.pi**2*np.sin(2.0*np.pi*x) #パラメータの設定 L = 1.0 kappa = 1.0 N = 51 T = 1.0 lam = 0.4 num = 30 #熱方程式の計算 x, tn, sol = heat_0dbc_ex2(L, kappa, initial3, righthand3, N, T, lam, num) #図の表示 plot_solution(x, sol)
結果は,図8.6の通りである.
このとき.数値解は,時間が十分に経過した際に,ある関数
非斉次熱方程式の初期値境界値問題(8.12),(8.1b,c)と,その陽的スキーム(8.13),(8.6b,c)の誤差について考察する.その前に,まず,(8.13),(8.6b,c)の解
さて,改めて,誤差
一方で,
定理.
上の計算を実験的に確かめてみよう.
そのために,(8.14)の
In [10]: #誤差の計算、非斉次熱方程式、零Dirichlet境界条件、陽的スキーム def error_heat(L, kappa, initial, righthand, exact, T, lam): N = 10 num = 200 kmax = 6 hv = np.zeros(kmax) ev = np.zeros(kmax) for k in range(kmax): N = N*2 x, tn, sol = heat_0dbc_ex2(L, kappa, initial, righthand, N, T, lam, num) err = 0.0 for n in range(tn.shape[0]): tval = tn[n] err = max(err, np.linalg.norm(sol[n,:] - exact(x, tval), ord=np.inf)) hv[k] = L/(N+1) ev[k] = err rate=(np.log(ev[1:]) - np.log(ev[:-1])) / (np.log(hv[1:]) - np.log(hv[:-1])) return hv, ev, rate
In [11]: # 初期値 a def initial2(x): return x**3*(1.0 - x) # 右辺の関数 f def righthand2(x, t): return np.exp(t)*(-x**4 + x**3 + 12*x**2 - 6*x) # 厳密解 u def exact2(x, t): return np.exp(t)*(1.0 - x)*x**3 #パラメータの設定 L = 1.0 kappa = 1.0 N = 51 T = 1.0 lam = 0.4 num = 30 #熱方程式の計算 hv, ev, rate = error_heat(L, kappa, initial2, righthand2, exact2, T, lam) #収束の速さの出力 for i in range(rate.shape[0]-1): print(f'{hv[i+1]:.3f}, {rate[i]:.3f}') #結果の描画(両対数目盛) plt.plot(hv, ev, 'bo-') plt.xscale('log') plt.yscale('log') plt.legend(['explicit']) plt.xlabel('h') plt.ylabel('error') plt.grid('on') plt.gca().set_aspect('equal', adjustable='box') plt.show() Out [11]: 0.024, 1.995 0.012, 1.999 0.006, 2.000 0.003, 2.000
ギターの弦のように真っ直ぐに張った弦の横振動は,波動方程式
引き続き,格子点の集合
熱方程式の場合と同様に考えると,(8.17a)の差分近似は,
まとめると,
波動方程式に対する初期値境界値問題(8.17a,b,c)に対する差分解法は,次のようになる.まず,
(8.18c)と(8.18d)で,
In [12]: #非斉次波動程式、零Dirichlet境界条件、陽的スキーム def wave_0dbc_ex(L, c, funcf, funcg, N, T, lam, num): h = L/(N+1) tau = lam*h/c nmax = int(T/tau) step=int(max(1, nmax/num)) xgrid=np.linspace(0.0, L, N+2) tgrid=np.array([0.0]) unew = np.zeros_like(xgrid) # n = 0 u = np.vectorize(funcf)(xgrid) n = 0 tnow = n*tau sol = np.copy(u) upast = np.copy(u) # n = 1 u[1:N+1]=tau*np.vectorize(funcg)(xgrid[1:N+1])+(1-lam**2)*u[1:N+1]+(lam**2/2.0)*(u[0:N]+u[2:N+2]) n = 1 tnow=n*tau sol=np.vstack((sol,u)) tgrid=np.append(tgrid, tnow) # n >= 2 for n in range(1, nmax, 1): unew[1:N+1] = 2*(1-lam**2)*u[1:N+1] + lam**2*(u[0:N]+u[2:N+2]) - upast[1:N+1] upast=np.copy(u) u=np.copy(unew) tnow=(n+1)*tau if (n+1)%step==0: sol=np.vstack((sol,u)) tgrid=np.append(tgrid, tnow) return xgrid, tgrid, sol
In [13]: # 初期値 f def displacement(x): return np.sin(2.0*np.pi*x) # 初期値 g def velocity(x): return 0.0 #パラメータの設定 L = 1.0 c = 1.0 N = 51 T = 2 lam = 1.0 num = 60 #波動方程式の計算 x, tn, sol = wave_0dbc_ex(L, c, displacement, velocity, N, T, lam, num) #図の表示 plot_solution(x, sol) #アニメーションを表示 anim = plot_animation(x, tn, sol) #結果をheat.gifに保存(Googleドライブをマウントしておくこと!) anim.save('/content/drive/MyDrive/Colab Notebooks/fig/wave.gif', writer='pillow') rc('animation', html='jshtml') plt.close() anim
しかしながら,図8.9に示した通り,
実は,
しかしながら,熱方程式の(8.16)のような顕著な安定性が得られないため,誤差解析は,案外厄介である.興味のある人は,[齊藤2023]や[John]を見よ.したがって,本講義では,数値実験のみで誤差の挙動を確かめておく.
In [14]: #誤差の計算、波動方程式、零Dirichlet境界条件、陽的スキーム def error_wave(L, c, funcf, funcg, exact, T, lam): N = 10 num = 100 kmax = 6 hv = np.zeros(kmax) ev = np.zeros(kmax) for k in range(kmax): N = N*2 x, tn, sol = wave_0dbc_ex(L, c, funcf, funcg, N, T, lam, num) err = 0.0 for n in range(tn.shape[0]): tval = tn[n] err = max(err, np.linalg.norm(sol[n,:] - exact(x, tval), ord=np.inf)) hv[k] = L/(N+1) ev[k] = err rate=(np.log(ev[1:]) - np.log(ev[:-1])) / (np.log(hv[1:]) - np.log(hv[:-1])) return hv, ev, rate
In [15]: # 初期値 f def displacement(x): return np.sin(2.0*np.pi*x) # 初期値 g def velocity(x): return 0.0 # 厳密解 u def exact_wave(x, t): return np.sin(2.0*np.pi*x)*np.cos(2.0*np.pi*t) #パラメータの設定 L = 1.0 c = 1.0 T = 1.0 lam = 0.9 #波動方程式の計算 hv, ev, rate = error_wave(L, c, displacement, velocity, exact_wave, T, lam) #収束の速さの出力 for i in range(rate.shape[0]-1): print(f'{hv[i+1]:.3f}, {rate[i]:.3f}') #結果の描画(両対数目盛) plt.plot(hv, ev, 'bo-') plt.xscale('log') plt.yscale('log') plt.legend(['explicit']) plt.xlabel('h') plt.ylabel('error') plt.grid('on') plt.gca().set_aspect('equal', adjustable='box') plt.show() Out [15]: 0.024, 2.004 0.012, 2.000 0.006, 2.000 0.003, 2.000
注意.
In [15] において,
注意. (8.22)を,Courant--Friedrichs--Lewy (CFL) 条件と言うと述べた. 一方で,熱方程式に対する(8.10)も,CFL条件と言われることがあるが,私個人は,適切な用語法とは思えないため,使わない.
熱方程式の初期値境界値問題(8.1a,b,c)に対して,厳密解を自分で作り,
熱方程式(8.1a)に対して,初期条件(8.1c)と,非斉次のDirichlet境界条件
初期条件
波動方程式の初期値境界値問題(8.17a,b,c)において,境界条件を,周期境界条件
ヒント: この場合は,