自由粒子波束の時間発展をシミレーションする


前述のブログに示した自由波束の時間発展の様子をシミュレーションして見た.Bernd Thaller : 「Visual Quantum Mechanics」と「Advanced Visual Quantum Mechanics」を参照して, その python プログラムを書く手順とその結果の動画を示しておく.

Thaller cover1


まずはシミュレーションプログラムを書くための予備知識を確認しておく.

Schrödinger 理論による時間発展

自由粒子のシュレディンガー方程式は,

(1)itψ(x,t)=22m2ψ(x,t)=Eψ(x,t)

この平面波解は,
ψ(x,t)=exp{i(kxωt)}=exp(ipxip22mt)(2)=exp(iEt)exp(i2mEx)

ただし,
(3)p=k,|k|=2mE,E=ω=p22m=2k22m

m=1, =1, c=1 の単位系を用いたときの平面波解を uk(x,t) とすると,

(4)uk(x,t)=exp(ikxik22t)

ただし Ek の関係は次である:
(5)E=E(k)=12k2

一般解 ψ は, 平面波解 u の重ね合わせによって表せるから,
(6)ψ(x,t)=1(2π)n/2Rnψ^0(k)eikxik22tdnk=1(2π)n/2Rneikxeik22tψ^0(k)dnk

従って ψ(x,t) は「 eik22tψ^0(k) のフーリエ変換として得られる」と言える.

    自由粒子の一般解を求める手順は次となる:

  1. 初期関数 ψ0 のフーリエ変換 ψ^ を求める.
  2. exp(ik2t/2)ψ^0(k) の逆フーリエ変換を求める.

具体例として初期関数として, 任意定数 α>0, そして pR に対する次のガウス型関数の時間発展を求めてみよう.

(7)ψ0(x)=eipxeαx2,xR

(第1ステップ) : この関数のフーリエ変換は次で与えられる:

(8)ψ^0(k)=1αexp{(kp)22α}

このとき, 関数 ψ0 の運動量 k は平均運動量 p の周りに分布している.

(第2ステップ) : 初期値問題の時間 t に於ける解は次で与えられる:

ψ(x,t)=12πeikx1αeik2t/2e(kp)2/2αdk(9)=12παexp(p22α)exp{(it+1/α)2k2+(ix+p/α)k}dk

最後の積分中の指数関数は 次に書くことが出来る:
(10)it+1/α2q(k)2+(ix+p/α)22(it+1/α),withq(k)=kix+p/αit+1/α

従って ψ(x,t) は,
(11)ψ(x,t)=12παexp{p22α+(ix+p/α)22(it+1/α)}exp{(it+1/α)q(k)22}dk

このとき,
exp{(it+1/α)q(k)22}dk=exp{(it+1/α)2k2}dk=2πit+1/α(12)=2πα1+itα

よって ψ(x,t) は次のように書ける:
(13)ψ(x,t)=11+iαtexp{αx22ixp+ip2t2(1+iαt)}

このとき, この指数関数の実部は 次のように書くことが出来る:
(14)α2(1+α2t2)(xpt)2

従って, 時間 t に於けるガウス波束の絶対値は やはりガウス関数である:
(15)|ψ(x,t)|=(1+α2t2)1/4exp{α(t)2(xpt)2},whereα(t)=α1+α2t2

ガウス関数 |ψ(x,t)| 及び確率密度 ρ=|ψ(x,t)|2 は, 位置 x(t)=pt を中心として拡がっている.それは質量 m=1 で運動量 p を持った粒子が初期条件 x(0)=0 で運動するときの, ちょうど時刻 t に於ける位置である.時刻 t に於けるガウス関数の拡がりは 1/α(t) で与えられ, おおよそ t のように増大する.これは「波束の拡がり」と呼ばれる.またそれと同時に, 波束の最大値は 1/t のように減少する.

Dirac 理論による自由粒子波束の時間発展

時間依存する自由粒子の Dirac 方程式は, 前述の Schrödinger 理論と全く同じ仕方で解くことが出来る.まず, 二乗可積の初期スピノルとして 例えば,

(16)ψ(x,t=0)=ϕ(x)=(ϕ1(x)ϕ2(x))=(132π)1/4exp(x216)(11)

