第2章: 熱伝導方程式と拡散現象

Heat Equation and Diffusion

2.1 熱伝導方程式の導出

フーリエの熱伝導法則から拡散方程式を導出します。

📐 理論

熱伝導方程式(拡散方程式):

$$\frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}$$

ここで $u(x,t)$ は温度、$\alpha = k/(\rho c_p)$ は熱拡散率

$k$: 熱伝導率、$\rho$: 密度、$c_p$: 比熱

💻 コード例 1: 熱伝導方程式の数値解(陽的スキーム)
import numpy as np import matplotlib.pyplot as plt # パラメータ L = 10.0 # 棒の長さ alpha = 0.1 # 熱拡散率 T_total = 10.0 Nx = 100 Nt = 2000 x = np.linspace(0, L, Nx) t = np.linspace(0, T_total, Nt) dx = x[1] - x[0] dt = t[1] - t[0] # 安定性条件の確認 r = alpha * dt / dx**2 print(f"安定性パラメータ r = {r:.4f} (安定条件: r ≤ 0.5)") # 初期条件: ステップ関数 u = np.zeros((Nt, Nx)) u[0, Nx//4:3*Nx//4] = 100.0 # 境界条件: u(0,t) = u(L,t) = 0 # 陽的スキーム(FTCS: Forward Time Central Space) for n in range(Nt-1): for i in range(1, Nx-1): u[n+1, i] = u[n, i] + r * (u[n, i+1] - 2*u[n, i] + u[n, i-1]) u[n+1, 0] = 0 u[n+1, -1] = 0 # 可視化 fig, axes = plt.subplots(2, 2, figsize=(14, 10)) axes = axes.flatten() times_idx = [0, Nt//10, Nt//3, Nt-1] for idx, n in enumerate(times_idx): ax = axes[idx] ax.plot(x, u[n], 'b-', linewidth=2) ax.axhline(0, color='gray', linewidth=0.5) ax.grid(True, alpha=0.3) ax.set_xlabel('位置 x', fontsize=12) ax.set_ylabel('温度 u(x,t)', fontsize=12) ax.set_title(f't = {t[n]:.2f}', fontsize=12) ax.set_ylim(-10, 110) plt.suptitle('熱伝導方程式の数値解(陽的スキーム)', fontsize=14) plt.tight_layout() plt.show() # 時空間プロット plt.figure(figsize=(12, 6)) plt.contourf(x, t, u, levels=50, cmap='hot') plt.colorbar(label='温度 u(x,t)') plt.xlabel('位置 x', fontsize=12) plt.ylabel('時間 t', fontsize=12) plt.title('熱拡散の時空間図', fontsize=14) plt.tight_layout() plt.show() print("\n熱伝導方程式の性質:") print("- 熱は高温から低温へ拡散") print("- 時間とともに温度分布は滑らかになる") print("- エントロピーは増大(不可逆過程)")

2.2 基本解(ガウス核)

無限領域での熱伝導方程式の基本解を学びます。

📐 理論

基本解(ガウス核):

$$G(x,t) = \frac{1}{\sqrt{4\pi\alpha t}} e^{-x^2/(4\alpha t)}$$

畳み込みによる一般解:

