Rで微分方程式:1階偏微分方程式

Rで 微分方程式:1階偏微分方程式 を試みます。

本ポストはこちらの続きです。

Rで微分方程式:2階常微分方程式
Rで 微分方程式:2階常微分方程式 を試みます。本ポストはこちらの続きです。1. 2階常微分方程式の概要1.1 2階常微分方程式とは?1階が「変化の速さ(速度)」を記述する方程式だったのに対し、2階は「変化の速さの変化の速さ(加速度)」を記...

1. 1階偏微分方程式の概要

1.1 常微分方程式(ODE)と偏微分方程式(PDE)の違い

これまで扱ってきた常微分方程式(ODE)と偏微分方程式(PDE)の最も根本的な違いは、「独立変数の数」です。

  • 常微分方程式 (ODE): 独立変数が1つ

    • 例: y(t) のように、時間 t だけに依存する関数を扱う。
    • 「時間の経過と共に、ある1つの値(人口、おもりの位置など)がどう変わるか」を記述する。
  • 偏微分方程式 (PDE): 独立変数が2つ以上

    • 例: u(t, x) のように、時間 t 空間 x の両方に依存する関数を扱う。
    • 「時間と共に、空間的な分布(川の汚染物質の濃度分布、弦の振動の形など)がどう変わるか」を記述する。

独立変数が複数あるため、微分を考えるときには「どの変数に注目して微分するか」を区別する必要があります。この特定の変数だけに注目した微分を「偏微分」と呼び、記号 (パーシャル、デル、ラウンドディーなどと読む)を使って ∂u/∂t のように書きます。

1.2 具体例:1階偏微分方程式「移流方程式」

最もシンプルで代表的な1階偏微分方程式が「移流(いりゅう)方程式」です。これは、ある性質(濃度、温度など)の分布が、形を変えずに一定の速度で移動していく様子を記述します。

∂u/∂t + c * ∂u/∂x = 0

  • u(t, x): 時刻 t、位置 x における物理量(例:濃度)
  • c: 移流速度(波が移動する速さ)
  • ∂u/∂t: ある一点 x に固定したときの、u の時間的な変化率。
  • ∂u/∂x: ある時刻 t に固定したときの、u の空間的な勾配(傾き)。

この式を変形すると ∂u/∂t = -c * ∂u/∂x となります。これは「ある点での時間変化は、その点の空間的な勾配に比例する」ことを意味します。例えば、濃度分布が山のような形をしている場合、山の右斜面(∂u/∂x > 0)では ∂u/∂t < 0 となり濃度が減少します。逆に左斜面(∂u/∂x < 0)では ∂u/∂t > 0 となり濃度が増加します。結果として、山全体が形を保ったまま右方向へ移動していくことになります。

1.3 初期値・境界値問題と数値解法

PDEを解くためには、ODEで必要だった初期値に加え、空間的な端の振る舞いを決める「境界条件」が必要になります。

  • 初期条件: u(0, x) の形。シミュレーション開始時の濃度分布など。
  • 境界条件: 空間領域の端(例: x=0x=L)で u がどう振る舞うか。例えば「端から流れ込む濃度は常に0」や「端から出ていったものが反対側から入ってくる(周期的)」など。

PDEの数値解法では、時間と空間の両方を微小な区間(Δt, Δx)で区切った格子(グリッド)を考え、各格子点での u の値を差分近似で計算していきます。今回は「風上差分法」というシンプルな手法を用います。


2. シミュレーションのためのRコード

それでは、Rで移流方程式のシミュレーションを行います。ここでは、ある初期分布(山のような形)が時間と共に移動していく様子をアニメーションで可視化します。

2.1 準備:必要なパッケージの読み込み

# パッケージの読み込み
library(ggplot2)
library(dplyr)
library(gganimate)
library(transformr)

2.2 シミュレーションの実行(差分法による実装)

今回は deSolve のようなソルバーは使わず、偏微分方程式の数値計算の基本である差分法を直接forループで実装します。

Step 1: パラメータと初期条件の設定
# --- シミュレーションパラメータ ---
L <- 10.0 # 空間領域の長さ (0 <= x <= L)
Nx <- 200 # 空間の分割数
dx <- L / Nx # 空間ステップ幅
c_speed <- 1.0 # 移流速度
T_end <- 8.0 # シミュレーション終了時間
CFL <- 0.5 # クーラン数(安定性のためのパラメータ)
dt <- CFL * dx / c_speed # 時間ステップ幅