のフーリエ変換 F を行う.その結果, 運動量空間に於ける二乗可積スピノル ϕ^(k) が得られる:
(17)ϕ^(k)=12πReikxϕ(x)dx

次に, この列ベクトルを基礎スピノル upos(k)uneg(k) の線形結合として書く:
(18)ϕ^(k)=ϕ^+(k)upos(k)+ϕ^(k)uneg(k)

ここで展開係数 ϕ^±(k)C2 スカラー積によって与えられる (下の式 (24) を参照):
ϕ^+(k)=upos(k),ϕ^(k)2=upos(k)1ϕ^1(k)+(upos)2ϕ^2(k)=d+(k)ϕ^1(k)+d(k)2ϕ^2(k),(19)ϕ^(k)=uneg(k),ϕ^(k)2=(uneg)1ϕ^1(k)+(uneg)2ϕ^2(k)=d(k)ϕ^1(k)+d+(k)2ϕ^2(k)

運動量空間に於ける自由粒子の Dirac 演算子 H0 の作用 h0(k) は,
(20)h0(k)=cαk+βmc2=(mc2ckckmc2)

で与えられ, その固有値は次である:
(21)λ(k)=c2k2+m2c4

d+(k)d(k) を, この h0(k) を対角化するユニタリー行列 u(k) の係数とする:
(22)u(k)=d+(k)12 + d(k)βα

すると u(k) を構成する列ベクトルが, 行列 h0(k) の固有ベクトルとなる:
(23)u(k)=d+(k)12  id(k)σ2

従って, 正の固有値 +λ(k) に属する固有ベクトル upos(k) と, 負の固有値 λ(k) に属する固有ベクトル uneg(k) は次となる:
(24)upos(k)=u(k)(10)=(d+(k)d(k)),uneg(k)=u(k)(01)=(d(k)d+(k))

次のステップは, 式 (18) の ϕ^(k) 部分に 時間因子 exp(iλ(k)t) を掛け合わせることである:
(25)ψ^(k,t)=ϕ^+(k)upos(k)eiλ(k)t+ϕ^(k)uneg(k)eiλ(k)t

最後に, この ψ^(k,t) を「逆フーリエ変換」する:
ψ(x,t)=12πReikxψ^(k,t)dk=R{ϕ^+(k)12πupos(k)eikxiλ(k)t+ϕ^(k)12πuneg(k)eikx+iλ(k)t}dk(26)=R{ϕ^+(k)upos(k;x,t)+ϕ^(k)uneg(k;x,t)}dk

ただし, 最後の段階の upos(k;x,t)uneg(k;x,t) は, 自由粒子に対する Dirac 方程式の平面波解である:
(27)upos(k;x,t)=12πupos(k)eikxiλ(k)t,uneg(k;x,t)=12πuneg(k)eikx+iλ(k)t

この式 (27) によって, 初期状態がガウス波束 ϕ の場合に於ける「時間依存する自由 Dirac 方程式 解」が得られたことになる.

複素数値関数の可視化

ここでは複素数を可視化するのに「位相」をカラーコードで表す方法を用いる.それは, 位置 x に於ける(複素数値を持つ)「波動関数の絶対値」はプロットで示し,「位相」を示すにはそのグラフと x 軸間の領域を「波動関数の位相に対応したカラーマップの色」で塗り潰すやり方である.カラーマップは HSV を用いる.詳しくは B.Thaller :「Visual Quantum Mechanics」の第 1 章を参照すべし.

シミュレーションプログラムとその結果

最初に Schrödinger 理論による時間発展のシミュレーションプログラムとその結果を示す.以下の python プログラムは全て, 今流行の生成 AI に書いてもらった.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import cm

# Define the function Psi
def Psi(x, t, a, p):
return (1 / np.sqrt(1 + 1j * a * t)) * np.exp(-(a * x**2 - 2 * 1j * x * p + 1j * p**2 * t) / (2 * (1 + 1j * a * t)))

# Define the ColorFunction
def color_function(x, t, a, p):
return cm.hsv(np.mod(np.angle(Psi(x, t, a, p)), 2 * np.pi) / (2 * np.pi))

# Set up the figure and axis
fig, ax = plt.subplots(figsize=(8, 6))
x = np.linspace(-10, 30, 400)
a = 1/16
p = 3/4
t_values = np.linspace(0, 50, 300) # Time values for animation

