LoginSignup
0
2

偏微分方程式を数値計算で解いた(自分用メモ)

Posted at

タイトルそのままです.偏微分方程式をPythonで解きました.

問題

円柱座標系で,$z$軸に印加された磁場$B_z$中に静止した無限長の半径$a$,抵抗率$\eta$の円柱プラズマを考える.このプラズマに$z$軸方向の電流$I_z$を流す時,方位角方向の磁場$B_\theta$が発生する.プラズマに浸透する$B_\theta$と電流密度$J_z$の時空間発展を求める.
(金属に電流をいきなり流した時の過渡応答と同じになるっぽい.いわゆる表皮効果)

解きたい微分方程式

〜〜導出略〜〜

$$
\frac{\partial B_\theta}{\partial t} = \frac{\eta}{\mu_0}\frac{\partial}{\partial r}\left(\frac{1}{r} \frac{\partial (rB_\theta)}{\partial r}\right)
$$
$\mu_0$は真空の透磁率.また電流密度$J_z$は$\nabla \times B=\mu_0 J_z$より,
$$
J_z = \frac{1}{\mu_0r}\frac{\partial}{\partial r}(rB_\theta)
$$
$r>a$では$J_z=0$なので$B_\theta(r)=\mu_0I_z/2\pi r$.また,定常状態($\partial/\partial t=0$)で,左辺を解くと,$B_\theta(r)=(\mu_0I_z/2\pi a^2)r$になる($r=a$で$B_\theta(r)=\mu_0I_z/2\pi a$).$r$を$a$,$t$を$a^2\mu_0/\eta$,$B_\theta(r)$を$B_\theta(a)$,$J_\theta$を$I_z/\pi a^2$でそれぞれ規格化して整理すると,

\frac{\partial B_\theta}{\partial t} = \frac{\partial}{\partial r}\left(\frac{1}{r} \frac{\partial (rB_\theta)}{\partial r}\right)\\
= \left\{\frac{\partial^2}{\partial r^2} + \frac{1}{r} \frac{\partial}{\partial r} -\frac{1}{r^2} \right\}B_\theta

$$
J_z = \frac{1}{2}\frac{\partial}{\partial r}(rB_\theta)
$$

これを数値計算で解く.

離散化

以下,添字が煩雑なので$B_\theta$を$B$と書きます.時刻$t^j$,位置$r_i$における値を$B_i^j$,時間と距離の幅をそれぞれ$\Delta t$, $\Delta r$とする.
左辺について,
$$
(\text{左辺})=\frac{B_i^{j+1}-B_i^{j}}{\Delta t}
$$

右辺について
1階中心差分商と,
$$
\frac{\partial B}{\partial r} \approx \frac{B_{i+1}^j-B_{i+1}^j}{2\Delta r}
$$
2階中心差分商
$$
\frac{\partial B}{\partial r} \approx \frac{B_{i+1}^j-2B_{i}^j+B_{i-1}^j}{\Delta r^2}
$$
を使って書き下すと,
$$
(\text{右辺})=\frac{B_{i+1}^j-2B_{i}^j+B_{i-1}^j}{\Delta r^2}
+ \frac{1}{r_i}\frac{B_{i+1}^j
- B_{i+1}^j}{2\Delta r}-\frac{1}{r_i^2}B_i^j\
= \left(\frac{1}{\Delta r^2}-\frac{1}{2\Delta r}\right)B_{i-1}^j
- \left(-\frac{2}{\Delta r^2}+\frac{1}{r_i^2}\right)B_{i}^j
+ \left(\frac{1}{\Delta r^2}+\frac{1}{2\Delta r}\right)B_{i+1}^j\
=a_iB_{i-1}^j - b_iB_{i}^j + c_iB_{i+1}^j
$$
ここで$a_iB_{i-1}^j - b_iB_{i}^j + c_iB_{i+1}^j$を$\delta B_i^j$と定義する.

ここで,左辺と右辺の添字$j$の取り方で前進差分と後進差分がある.

前進差分

