跳到正文
EN

C.2

分步算符法

本站波包动画背后的算法:位置空间乘势能相位、动量空间乘动能相位,FFT 来回切换——严格保模、二阶收敛,附完整代码与陷阱清单。

建议先掌握

学完本节你应该能

  • 说出算符分裂的思想与 Strang 分裂的误差阶
  • 写出用 FFT 演化波包的完整程序
  • 解释为什么该算法严格保模,并知道周期边界带来的陷阱

含时薛定谔方程的解 Ψ(t)=eiH^t/Ψ(0)\Psi(t)=\ee^{-\ii\hat Ht/\hbar}\Psi(0) 写起来一行, 算起来麻烦:H^=T^+V^\hat H=\hat T+\hat V 里动能 T^\hat T 在位置表象是微分算符。 分步算符法(split-operator method)的观察是:

  • eiV^Δt/\ee^{-\ii\hat V\Delta t/\hbar}位置表象是对角的——逐点乘一个相位,零成本;
  • eiT^Δt/\ee^{-\ii\hat T\Delta t/\hbar}动量表象是对角的——也是逐点乘相位;
  • 两个表象之间的换基恰好是傅里叶变换(A.2), 而 FFT 把它做到了 O(NlogN)O(N\log N)

于是每一步演化 = 乘相位 → FFT → 乘相位 → 逆 FFT。 正文里波包撞势垒自由波包扩散的动画全是这个算法在跑。

分裂公式与误差阶

麻烦在于 T^\hat TV^\hat V 不对易,所以 ei(T^+V^)Δt/eiT^Δt/eiV^Δt/\ee^{-\ii(\hat T+\hat V)\Delta t/\hbar}\ne\ee^{-\ii\hat T\Delta t/\hbar}\ee^{-\ii\hat V\Delta t/\hbar}。 但 Δt\Delta t 很小时可以近似拆开,拆法的对称性决定精度:

一步演化的完整流程(Strang 分裂):

Ψ  ×eiVΔt/2   FFT   ×eik2Δt/2m   FFT1   ×eiVΔt/2  Ψ(C.2.3)\Psi\ \xrightarrow{\ \times\,\ee^{-\ii V\Delta t/2\hbar}\ } \ \xrightarrow{\ \text{FFT}\ } \ \xrightarrow{\ \times\,\ee^{-\ii\hbar k^2\Delta t/2m}\ } \ \xrightarrow{\ \text{FFT}^{-1}\ } \ \xrightarrow{\ \times\,\ee^{-\ii V\Delta t/2\hbar}\ }\ \Psi'\tag{C.2.3}

完整代码:波包撞方势垒

EV0E\approx V_0 的波包打在势垒上,一半穿过一半弹回(自然单位 =m=1\hbar=m=1):

import numpy as np

# ---- 网格 ----
L, N = 40.0, 1024
x = np.linspace(-L/2, L/2, N, endpoint=False)
dx = x[1] - x[0]
k = 2 * np.pi * np.fft.fftfreq(N, d=dx)     # FFT 约定下的动量网格

# ---- 势能:中间一道方势垒 ----
V0, a = 2.0, 1.0
V = np.where(np.abs(x) < a / 2, V0, 0.0)

# ---- 初态:从左边入射的高斯波包(中心动量 k0,动能 k0²/2 = 2 = V0)----
x0, k0, sigma = -10.0, 2.0, 1.5
psi = np.exp(-(x - x0)**2 / (4 * sigma**2) + 1j * k0 * x)
psi /= np.sqrt(np.sum(np.abs(psi)**2) * dx)   # 归一化

# ---- 预先算好演化因子(Strang 分裂:V/2 → T → V/2)----
dt, steps = 0.005, 2000
expV_half = np.exp(-0.5j * V * dt)
expT      = np.exp(-0.5j * k**2 * dt)

for _ in range(steps):
    psi = expV_half * psi                        # 半步势能
    psi = np.fft.ifft(expT * np.fft.fft(psi))    # 整步动能(动量空间)
    psi = expV_half * psi                        # 半步势能

# ---- 检验:模守恒 + 透射/反射概率 ----
norm = np.sum(np.abs(psi)**2) * dx
T = np.sum(np.abs(psi[x >  a/2])**2) * dx
R = np.sum(np.abs(psi[x < -a/2])**2) * dx
print(f"演化 {steps} 步后  norm = {norm:.12f}")
print(f"透射 T = {T:.4f}   反射 R = {R:.4f}   T + R = {T+R:.4f}")

实测输出:

演化 2000 步后  norm = 1.000000000000
透射 T = 0.5119   反射 R = 0.4865   T + R = 0.9984

关键行说明:

  • np.fft.fftfreq(N, d=dx):给出 FFT 约定下每个数组下标对应的波数 (前一半是正 kk、后一半是负 kk),乘 2π2\pi 后就是动量网格。 自己手排 kk 数组是本算法头号错误来源——顺序排错,波包会瞬间碎掉。
  • endpoint=False:FFT 隐含周期边界,x=L/2x=-L/2x=+L/2x=+L/2 是同一个点,不能重复取。
  • 演化因子在循环外预先算好:每步只剩逐点乘法和两次 FFT。
  • norm 精确到 12 位——保模是结构性的,不依赖 Δt\Delta t 小。
  • T+R=0.9984T+R=0.9984 不是误差:还差的 0.0016 是仍滞留在势垒区 x<a/2\lvert x\rvert<a/2 内的概率。
  • 中途每隔若干步把 np.abs(psi)**2 画出来,就是正文里的隧穿动画。

收敛阶检验

在谐振子势里演化一个相干态,用极小步长的解当参考,看误差随 Δt\Delta t 的变化:

import numpy as np

L, N = 40.0, 1024
x = np.linspace(-L/2, L/2, N, endpoint=False)
dx = x[1] - x[0]
k = 2 * np.pi * np.fft.fftfreq(N, d=dx)
V = 0.5 * x**2                        # 谐振子势

psi0 = np.exp(-(x - 3.0)**2 / 2)      # 相干态:来回摆而不变形
psi0 = psi0 / np.sqrt(np.sum(np.abs(psi0)**2) * dx)

def evolve(psi, dt, steps):
    expV_half = np.exp(-0.5j * V * dt)
    expT = np.exp(-0.5j * k**2 * dt)
    for _ in range(steps):
        psi = expV_half * np.fft.ifft(expT * np.fft.fft(expV_half * psi))
    return psi

T_total = 2.0
ref = evolve(psi0.copy(), T_total / 6400, 6400)   # 极小步长作参考解

print("   dt        误差 ‖ψ - ψ_ref‖    误差比")
prev = None
for steps in [50, 100, 200, 400]:
    psi = evolve(psi0.copy(), T_total / steps, steps)
    err = np.sqrt(np.sum(np.abs(psi - ref)**2) * dx)
    ratio = f"{prev/err:.2f}" if prev else "  --"
    print(f" {T_total/steps:.4f}     {err:.3e}      {ratio}")
    prev = err

实测输出:

   dt        误差 ‖ψ - ψ_ref‖    误差比
 0.0400     1.162e-03        --
 0.0200     2.905e-04      4.00
 0.0100     7.257e-05      4.00
 0.0050     1.809e-05      4.01

步长减半、误差准确变为 1/4——Strang 分裂的 O(Δt2)O(\Delta t^2) 全局误差, 和推导完全一致。

全站第 104 / 106 节 · 用 翻页