# Initialize plot elements
line, = ax.plot(x, np.abs(Psi(x, 0, a, p)), lw=2) # Initialize with t=0 data
ax.set_xlim([-10, 30])
ax.set_ylim([0, 1.05])
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$|\psi(x)|$')
plt.title("Time Evolution of " r'$|\psi(x, t)|$')

# Initial fill with colors
fill_collections = []
for i in range(len(x) - 1):
x_fill = [x[i], x[i+1]]
y_fill = [np.abs(Psi(x[i], 0, a, p)), np.abs(Psi(x[i+1], 0, a, p))]
fill = ax.fill_between(x_fill, y_fill, 0, color=color_function(x[i], 0, a, p), alpha=0.7)
fill_collections.append(fill)

def update(frame):
t = t_values[frame]
y = np.abs(Psi(x, t, a, p))
line.set_data(x, y)

# Remove previous fills
for fill in fill_collections:
fill.remove()
fill_collections.clear()

# Update fill colors
for i in range(len(x) - 1):
x_fill = [x[i], x[i+1]]
y_fill = [y[i], y[i+1]]
fill = ax.fill_between(x_fill, y_fill, 0, color=color_function(x[i], t, a, p), alpha=0.7)
fill_collections.append(fill)

return line,

# Create animation
ani = animation.FuncAnimation(fig, update, frames=len(t_values), blit=False, interval=50)

# Save animation as GIF
ani.save("Psi_wave3.gif", writer="pillow", fps=20)

plt.show()

出力されたシミュレーション動画は次である:

図 1. ガウス波束の時間発展を Schrödinger 理論によってシミュレーションしたグラフ

このグラフの色と偏角との関係は, 次のカラーマップによって配色されている:

図 2. 複素平面のカラーマップ

この図もやはり生成AIの助けを借りて書いたものである.上記本のカラーページに載っていた図を生成AIに与え,「これと同じグラフを書いて」とお願いするだけであった.

次は同様なガウス波束を, 今度は Dirac 理論によってシミュレーションしたプログラムを示す:

import matplotlib
matplotlib.use('TkAgg')

import numpy as np
import scipy.integrate as integrate
import matplotlib.pyplot as plt
import matplotlib.animation as animation

def La(k):
    return np.sqrt(k**2 + 1)

def dp(k):
    return np.sqrt(1 + 1/La(k)) / np.sqrt(2)

def dm(k):
    return np.sign(k) * np.sqrt(1 - 1/La(k)) / np.sqrt(2)

def b(k):
    return (2/np.pi)**(1/4) * np.exp(-4 * (k-3/4)**2) * (dp(k) + dm(k))

def d(k):
    return (2/np.pi)**(1/4) * np.exp(-4 * (k-3/4)**2) * (dp(k) - dm(k))

def up1(k, x, t):
    return dp(k) / np.sqrt(2) * np.exp(1j * k * x - 1j * La(k) * t)

def up2(k, x, t):
    return dm(k) / np.sqrt(2) * np.exp(1j * k * x - 1j * La(k) * t)

def um1(k, x, t):
    return -dm(k) / np.sqrt(2) * np.exp(1j * k * x + 1j * La(k) * t)

def um2(k, x, t):
    return dp(k) / np.sqrt(2) * np.exp(1j * k * x + 1j * La(k) * t)

def psi1(x, t):
    def integrand_real(k):
        return np.real(b(k) * up1(k, x, t) + d(k) * um1(k, x, t))
    def integrand_imag(k):
        return np.imag(b(k) * up1(k, x, t) + d(k) * um1(k, x, t))
    result_real, _ = integrate.quad(integrand_real, -np.inf, np.inf, epsabs=1e-3, epsrel=1e-3)
    result_imag, _ = integrate.quad(integrand_imag, -np.inf, np.inf, epsabs=1e-3, epsrel=1e-3)
    return result_real + 1j * result_imag