$j$を$\left(B_i^{j+1}-B_i^{j}\right)/\Delta t = \delta B_i^j$ととる方法で,時刻$j$の値から$j+1$を計算する(陽解法).$B_i^{j}$を移項すると漸化式になるので,計算が簡単.
しかし,$\Delta r$に対して$\Delta t$を細かくとらないと数値的に不安定になる.

後進差分

$j$を$\left(B_i^{j+1}-B_i^{j}\right)/\Delta t = \delta B_i^{j+1}$ととる方法.時刻$j$の値を満たすような$j+1$での値を計算する(陰解法).連立方程式を解く必要があり,ちょっとしんどい.

θ法

前進差分と後進差分のブレンド.
$$
\left(B_i^{j+1}-B_i^{j}\right)/\Delta t = (1-\theta)\delta B_i^j + \theta\delta B_i^{j+1}
$$
時間方向に2次精度になるらしい.参考

コード

差分方程式解く部分

import numpy as np
from tqdm import tqdm

def theta_method(n=10, m=100, theta=0.0, t_max=1.0):
    n = int(n)
    m = int(m)
    r, h = np.linspace(0.0001, 1, n + 1, retstep=True)
    t, tau = np.linspace(0, t_max, m + 1, retstep=True)

    a = 1 / h ** 2 - 1 / (2 * r * h)
    b = 2 / h ** 2 + 1 / r ** 2
    c = 1 / h ** 2 + 1 / (2 * r * h)

    B_theta = np.zeros((n + 1, m + 1))
    w = np.zeros(n + 1)
    A = np.zeros((n + 1, n + 1))
    A[0, 0] = 1
    A[n, n] = 1

    for i in range(1, n):
        A[i, i - 1] = -tau * theta * a[i]
        A[i, i] = 1 + tau * theta * b[i]
        A[i, i + 1] = -tau * theta * c[i]

    B_theta[:, 0] = np.zeros_like(r)

    for j in tqdm(range(m)):
        w[0] = 0
        w[n] = 1
        w[1:n] = (1 - theta) * tau * a[1:n] * B_theta[:-2, j] \
                 + (1 - (1 - theta) * tau * b[1:n]) * B_theta[1:n, j] \
                 + (1 - theta) * tau * c[1:n] * B_theta[2:, j]
        v = np.linalg.solve(A, w)
        for i in range(n + 1):
            B_theta[i, j + 1] = v[i]

    return r, h, t, tau, B_theta, np.apply_along_axis(lambda B: calc_J_z(B, r), 0, B_theta)

def calc_J_z(B_theta, r):
    return B_theta / r + np.gradient(B_theta, r)

描画