$$u(x,t) = \int_{-\infty}^{\infty} G(x-\xi, t) f(\xi) d\xi$$
💻 コード例 2: 基本解(ガウス核)の可視化
import numpy as np import matplotlib.pyplot as plt # パラメータ alpha = 0.1 x = np.linspace(-10, 10, 500) # 基本解 def fundamental_solution(x, t, alpha): if t <= 0: return np.zeros_like(x) return (1/np.sqrt(4*np.pi*alpha*t)) * np.exp(-x**2 / (4*alpha*t)) # 時間発展 t_values = [0.1, 0.5, 1.0, 2.0, 5.0] fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # 基本解の時間発展 for t in t_values: G = fundamental_solution(x, t, alpha) ax1.plot(x, G, linewidth=2, label=f't = {t}') ax1.grid(True, alpha=0.3) ax1.set_xlabel('位置 x', fontsize=12) ax1.set_ylabel('G(x,t)', fontsize=12) ax1.set_title('基本解(ガウス核)の時間発展', fontsize=14) ax1.legend() # 畳み込みによる一般解 # 初期条件: ステップ関数 def initial_condition(x): return np.where(np.abs(x) < 1, 1.0, 0.0) # 畳み込み def convolution_solution(x, t, alpha, f): xi = np.linspace(-20, 20, 1000) dxi = xi[1] - xi[0] u = np.zeros_like(x) for i, x_val in enumerate(x): G = fundamental_solution(x_val - xi, t, alpha) integrand = G * f(xi) u[i] = np.trapz(integrand, dx=dxi) return u # 異なる時刻での解 for t in [0.5, 1.0, 2.0, 5.0]: u = convolution_solution(x, t, alpha, initial_condition) ax2.plot(x, u, linewidth=2, label=f't = {t}') # 初期条件 ax2.plot(x, initial_condition(x), 'k--', linewidth=2, alpha=0.5, label='初期条件') ax2.grid(True, alpha=0.3) ax2.set_xlabel('位置 x', fontsize=12) ax2.set_ylabel('温度 u(x,t)', fontsize=12) ax2.set_title('畳み込みによる一般解', fontsize=14) ax2.legend() plt.tight_layout() plt.show() print("=== 基本解の性質 ===") print("- 質量保存: ∫G(x,t)dx = 1") print("- 時間とともに広がる(拡散)") print("- t→0 でデルタ関数に近づく")

2.3 変数分離法による境界値問題

有限領域での熱伝導方程式を変数分離法で解きます。

💻 コード例 3: 変数分離解とフーリエ級数
import numpy as np import matplotlib.pyplot as plt # パラメータ L = 10.0 alpha = 0.1 n_modes = 20 x = np.linspace(0, L, 200) # 初期条件 def initial_temp(x): return 100 * np.sin(np.pi * x / L) + 50 * np.sin(2 * np.pi * x / L) # フーリエ係数 def compute_fourier_coeff(f, L, n_max): coeffs = [] for n in range(1, n_max+1): x_int = np.linspace(0, L, 1000) integrand = f(x_int) * np.sin(n * np.pi * x_int / L) c_n = (2/L) * np.trapz(integrand, x_int) coeffs.append(c_n) return coeffs c_n = compute_fourier_coeff(initial_temp, L, n_modes) print(f"フーリエ係数 c_n: {c_n[:5]}") # 変数分離解 def separation_solution(x, t, L, alpha, c_n): u = np.zeros_like(x) for n, c in enumerate(c_n, 1): lambda_n = (n * np.pi / L)**2 u += c * np.sin(n * np.pi * x / L) * np.exp(-alpha * lambda_n * t) return u # 可視化 fig, axes = plt.subplots(2, 3, figsize=(15, 10)) axes = axes.flatten() times = [0, 1, 2, 5, 10, 20] for idx, t in enumerate(times): ax = axes[idx] u = separation_solution(x, t, L, alpha, c_n) ax.plot(x, u, 'b-', linewidth=2) ax.axhline(0, color='gray', linewidth=0.5) ax.grid(True, alpha=0.3) ax.set_xlabel('位置 x', fontsize=11) ax.set_ylabel('温度 u(x,t)', fontsize=11) ax.set_title(f't = {t:.1f}', fontsize=12) ax.set_ylim(-200, 200) plt.suptitle('変数分離解: フーリエ級数展開', fontsize=14) plt.tight_layout() plt.show() # 各モードの減衰 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # モードの時間減衰 t_range = np.linspace(0, 20, 200) for n in range(1, 6): lambda_n = (n * np.pi / L)**2 amplitude = np.exp(-alpha * lambda_n * t_range) ax1.plot(t_range, amplitude, linewidth=2, label=f'Mode {n}') ax1.grid(True, alpha=0.3) ax1.set_xlabel('時間 t', fontsize=12) ax1.set_ylabel('振幅(規格化)', fontsize=12) ax1.set_title('各モードの時間減衰', fontsize=14) ax1.legend() # 減衰率 modes = np.arange(1, 11) decay_rates = alpha * (modes * np.pi / L)**2 ax2.plot(modes, decay_rates, 'bo-', linewidth=2, markersize=8) ax2.grid(True, alpha=0.3) ax2.set_xlabel('モード番号 n', fontsize=12) ax2.set_ylabel('減衰率 $\\alpha\\lambda_n$', fontsize=12) ax2.set_title('減衰率とモード番号の関係', fontsize=14) plt.tight_layout() plt.show() print("\n変数分離解の特徴:") print("- 高次モードほど速く減衰") print("- 長時間後は基本モード(n=1)が支配的") print(f"- 基本モードの減衰時定数: τ = 1/(αλ₁) = {1/(alpha*(np.pi/L)**2):.2f}")