def psi2(x, t):
    def integrand_real(k):
        return np.real(b(k) * up2(k, x, t) + d(k) * um2(k, x, t))
    def integrand_imag(k):
        return np.imag(b(k) * up2(k, x, t) + d(k) * um2(k, x, t))
    result_real, _ = integrate.quad(integrand_real, -np.inf, np.inf, epsabs=1e-3, epsrel=1e-3)
    result_imag, _ = integrate.quad(integrand_imag, -np.inf, np.inf, epsabs=1e-3, epsrel=1e-3)
    return result_real + 1j * result_imag

def rho(x, t):
    return np.abs(psi1(x, t))**2 + np.abs(psi2(x, t))**2

x_values = np.linspace(-20, 20, 100)
times = np.linspace(0, 30, 300)

fig, ax = plt.subplots()
ax.set_xlim(-20, 20)
ax.set_ylim(-0.5, 1)
ax.grid()
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$\rho(x)$')
ax.set_title("Time evolution of a Gaussian wave packet with positive momentum")

fill = ax.fill_between(x_values, np.zeros_like(x_values), np.zeros_like(x_values), color='blue', alpha=0.5)
line1, = ax.plot([], [], color='red', lw=2)  # Line for y_psi1
line2, = ax.plot([], [], color='green', lw=2)  # Line for y_psi2

def animate(t):
    y_rho = [rho(x, t) for x in x_values]
    y_psi1 = [np.abs(psi1(x, t))**2 for x in x_values]
    y_psi2 = [-1*np.abs(psi2(x, t))**2 for x in x_values]
    global fill
    fill.remove()
    fill = ax.fill_between(x_values, 0, y_rho, color='blue', alpha=0.5)
    line1.set_data(x_values, y_psi1)
    line2.set_data(x_values, y_psi2)
    return fill, line1, line2

ani = animation.FuncAnimation(fig, animate, frames=times, interval=20, blit=False, repeat=False)
ani.save("wave_packet_momentum.gif", writer="pillow") # gif動画として保存する.
#plt.show() # 表示もさせたいならコメントアウトのマークを外す.
print("Program is finished!")

この場合, 数値積分の計算などを何回も行うので動画作成に時間がだいぶ掛かってしまう(非力のMac miniでは30分前後掛かった).

そのため cython を導入して高速化してみた.以下は生成AIの指示である:
【準備】
(1) まず Cython用の .pyx ファイル: wave_packet.pyx (Cythonコード)を作成します:

#
# wave_packet.pyx
#

# distutils: language = c
# cython: boundscheck=False, wraparound=False, nonecheck=False, cdivision=True

import numpy as np
cimport numpy as cnp
from scipy.integrate import quad
cimport cython
from libc.math cimport sqrt, exp, sin, cos

cdef double La(double k):
    return sqrt(k * k + 1)

cdef double dp(double k):
    return sqrt(1 + 1/La(k)) / sqrt(2)

cdef double dm(double k):
    return (1 if k >= 0 else -1) * sqrt(1 - 1/La(k)) / sqrt(2)

cdef double complex up1(double k, double x, double t):
    cdef double complex phase = cos(k*x - La(k)*t) + 1j*sin(k*x - La(k)*t)
    return dp(k) / sqrt(2) * phase

cdef double complex up2(double k, double x, double t):
    cdef double complex phase = cos(k*x - La(k)*t) + 1j*sin(k*x - La(k)*t)
    return dm(k) / sqrt(2) * phase

cdef double complex um1(double k, double x, double t):
    cdef double complex phase = cos(k*x + La(k)*t) + 1j*sin(k*x + La(k)*t)
    return -dm(k) / sqrt(2) * phase

cdef double complex um2(double k, double x, double t):
    cdef double complex phase = cos(k*x + La(k)*t) + 1j*sin(k*x + La(k)*t)
    return dp(k) / sqrt(2) * phase

cdef double complex b(double k):
    """ Computes the function b(k) """
    cdef double norm = (2/3.141592653589793)**0.25
    return norm * exp(-4 * (k - 0.75)**2) * (dp(k) + dm(k))

cdef double complex d(double k):
    """ Computes the function d(k) """
    cdef double norm = (2/3.141592653589793)**0.25
    return norm * exp(-4 * (k - 0.75)**2) * (dp(k) - dm(k))

