以下の内容はhttps://bakkyalo.hatenablog.jp/entry/2025/06/01/224710より取得しました。


exp(iωt-ikx) が平面波ってどういうことやねん

高校の物理の教科書にはこう書いてあると思います。

波の式は

\displaystyle y = A \sin 2\pi \left( \frac{t}{T} - \frac{x}{\lambda} \right)

そして、大学の電磁気学量子力学の教科書にはこう書いてあると思います。

平面波の式は

u = \exp \left [ i ( \omega t - \boldsymbol{k} \cdot \boldsymbol{x}) \right\rbrack

はい。ぶっちゃけ分かんないっすよね。
分かんないので Python を使います。


波の式を Python で書いてみる

1 次元の波を描いてみる

Pythonmatplotlib.animation.FuncAnimation では時間変化する関数のアニメーションを作れるので、これを使ってプロットしてみます。

なお、 A 1 で固定しておきます。

\displaystyle y = A \sin 2\pi \left( \frac{t}{T} - \frac{x}{\lambda} \right)

T と λ をそれぞれ 1, 2, 3 と変えていった時の波の様子。右にいくほど λ が大きくなり、下にいくほど T が大きくなります。

Python の一例

# wave_multiple.py
# たくさんの波を描くコード
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

A = 1
T_ARRAY = np.array([1, 2, 3])
LAMBDA_ARRAY = np.array([1, 2, 3])
FPS = 20

fig, ax = plt.subplots(nrows=len(T_ARRAY), ncols=len(LAMBDA_ARRAY), layout='constrained')
x = np.linspace(-1, 5, 100)

# frame には 0 からの整数値が入る (0, 1, 2, ..., frames)
def update(frame):
    t = frame / FPS
    fig.suptitle(f't = {t:.2f}', fontsize=16)

    for T_INDEX, T in enumerate(T_ARRAY):
        for LAMBDA_INDEX, LAMBDA in enumerate(LAMBDA_ARRAY):
            ax[T_INDEX][LAMBDA_INDEX].cla()
            ax[T_INDEX][LAMBDA_INDEX].set(xlim=[-1, 5], ylim=[-1.2, 1.2], xlabel='x', ylabel='y')
            ax[T_INDEX][LAMBDA_INDEX].set_aspect('equal')
            ax[T_INDEX][LAMBDA_INDEX].grid(True, which='both', color='gray', linestyle='--', linewidth=0.5)
            ax[T_INDEX][LAMBDA_INDEX].tick_params(direction='in')
            ax[T_INDEX][LAMBDA_INDEX].axhline(y=0, color='black', linewidth=0.5)
            ax[T_INDEX][LAMBDA_INDEX].axvline(x=0, color='black', linewidth=0.5)
            ax[T_INDEX][LAMBDA_INDEX].title.set_text(f'T = {T}, λ = {LAMBDA}')

            y =  A * np.sin(2 * np.pi * (t / T - x / LAMBDA))
            y0 = A * np.sin(2 * np.pi * t / T)

            wave = ax[T_INDEX][LAMBDA_INDEX].plot(x, y)
            dot, = ax[T_INDEX][LAMBDA_INDEX].plot(0, y0, color='red', marker='o', markersize=5)

if __name__ == '__main__':

    ani = FuncAnimation(
        fig = fig,
        func = update,
        frames = np.lcm.reduce(T_ARRAY) * FPS,
        interval = 1000 / FPS
    )
    ani.save('wave_multiple.gif', writer='pillow')


上の図を眺めてみると、まず  \lambda が大きいほど (右にいくほど) 波が横長になっていることが分かります。この長さのことを 波長 (wave length) と呼びます。

次に、波長  \lambda 程は分かりにくいかもしれませんが、 T が大きくなると (下に行くほど) 赤い点 🔴 の動きがゆっくりになっているのも分かると思います。この赤い点は位置  x = 0 での波の変位 (displacement) を表していて、これが行って戻ってくるまでにかかる時間が  T です。時間がかかるということは即ちゆっくりということですね。これを 周期 (period) と呼びます。

それと何となくですが、右上の波の方が激しく、左下の方は穏やかであるようにも見えます。この激しさの正体は波が横方向に進む速さでして、位相速度 (phase velocity) と呼ばれます。
高校の物理の教科書に

 v = f\lambda = \dfrac{\lambda}{T}

というのがありましたが、これは周期  T に対して波長  \lambda が大きいほど波が速く進むと言っています。実際、  \dfrac{\lambda}{T} の値は

T\λ 1 2 3
1 1 2 3
2 1/2 1 3/2
3 1/3 2/3 1

と左下に向かうにつれて段々小さくなっています。



2 次元平面波をプロットしてみる

さて、次に 2 次元平面波をプロットしてみましょう。

平面波の式


u (t, \boldsymbol{x} ) = \exp \left [ i ( \omega t - \boldsymbol{k} \cdot \boldsymbol{x}) \right\rbrack

に出てくる  \boldsymbol{k} \cdot \boldsymbol{x} \mathbb{R} 上のベクトル空間の標準内積で、例えば 2 次元の場合は  \boldsymbol{k} = (k_1, k_2),  \boldsymbol{x} = (x_1, x_2) とすると

 \boldsymbol{k} \cdot \boldsymbol{x} = k_1 x_1 + k_2 x_2

などとなります。3 次元でも同様です。これを踏まえて、実際に 2 次元平面波

 u(t, x_1, x_2) = \exp [ i ( \omega t - k_1 x_1  - k_2 x_2 ) ]

をプロットしてみましょう。電磁気学などではその実部だけを問題視するので、仮に  \cos でプロットしてみることにします。つまり

 u(t, x, y) = \cos (\omega t - k_1 x_1 - k_2 x_2)

です。

ω を固定し、k1 と k2 をそれぞれ 1, 2, 3 と変えていった時の 2 次元平面波の様子。右にいくほど k2 が大きくなり、下にいくほど k1 が大きくなります。

Python の一例

# 2d_plane_wave_multiple.py
# たくさんの 2次元平面波を描くコード

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from mpl_toolkits.mplot3d import Axes3D

A = 1
OMEGA_ARRAY = np.array( [1, 1, 1] )
OMEGA = 1
K1_ARRAY = np.array( [1, 2, 3] )
K2_ARRAY = np.array( [1, 2, 3] )

FPS = 10
PERIOD = 2 * np.pi / OMEGA
NUM_FRAMES = int(PERIOD * FPS)

fig, ax = plt.subplots(nrows=len(K1_ARRAY), ncols=len(K2_ARRAY), figsize=(12, 9), layout='constrained', subplot_kw={'projection': '3d'})

x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)

# frame には 0 からの整数値が入る (0, 1, 2, ..., frames)
def update(frame):
    t = frame / FPS
    print(f'Now frame  {frame} / {NUM_FRAMES}')
    fig.suptitle(rf'$t = {t:.2f}$', fontsize=16)

    for K1_INDEX, K1 in enumerate(K1_ARRAY):
        for K2_INDEX, K2 in enumerate(K2_ARRAY):

            ax[K1_INDEX][K2_INDEX].cla()
            ax[K1_INDEX][K2_INDEX].set(xlim=[-5, 5], ylim=[-5, 5], zlim=[-3, 3], xlabel=r'$x_1$', ylabel=r'$x_2$', zlabel=r'$u$')
            ax[K1_INDEX][K2_INDEX].set_aspect('equal')
            ax[K1_INDEX][K2_INDEX].grid(True, which='both', color='gray', linestyle='--', linewidth=0.5)
            ax[K1_INDEX][K2_INDEX].tick_params(direction='in')
            ax[K1_INDEX][K2_INDEX].title.set_text(rf'$(k_1, k_2) = ({K1}, {K2})$')

            u =  A * np.cos(OMEGA * t - K1 * X - K2 * Y)

            wave = ax[K1_INDEX][K2_INDEX].plot_surface(
                X, Y, u,
                cmap = 'viridis'
            )

if __name__ == '__main__':

    ani = FuncAnimation(
        fig = fig,
        func = update,
        frames = NUM_FRAMES,
        interval = 1000 / FPS
    )
    ani.save('2d_plane_wave_multiple.gif', writer='pillow')



まず第一印象として、我々が普段「波」と聞いて想像するものがたしかに描けているなというのが見て取れます。

次に、 k_1 が増える方向に (上段から下段に) 眺めると、波の進む方向が時計回りに回転しており、波の数が増えていそうなのが分かります。
同様に、 k_2 を増える方向に見ると、波の進む方向は反時計回りに回転し、同時に波の数が増えています。



...と言っても、1 次元の時よりも複雑で、どのパラメーターがどのように影響しているのか実際よく分からないですよね。
という訳で、ブラウザ上でアニメーションのパラメーターを調節できるようにしてみます。


Plotly.js を使うと、大体どの環境でも動くプロットやアニメーションをブラウザ上で表示させることができます。
その一例がこちらになります











アニメーション再生をしたり、 \omega k_1, k_2 を自由に変えられるようになっています。
何となくですが、波の進む方向が  (k_1, k_2) で決まっていそうなのが分かりますね。


ソースコード
2D Plane Wave Animator with Plotly.js | あかり描像のページ
↑ こちらの右上「View on GitHub」からどうぞ。最新の更新はそちらになると思います

ちょっと理論の考察

波数 k[m⁻¹]

波長  \lambda [m] に対し、波数  k [m⁻¹] を次の式で定義します。

 k = \dfrac{2\pi}{\lambda}

あえて日本語で言うなら、「波長の逆数を  2\pi 倍したものを波数という」となります。
…何を言っているんだこの人は。

という訳なので、こんなイメージを持ってください。

単位円周上にサイン波が乗っている。円周 2π で 2 往復する場合が k = 2、3 往復する場合が k = 3、4 往復で k = 4。

この図のように「単位円周上に波長  \lambda の波があるとき、波は何個 (すなわち何往復分) あるでしょう?」と訊かれたら、

 k = \dfrac{2\pi}{\lambda}

となりますね。


この  k を使うと、位相速度の式  v = f \lambda

\begin{equation}
v = f \lambda = 2 \pi f \cdot \frac{\lambda}{2\pi} = \frac{\omega}{k}
\end{equation}

すなわち  \omega = vk となります。この形の式は後々よく使うので、覚えておくとよいです。

2 次元平面波について

さて、2 次元平面波についてはどうやら  k_1, k_2 の値が変わると波が進む方向が変わるようです。では具体的にどの方向に進んでいるのでしょうか?

波の山の位置

波が進む方向を考えるには、波の山 (あるいは谷) の動きを考えるのが分かりやすいですね。もう少し詳細にいうと平面波の等高線、すなわち変位  u が等しい点の集合がどうなっているかを考えてみようということです。

 u(t, x_1, x_2) = \cos (\omega t - k_1 x_1 - k_2 x_2) = \text{const.}

 \cos が一定になるということは、 \cos の中身が一定になればよいですから、この点の集合は

 \omega t - k_1 x_1 - k_2 x_2 = \text{const.}

のような形の直線上にあることになりますね。*1 ここで、この直線の法線ベクトルが  \boldsymbol{k} = (k_1, k_2) になっていることに注意しましょう。

const. を 0 として平面波の等高線の 1 つを取り出した。この直線の法線ベクトルは (k1, k2) であることに注意!
波の進む方向

さて、波の山の形が分かったので、次は波の山が時間の経過とともにどう進むのか考えてみましょう。すなわち、直線

 \omega t - k_1 x_1 - k_2 x_2 = \text{const.}

の上にある一点 (仮に  (x_1, x_2) としておきます) が微小時間経過後にどこにいるかを考えることになりますが、これは t微分することでわかります。 x_1, x_2 も時間の経過によって変化することに注意すると *2

\begin{equation}
\omega - k_1 \frac{d x_1}{dt} - k_2 \frac{d x_2}{dt} = 0
\end{equation}

ここで点  (x_1, x_2) にある媒質の速度ベクトルを  \boldsymbol{v} = (\frac{d x_1}{dt}, \frac{d x_2}{dt}) とすると、今の式は次のように書けます。

\begin{equation}
\boldsymbol{k} \cdot \boldsymbol{v} = \omega
\end{equation}

原点 O にある媒質が単位時間後に点 P に移動したとすると、その速度ベクトル v は図の赤矢印のようになる。この時、速度ベクトル v と波数ベクトル k の内積が角周波数 ω と一致する

PGF/TikZ の一例

\documentclass[border=3]{standalone}

\usepackage{tikz}
\usepackage{pgfplots}
\usepackage{bm}
\pgfplotsset{compat=1.18}
\usetikzlibrary{angles}

\begin{document}
    \pgfmathsetmacro\kx{1}
    \pgfmathsetmacro\ky{2.0}
    \pgfmathsetmacro\kxy{sqrt(\kx*\kx + \ky*\ky)}
    \pgfmathsetmacro\omegaxy{4.0}

    % 波面の方程式 (1: ω, 2: 時刻 t, 3: x 座標)
    \pgfmathdeclarefunction{wave}{3}{%
        \pgfmathparse{((#1) * (#2) - \kx * (#3)) / \ky}
    }
    \begin{tikzpicture}
        % 原点, 座標軸
        \draw (0, 0) node [below left] {O};
        \draw[-stealth, thick] (-1, 0) -- (5, 0) node[right] {$x_1$};
        \draw[-stealth, thick] (0, -1) -- (0, 5) node[above] {$x_2$};

        % 波面
        \draw[domain=-1:5, ultra thick] plot(\x, {wave(\omegaxy, 1, \x)}) node[right] at (2.5, 1) {$k_1 x_1 + k_2 x_2 = \omega \cdot 1$};
        \draw[domain=-1:3, very thick, dashed] plot(\x, {wave(\omegaxy, 0, \x)}) node[right] at (2.5, -1) {$k_1 x_1 + k_2 x_2 = \omega \cdot 0$};

        % 座標の定義
        \coordinate (O) at (0, 0);
        % k の根元
        \coordinate (H) at (\omegaxy * \kx / \kxy / \kxy, \omegaxy * \ky / \kxy / \kxy);
        \coordinate (P) at (2, {wave(\omegaxy, 1, 2)} );

        \draw[fill=red] (P) circle (2pt);

        % 矢印
        \draw[color=blue, thick, -stealth] (H) -- ++(\kx, \ky) coordinate (K) node[above right] {$\bm{k} = (k_1, k_2)$};
        \draw[color=blue, dashed] (O) -- (H);
        \draw[color=red, thick, -stealth] (O) -- (P) node[above right] {P} node [midway, below right] {$\bm{v}$};

        % 角度
        \pic [draw, -stealth, pic text=$\theta$, angle eccentricity=1.5] {angle=P--O--H};
        \pic [draw, angle radius=8] {right angle=K--H--P};

    \end{tikzpicture}
\end{document}


特に、速度ベクトル  \boldsymbol{v} \boldsymbol{k} と平行である時、

\begin{equation}
\omega = \pm \vert \boldsymbol{v} \vert \vert \boldsymbol{k} \vert
\end{equation}

と、1 次元の時の位相速度と同じ式になります *3


まとめると、

平面波  u (t, \boldsymbol{x}) = \exp \left[i (\omega t -  \boldsymbol{k} \cdot \boldsymbol{x}) \right] の波面は波数ベクトル  \boldsymbol{k} の方向に移動していると考えることができ、その速度  \boldsymbol{v} = \dfrac{\omega}{\boldsymbol{k}} 位相速度 と呼ぶ。

となります。

言語について

今回は Python を使用しましたが、Python でなくとも物理シミュレーションは可能です。

伝統的な可視化ツールとしては例えば gnuplot があります。
www.gnuplot.info

gnuplot と一緒に使われることが多い Fortran は並列計算とも相性が良いです。
fortran-lang.org

Julia という手もあります。
julialang.org

JavaScript で実装されている方もいます。
irobutsu.a.la9.jp

もし学校などから無料ライセンスを提供されているのであれば、MatlabMathematica を使うという手もあります。他にも Unity のようなゲームエンジンを利用してみるのも楽しいでしょう (こーじ さんのチャンネルおすすめ)。

言語によって得意なものやライブラリの守備範囲等がまちまちなので、色々な言語を試してみて、自分に触りやすそうなものを探していくと楽しいと思います。何より、プログラミングは特段怖いものではなく、むしろ強力な味方だといち早く実感するのが大事なことかなと個人的は思います。


生成 AI について

昨今の生成 AI の発展はすさまじいものがあります。

例えば今回扱った 2 次元平面波のアニメーションに関してですが、Claude くんに

Plotly.js を使って、平面波
u(t, x1, x2) = cos(\omega t - k1 x1 - k2 x2)
の 曲面のアニメーションを描画したい。
ここで、ユーザーは Web ブラウザ上の toggle を使って \omega, k1, k2 を自由に設定できるようにしたい。

などとお願いすれば、

claude.ai

↑ こんなのを 2, 30 秒ほどで作ってくれます。やばいですね。

挙動がおかしかったら修正するようにお願いすればよいし、言っていることが分からなくなったらそれも聞けばよい。質問攻めしても呆れられるようなこともない。
作りたいものさえあれば、プログラムの知識がなくとも何でも作れるようになっているという状況になっています。


...もう人間いらないかもしれないですね。


See Also

bakkyalo.hatenablog.jp

同じく Plotly を使っていますが、こちらは Python なので今回のようにユーザーが操作できるようにはなっていません。できるようにするにはバックエンドが使える環境が必要になってきます

*1:もう少し正確に言うと、 \cos \theta が定数  C \ (-1\leq C\leq 1) になるような  \theta \theta = \pm \cos^{-1} C + 2 n \pi と、周期的に無数存在しますから、平面波の等高線は周期的な直線群になりますね。

*2:ウェーブマシンを見たことがある方は、媒質はその場で単振動しているにすぎず、横方向に移動している訳ではないことを知っていると思います。実際、ここで "変化" と言っているのは何も媒質一点の運動を正確に追っている訳ではなく、波面上の点がどう動いているように見えるかマクロに追っているにすぎません。

*3:上の脚注でも少し言及しましたが、速度ベクトル v は媒質の正確な運動を表現しているのではなく、波面がマクロにはどう動いているように見えるかを表しているにすぎません。そのような仮定の下で速度ベクトル v を表現すると、v には波数ベクトル k とのなす角 θ だけの任意性が出てきます。実際、波面上の各点がいっせいに k と θ の角をなして移動していると考えても単位時間経過後の波面に変わりはなく、平面波の式 u = exp(iωt - ikx) はこの向きを規定するに足る情報を持っていません。ただ、それだと考えにくい上に、 θ に物理的な意味がある訳でもないので、特に θ = 0 として「波面は波数ベクトル k の方向に移動している」と捉えよう、そしてその時の速度を位相速度と呼ぼうと、そういうことになります。




以上の内容はhttps://bakkyalo.hatenablog.jp/entry/2025/06/01/224710より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14