こんにちは(@t_kun_kamakiri)。
今回は、「1次元の移流方程式」をPythonで実装するというのやります。
基本的な内容はこちらのサイトにそってやっていきます。
この記事ではこんな人を対象にしています。
- Pythonを使い始めたけどどう使うかわからない
- 流体の数値計算をはじめて勉強する人
☟前回の記事がまだの人はこちらから
1次元移流方程式の差分法で離散化してPythonで解く!
1次元移流方程式とは
移流方程式について簡単な解説をしておこうと思います。
\frac{\partial u}{\partial t}+c\frac{\partial u}{\partial x}=0\tag{1}
\end{align*}
\Delta u =u(x+dx,t+dt)-u(x,t)\tag{2}
\end{align*}
\Delta u &=u(x+c\Delta t,t+\Delta t)-u(x,t)\\
&=u(x+c\Delta t,t+\Delta t)-u(t, x+c\Delta t)\\
&+u(x+c\Delta t,t)-u(x,t)\\
&=\frac{u(x+c\Delta t,t+\Delta t)-u(x+c\Delta t,t)}{\Delta t}\Delta t\\
&+\frac{u(x+c\Delta t,t)-u(x,t)}{\Delta x}\Delta x\\
&=\frac{\partial u}{\partial t}\Delta t+c\frac{\partial u}{\partial t}\Delta t
\end{align*}
ここから、移流方程式の(1)は、最後に両辺\(\Delta t\)で割り\(\frac{\Delta u}{\Delta t} =0\)となる方程式であることがわかります。
u(t,x)=u(x-ct)
\end{align*}
移流方程式を離散化する
偏微分方程式をパソコンで解くには微分項を離散化して代数的に取り扱う必要があります。
- 時間微分の項(第一項)
- 空間微分の項(第二項)
をどのように扱うのかが問題です。
時間微分の項(第一項)
\frac{\partial u}{\partial t} \approx \frac{u(t+\Delta t)-u(t)}{\Delta t}\tag{3}
\end{align*}
空間微分の項(第二項)
\frac{\partial u}{\partial x} \approx \frac{u(x)-u(x-\Delta x)}{\Delta x}\tag{4}
\end{align*}
\frac{u^{n+1}_{i}-u^{n}_{i}}{\Delta t}+c\frac{u^{n}_{i}-u^{n}_{i-1}}{\Delta x}=0\tag{5}
\end{align*}
※もちろんあるnステップでのある場所(i番目の格子点)ではなく、計算領域の全ての格子点での値を知りたいってことです。
u^{n+1}_{i}=u^{n}_{i}-\frac{c\Delta t}{\Delta x}\big(u^{n}_{i}-u^{n}_{i-1}\big)\tag{6}
\end{align*}
Pythonを使って移流方程式の数値計算
それでは数値計算に移ります。
Pythonのライブラリをインポート
まずは、ここで必要なPythonのライブラリをインポートします。
【必要なライブラリ】
- Numpy:数値計算用のライブラリ
- Matplotlib:波の形状を2次元プロットして可視化するためのライブラリ
Pythonのコードを書きます。
1 2 |
import numpy as np import matplotlib.pyplot as plt |
実行してエラーがなければ記述に問題はないでしょう。
※Google Colaboratoryには標準で「NUmpy」と「Matplotlib」が搭載されているので特にエラーなく使えます。
初期条件を設定
次に「初期状態の設定」を行います。
数値計算をする際に特に単位は決まっていませんが、流体解析の場合はSI単位系(m-sec-kg)としておくのが良いかと思います。
【初期設定】
- 時間刻みは0.25[s]
- 速度c=1[m/s]
空間\(x\)は「0~2(m)」を41分割します。
Pythonのコードを書きます。
1 2 3 4 5 6 7 8 9 10 11 12 |
nx = 41 dx = 2 / (nx-1) nt = 25 dt = .025 c = 1 x = np.linspace(0,2,nx) print('dx=',dx) print('x=',x) print(len(x)) print(type(x)) |
【結果】
1 2 3 4 5 6 |
dx= 0.05 x= [0. 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65 0.7 0.75 0.8 0.85 0.9 0.95 1. 1.05 1.1 1.15 1.2 1.25 1.3 1.35 1.4 1.45 1.5 1.55 1.6 1.65 1.7 1.75 1.8 1.85 1.9 1.95 2. ] 41 <class 'numpy.ndarray'> |
今回は確認のために「dxの値」「xの要素」「xの要素の数」「xの型」を出力しておきました。
np.ones(数)
次に、\(u\)の初期状態を決めておきまます。
Pythonのコードを書きます。
1 2 3 4 5 |
u = np.ones(nx) print(u) print(len(u)) print(type(u)) |
【結果】
1 2 3 4 |
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] 41 <class 'numpy.ndarray'> |
初期状態を「u」という変数で定義しています。
途中で結果を出力しながら自分が思っている設定になっているかを確認すると良いです。
これで要素の数がnx個で、値は全て1となりました。
次に、nx=41の要素のうち「10~21」の要素の値を「2」とします。
Pythonのコードを書きます。
1 2 3 |
u[int(.5 / dx):int(1 / dx + 1)] = 2 print(u) |
【結果】
1 2 |
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] |
これで\(u\)の初期状態ができました。
値だけを見ていてもわかりにくいので、Matplotlibで形状を確認してみましょう。
1 2 3 4 |
plt.plot(x,u) plt.xlabel('x') plt.ylabel('u') plt.grid(alpha = 0.3) |
【結果】
思っている形に初期状態ができています。
形を確認するだけならMatplotlibで「plt.plot(x,un)」だけで良いですが、ラベルとかグリッドを書いた方が見た目が良いのでお好みで設定できます。
移流方程式を解く
初期状態が設定できたのいよいよ移流方程式の数値計算を行います。
今回は時々刻々と変化する形状のアニメーション作成はせずに、最終的な形状だけをプロットしてみます。
【移流方程式を解く】
- un = u.copy()で初期状態をコピーします。
- u[i] = un[i] – c * dt / dx * (un[i] – un[i-1])とすることで(6)式、
\begin{align*}を解いています。
u^{n+1}_{i}=u^{n}_{i}-\frac{c\Delta t}{\Delta x}\big(u^{n}_{i}-u^{n}_{i-1}\big)\tag{6}
\end{align*}
では、Pythonのコードを書きます。
1 2 3 4 5 6 7 8 9 |
un = [] u = np.ones(nx) u[int(.5 / dx):int(1 / dx + 1)] = 2 for n in range(nt): un = u.copy() for i in range(1, nx): u[i] = un[i] - c * dt / dx * (un[i] - un[i-1]) |
「for i in range(1,nx)」のように、空間の要素を0からではなく1からにしています。
これは、数値計算の途中で「un[i-1]」があるため「un[0]」を用意しておかないとエラーが起こってしまうため「un[0]」を初期状態で設定しておいたのです。
実際に計算で使っている空間領域は「要素の1~nx」だけということになります。
※Pythonの配列は基本が「値の参照渡し」なので、Google Colaboratoryのようにインタラクティブに計算を実行させていると値が思っていないところで上書きされていたりするので、今回は偏微分を解く部分に初期状態の設定を書いておくようにしました。
以上の数値計算を行っても何の結果も出ないと思いますので最終状態の「u」をMatplotlibで出力してみましょう。
1 |
plt.plot(x,u) |
👆これが最終状態の「u」の形状です。
ちょっとわかりにくいので、初期状態と最終状態を比較してみます。
1 2 3 4 5 6 7 8 |
u_final = u.copy() u_initial = np.ones(nx) u_initial[int(.5 / dx):int(1 / dx + 1)] = 2 plt.plot(x,u_initial,label = 'initial') plt.plot(x,u_final, label = 'final') plt.legend() |
※最終状態のuを「u_final = u.copy()」として違うアドレスを用意して参照するようにします。
※初期状態は新たに作り直しています。
【結果】
「初期状態のまま形を変えずに+x方向に伝播する解」であるのが移流方程式の厳密解なのに、思いっきり形が変わっていますね(‘ω’)
(1)式は一般解があるので、一般解に勝る最強の精度はないです。
厳密な解を得るためには数値計算の離散化を工夫しないといけないということですね・・・・
全体のコード
全体のコードを載せておきますので、コピペすることで移流方程式の数値計算ができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import numpy as np import matplotlib.pyplot as plt nx = 41 dx = 2 / (nx-1) nt = 25 dt = .025 c = 1 x = np.linspace(0,2,nx) un = [] u = np.ones(nx) u[int(.5 / dx):int(1 / dx + 1)] = 2 for n in range(nt): un = u.copy() for i in range(1, nx): u[i] = un[i] - c * dt / dx * (un[i] - un[i-1]) u_final = u.copy() u_initial = np.ones(nx) u_initial[int(.5 / dx):int(1 / dx + 1)] = 2 plt.plot(x,u_initial,label = 'initial') plt.plot(x,u_final, label = 'final') plt.legend() |
まとめ
今回は「移流方程式」を離散化して空間に対して後退差分で数値計算を行いました。
結果は、初期状態から+x方向に伝搬するのですが形が変わって伝搬するという結果となりました。
数値計算の離散化をもう少し考えないといけないです・・・・が、今回は移流方程式を解いてみようという軽いノリなのでそこまでこだわりはしません。
本シリーズのナビエストークスの数値計算を終えた後に、離散化手法を見直していこうと思います。
☟ちなみに、こちらの記事に差分の仕方で「数値計算の安定性が全然違ってくる」というのを確認しています。
Pythonの完全初心者は書籍で学ぶとよい
Python自体が不安だという方は、こちらの書籍から勉強しても良いかも知れません。
☟こちらの本がPython初心者が挫折することなく勉強できる本です。
(本記事のようのPython使用環境と異なりますが、とてもわかりやすいので全く問題ありません)
nx=41の要素のうち「10~21」の要素の値を「2」とするのを
u[int(.5 / dx):int(1 / dx + 1)] = 2で表現するのはなぜか教えていただきたいです。またfor n in range(nt): の部分はなぜnt=25を使っているかも教えていただきたいです
お読みいただきありがとうございます。
dx = 2/40 = 0.05
int(.5 / dx) = 0.5/0.05 = 10
int(1 / dx + 1)] = 1/0.05 + 1 = 20 + 1 = 21
※intをつけているのは0.5/dxなどとした場合に浮動小数点となり整数値ではないのでエラーになるためです。
リストの要素の番号は整数である必要があるため。
空間座標で言うと[0.5~1.05]までをu=2にしています。
nt=25はここではタイムステップ数なので見たい現象が見れるタイムステップをご自身で決めれば良いです。
ただし、dt = .025としているので、時間に直すとnt = 25は0.25*25で6.25秒です。
6.25秒に特に意味はありません。
見たい現象が見れるまでの最短の時間を設定しただけです。
ntを小さすぎると見たい現象を見る前に計算が終わってしまいますし、ntが大きすぎると無駄に計算時間が増えてしまいます。
よろしくお願いいたします。