@cython.boundscheck(False)
@cython.wraparound(False)
def psi1(double x, double t):
    """ Compute psi1 using numerical integration """
    cdef double result_real, result_imag
    result_real, _ = quad(lambda k: (b(k) * up1(k, x, t) + d(k) * um1(k, x, t)).real, -10, 10, epsabs=1e-3, epsrel=1e-3)
    result_imag, _ = quad(lambda k: (b(k) * up1(k, x, t) + d(k) * um1(k, x, t)).imag, -10, 10, epsabs=1e-3, epsrel=1e-3)
    return result_real + 1j * result_imag

@cython.boundscheck(False)
@cython.wraparound(False)
def psi2(double x, double t):
    """ Compute psi2 using numerical integration """
    cdef double result_real, result_imag
    result_real, _ = quad(lambda k: (b(k) * up2(k, x, t) + d(k) * um2(k, x, t)).real, -10, 10, epsabs=1e-3, epsrel=1e-3)
    result_imag, _ = quad(lambda k: (b(k) * up2(k, x, t) + d(k) * um2(k, x, t)).imag, -10, 10, epsabs=1e-3, epsrel=1e-3)
    return result_real + 1j * result_imag

@cython.boundscheck(False)
@cython.wraparound(False)
def rho(double x, double t):
    """ Compute probability density """
    return abs(psi1(x, t))**2 + abs(psi2(x, t))**2

(2) Cythonコンパイル用 setup.py を作成します:

#
# setup.py
#
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy

ext_modules = [
    Extension(
        "wave_packet",
        ["wave_packet.pyx"],
        include_dirs=[numpy.get_include()],
        define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
    )
]

setup(
    ext_modules=cythonize(ext_modules, language_level=3, annotate=True),
)

(3) Pythonスクリプト main.py を作成します:

#
# main.py
#
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from wave_packet import psi1, psi2, rho

x_values = np.linspace(-20, 20, 100)
times = np.linspace(0, 30, 300)

fig, ax = plt.subplots()
ax.set_xlim(-20, 20)
ax.set_ylim(-0.5, 1)
ax.grid()
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$\rho(x)$')
ax.set_title("Time evolution of a Gaussian wave packet with positive momentum")

fill = ax.fill_between(x_values, np.zeros_like(x_values), np.zeros_like(x_values), color='blue', alpha=0.5)
line1, = ax.plot([], [], color='red', lw=2)
line2, = ax.plot([], [], color='green', lw=2)

def animate(t):
    y_rho = np.array([rho(x, t) for x in x_values])
    y_psi1 = np.array([abs(psi1(x, t))**2 for x in x_values])
    y_psi2 = -1 * np.array([abs(psi2(x, t))**2 for x in x_values])
    global fill
    fill.remove()
    fill = ax.fill_between(x_values, 0, y_rho, color='blue', alpha=0.5)
    line1.set_data(x_values, y_psi1)
    line2.set_data(x_values, y_psi2)
    return fill, line1, line2

ani = animation.FuncAnimation(fig, animate, frames=times, interval=20, blit=False, repeat=False)
ani.save("wave_packet_momentum.gif", writer="pillow")
print("Program is finished!")

【使い方】
1. Cythonコードをコンパイルします(ターミナルで):

python setup.py build_ext --inplace

2. Pythonスクリプト (main.py) を実行します(ターミナルで):

python main.py

この結果, 動画作成に掛かった時間は約30秒となった!.
シミュレーション結果は次である:

図 3. ディラック理論によるガウス波束の時間発展

今度はディラックスピノルの絶対値 |ψ(x,t)| だけでなく, スピノルの正エネルギー成分の絶対値 |ψ1| と負エネルギー成分の絶対値 |ψ2| も一緒に表示されている.色で塗られているのが |ψ(x,t)| である(今度は偏角のカラー表示はしていない).負エネルギー成分は x 軸の下に反転して描かれているので注意するべし.また, ときどき角のようなスパイクが見られるが, これはプログラムの計算誤差によるものである.プログラム中の数値などをキチンと調整すれば無くなると思う.左右の揺れである「Zitterbewegung」現象は初期の頃だけで見られる.

参考のために「初期状態で静止しているガウス波束の時間発展」の様子をシミュレーションした動画も示しておこう.左右に細かく揺れていて「Zitterbewegung」現象の存在がよく分かると思う:

図 4. 静止したガウス波束の時間発展