# --- 初期条件とグリッドの設定 ---
# 空間グリッド (x座標)
x <- seq(0, L, by = dx)

# 初期分布 u(t=0, x) をガウス分布(山形)で作成
u_initial <- exp(-(x - L / 4)^2 / 0.5)
u <- u_initial # uを現在の状態として保持

# 時間ステップの数
nsteps <- round(T_end / dt)

# 結果を保存するためのデータフレームを準備
results_df <- data.frame()

# シミュレーションの条件を表示
cat("--- シミュレーション条件 ---\n\n")
cat("モデル: 1次元移流方程式(1階偏微分方程式)\n")
cat(sprintf("移流速度: c = %.2f\n", c_speed))
cat(sprintf("空間: 0~%.1f, 時間: 0~%.1f\n", L, T_end))
cat("境界条件: 周期境界条件\n")
cat("--------------------------\n")
--- シミュレーション条件 ---

モデル: 1次元移流方程式(1階偏微分方程式)
移流速度: c = 1.00
空間: 0~10.0, 時間: 0~8.0
境界条件: 周期境界条件
--------------------------
Step 2: シミュレーションループの実行(風上差分法)
for (n in 0:nsteps) {
  # 現在のステップの結果をデータフレームに追加
  current_step_df <- data.frame(time = n * dt, x = x, u = u)
  results_df <- rbind(results_df, current_step_df)

  # uの値を次の時間ステップのためにコピー
  u_old <- u

  # 空間方向のループ(各点で次の時間ステップの値を計算)
  for (i in 2:Nx) {
    # 風上差分法による更新式
    # c > 0 なので、風上は左側(i-1)
    u[i] <- u_old[i] - (c_speed * dt / dx) * (u_old[i] - u_old[i - 1])
  }

  # 周期境界条件の適用
  # 左端(i=1)の更新
  u[1] <- u_old[1] - (c_speed * dt / dx) * (u_old[1] - u_old[Nx])

  # 右端(i=Nx+1)の値を左端と同じにする
  u[Nx + 1] <- u[1]
}
Step 3: 結果の可視化 (gganimate)

結果をアニメーションGIFとして描画・保存します。

# ggplotでアニメーションを作成
p_anim <- ggplot(results_df, aes(x = x, y = u, group = 1)) +
  geom_line(color = "tomato", linewidth = 1.2) +
  scale_x_continuous(breaks = seq(0, L, by = 2)) +
  labs(
    title = "移流方程式のシミュレーション",
    subtitle = "時刻: {round(frame_time, 2)}",
    x = "位置 (x)",
    y = "濃度 (u)",
    caption = "風上差分法による数値シミュレーション結果"
  ) +
  ylim(0, 1.2) + # y軸の範囲を固定
  theme_minimal() +
  theme(
    plot.title = element_text(face = "bold", size = 28),
    plot.subtitle = element_text(size = 26),
    axis.title = element_text(size = 26),
    plot.caption = element_text(size = 26),
    axis.text.x = element_text(size = 26, color = "black"),
    axis.text.y = element_text(size = 26, color = "black")
  ) +
  # gganimateの設定
  transition_time(time) +
  ease_aes("linear")

# アニメーションをレンダリングして表示・保存
# nframesを調整するとアニメーションの長さを変更できます
animate(p_anim, nframes = 200, fps = 20, width = 1344, height = 960, renderer = gifski_renderer("advection_simulation.gif"))
Figure 1

Figure 1 から、以下の点が視覚的に理解できます。

  • 移流: 初期の山形の分布が、その形を(少し崩しながらも)保ちつつ、一定の速度で右に移動しています。
  • 周期境界条件: 右端 x=10 から消えた波が、左端 x=0 から再び現れています。これは、空間がリング状につながっていると仮定した「周期境界条件」の振る舞いです。
  • 数値拡散: 時間が経つにつれて波のピークが少し低くなり、裾野が広がっています。これは、風上差分法というシンプルな数値解法が持つ特性で、「数値拡散」と呼ばれます。

以上です。