2.4 境界条件の種類

Dirichlet、Neumann、Robin境界条件を学びます。

📐 理論

境界条件の種類:

  • Dirichlet: $u(0,t) = T_0$ (温度指定)
  • Neumann: $\frac{\partial u}{\partial x}(0,t) = q_0$ (熱流束指定)
  • Robin: $\frac{\partial u}{\partial x} + hu = 0$ (対流境界)
💻 コード例 4: 異なる境界条件の比較
import numpy as np import matplotlib.pyplot as plt # パラメータ L = 10.0 alpha = 0.1 T_total = 15.0 Nx = 100 Nt = 1500 x = np.linspace(0, L, Nx) dx = x[1] - x[0] dt = T_total / Nt r = alpha * dt / dx**2 # 初期条件 def init_cond(x): return 100 * np.exp(-((x - L/2)/2)**2) # 3種類の境界条件でシミュレーション cases = { 'Dirichlet (u=0)': 'dirichlet', 'Neumann (∂u/∂x=0)': 'neumann', 'Robin (対流)': 'robin' } solutions = {} for name, bc_type in cases.items(): u = np.zeros((Nt, Nx)) u[0] = init_cond(x) for n in range(Nt-1): for i in range(1, Nx-1): u[n+1, i] = u[n, i] + r * (u[n, i+1] - 2*u[n, i] + u[n, i-1]) # 境界条件 if bc_type == 'dirichlet': # 両端で温度固定 u[n+1, 0] = 0 u[n+1, -1] = 0 elif bc_type == 'neumann': # 両端で断熱(∂u/∂x = 0) u[n+1, 0] = u[n+1, 1] u[n+1, -1] = u[n+1, -2] elif bc_type == 'robin': # 対流境界条件(簡易) h = 0.5 # 対流係数 u[n+1, 0] = (u[n+1, 1] + h*dx*0) / (1 + h*dx) u[n+1, -1] = (u[n+1, -2] + h*dx*0) / (1 + h*dx) solutions[name] = u # 可視化 fig, axes = plt.subplots(2, 3, figsize=(16, 10)) times_idx = [0, Nt//5, 2*Nt//5, 3*Nt//5, 4*Nt//5, Nt-1] t = np.linspace(0, T_total, Nt) for idx, n in enumerate(times_idx): ax = axes[idx//3, idx%3] for name, u in solutions.items(): ax.plot(x, u[n], linewidth=2, label=name, alpha=0.8) ax.axhline(0, color='gray', linewidth=0.5) ax.grid(True, alpha=0.3) ax.set_xlabel('位置 x', fontsize=11) ax.set_ylabel('温度 u(x,t)', fontsize=11) ax.set_title(f't = {t[n]:.2f}', fontsize=12) ax.set_ylim(-10, 110) if idx == 0: ax.legend() plt.suptitle('境界条件の種類による温度分布の違い', fontsize=14) plt.tight_layout() plt.show() print("=== 境界条件の物理的意味 ===") print("Dirichlet: 一定温度に保たれた境界(例: 氷水浴)") print("Neumann: 断熱境界(例: 断熱材)") print("Robin: 対流境界(例: 空気中への放熱)")

📝 章末問題

演習問題
  1. 熱拡散率 $\alpha = 0.2$ cm²/s、長さ $L=10$ cm の棒で、基本モードの減衰時定数を求めよ。
  2. Neumann境界条件で全体が断熱されている場合、平衡状態での温度分布を求めよ。
  3. 2次元正方形領域で、1つの辺のみが高温(他は低温)の場合の定常状態を求めよ。
  4. Crank-Nicolson法で $r=2$ の場合の数値解を陽的法と比較せよ。