if __name__ == '__main__':
    while True:
        cmd = input("quit(q) or [enter]>>")
        if cmd == "q":
            break
        else:
            # t_list = np.array(list(map(float, input("t>>").split(","))))
            t_list = np.array([0.0001, 0.001, 0.01, 0.1, 0.2, 0.5, 1])
            n = int(input("n>>"))
            m = int(input("m>>"))
            theta = float(input("theta>>"))

            cmd = input(f"lambda={n ** 2 * t_list.max() / m:.2} continue?(Y/n)>>")
            if cmd == "n":
                continue

            r, h, t, tau, B_theta, J_z = theta_method(n=n, m=m, theta=theta, t_max=t_list.max())
            ind = np.round(t_list / tau).astype(int)
            B_theta_list = B_theta.T[ind]
            J_z_list = J_z.T[ind]
            B_theta_inf = r
            J_z_inf = calc_J_z(r, r)

            fig, axes = plt.subplots(1, 3, figsize=(12, 6), sharex=True)
            fig.suptitle(rf"$n={n}$, $m={m}$, $\lambda={tau / h ** 2:.2}$, $\theta={theta:.2}$")
            axes[0].set_title(r"$B_\theta$")
            axes[0].set_ylabel(r"$B_\theta$")
            axes[0].set_ylim(0.0, 1.1)

            axes[1].set_title(r"$B_\theta$(steady)$-B_\theta$")
            axes[1].set_ylabel(r"$B_\theta$")
            axes[1].set_ylim(0.0, 1.1)

            axes[2].set_title(r"$J_z$")
            axes[2].set_ylabel(r"$J_z$")
            axes[2].set_ylim(0., 5)

            axes[0].set_xlim(0.0, 1.1)
            axes[0].plot(r, B_theta_inf, ls="--", c="k", label="steady")
            axes[1].plot(r, B_theta_inf, ls="--", c="k", label="steady")
            axes[2].plot(r, J_z_inf, ls="--", c="k", label="steady")

            for B, J, t_ in zip(B_theta_list, J_z_list, t_list):
                axes[0].plot(r, B, label=f"t={t_:.2}")
                axes[1].plot(r, B_theta_inf - B, label=f"t={t_:.2}")
                axes[2].plot(r, J, label=f"t={t_:.2}")

            for ax in axes:
                ax.legend()
                ax.set_xlabel(r"$r$")
                ylim = ax.get_ylim()
                ax.vlines(1, ylim[0], ylim[1], ls="-", colors="k", lw=1)
                ax.set_ylim(0, ylim[1])

            fig.tight_layout()
            fig.savefig(f"B_theta_penetration_{n}_{m}_{theta}.png", bbox_inches="tight", pad_inches=0.05, dpi=200)
            plt.show()

            # animation
            fig, axes = plt.subplots(1, 3, figsize=(12, 6), sharex=True)

            # pick up 100 times from t_list in log scale
            t_list_animation = np.unique(np.logspace(0, np.log10(m), MAX_FRAMES, dtype=int))
            frame_number = len(t_list_animation)
            skip = m // frame_number


            def update(frame):
                j = t_list_animation[frame]
                axes[0].cla()
                axes[1].cla()
                axes[2].cla()

                axes[0].set_title(rf"$B_\theta$ ($t={t[j]:.2}$)")
                axes[0].set_ylabel(r"$B_\theta$")
                axes[0].set_ylim(0.0, 1.1)

                axes[1].set_title(rf"$B_\theta$(steady)$-B_\theta$ ($t={t[j]:.2}$)")
                axes[1].set_ylabel(r"$B_\theta$")
                axes[1].set_ylim(0.0, 1.1)

                axes[2].set_title(rf"$J_z$ ($t={t[j]:.2}$)")
                axes[2].set_ylabel(r"$J_z$")
                axes[2].set_ylim(0., 5)

                axes[0].plot(r, B_theta.T[j])
                axes[1].plot(r, B_theta_inf - B_theta.T[j])
                axes[2].plot(r, J_z.T[j])

                axes[0].set_xlim(0.0, 1.1)
                axes[0].plot(r, B_theta_inf, ls="--", c="k", label="steady")
                axes[1].plot(r, B_theta_inf, ls="--", c="k", label="steady")
                axes[2].plot(r, J_z_inf, ls="--", c="k", label="steady")

                for ax in axes:
                    ax.legend()
                    ax.set_xlabel(r"$r$")
                    ylim = ax.get_ylim()
                    ax.vlines(1, ylim[0], ylim[1], ls="--", colors="k")
                    ax.set_ylim(0, ylim[1])

            print("making animation...")
            anim = FuncAnimation(fig, update, frames=range(frame_number), interval=200)
            print("saving animation...")
            anim.save(f"B_theta_penetration_{n}_{m}_{theta}.gif", writer="pillow", fps=10)
            print("done.")

結果

  1. $n=100$,$m=10000$,$\theta=0$(つまり前進差分)
    解が不安定になっている.

B_theta_penetration_100_10000_0.0.png

  1. $n=100$,$m=50000$,$\theta=0$(タイムステップを細かくした)
    いい感じです.

B_theta_penetration_100_50000_0.0.png

  1. $n=100$,$m=10000$,$\theta=1$(つまり後進差分)
    前進差分では発散していたが,後進差分では安定している.

B_theta_penetration_100_10000_1.0.png

  1. $n=100$,$m=10000$,$\theta=0.5$
    2, 3と同様の結果が得られた.精度はこれが一番いいらしい.
    ($t$に関する1次の誤差がキャンセルされるから?)

B_theta_penetration_100_10000_0.5.png

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2