4. 光束模式仿真基础:Python环境搭建与高斯光束传播

说实话,做光束模式识别,光有理论不行。你得亲手把光束“跑”出来看看。我见过不少同学,公式背得滚瓜烂熟,一打开IDE就懵了。嗯,这一章我们就来解决这个问题。

我会带你搭好Python环境,然后从零开始模拟一束高斯光束的传播。你想想看,当你在屏幕上看到那个光斑随着传播距离慢慢散开的时候,很多抽象的概念一下子就通了。

4.1 环境搭建:别小看这一步

我个人习惯用Anaconda来管理Python环境。为什么?因为它把NumPy、SciPy、Matplotlib这些科学计算库打包好了,省得你一个一个装,版本还容易打架。

如果你还没装,可以去Anaconda官网下载个Miniconda,轻量够用。装完之后,打开终端(Windows下是Anaconda Prompt),敲下面这行:

conda create -n beam_sim python=3.9
conda activate beam_sim
pip install numpy scipy matplotlib

等进度条跑完,环境就搭好了。我在项目中遇到过好几次因为库版本不对导致计算结果异常的情况——比如SciPy的傅里叶变换函数接口变了,代码跑出来全是NaN。所以建议你锁定版本:

pip install numpy==1.23.5 scipy==1.9.3 matplotlib==3.6.2
小提示: 如果你用的是M1/M2芯片的Mac,记得装arm64版本的Miniconda,不然NumPy的性能会打折扣。

4.2 核心库速览:三剑客

我们这次主要用三个库。说白了,它们的分工很明确:

库名 作用 本章用法
NumPy 数组运算、复数处理 构建二维网格、定义光场复振幅
SciPy 科学计算、FFT 用 fft2 / ifft2 做角谱传播
Matplotlib 数据可视化 画光强分布、相位图、3D曲面

你不需要把每个函数都背下来。记住一个原则:NumPy造数据,SciPy算数据,Matplotlib看数据。这就够了。

4.3 高斯光束的数学表达

先回顾一下公式。基模高斯光束在z=0处的光场分布是:

E(x, y, 0) = exp(-(x² + y²) / w₀²)

其中w₀是束腰半径。传播到距离z处,光场变成:

E(x, y, z) = (w₀ / w(z)) · exp(-(x² + y²) / w(z)²) · exp(-i·k·z - i·k·(x²+y²)/(2R(z)) + i·ζ(z))

看着复杂?别怕。我们一步一步拆开写代码。我在项目中习惯先把参数定义清楚:

import numpy as np

# 物理参数
wavelength = 532e-9       # 波长 532nm(绿光)
w0 = 1e-3                 # 束腰半径 1mm
z = 0.5                   # 传播距离 0.5m
k = 2 * np.pi / wavelength # 波数

# 计算光束参数
zR = np.pi * w0**2 / wavelength  # 瑞利距离
wz = w0 * np.sqrt(1 + (z/zR)**2) # z处的光斑半径
Rz = z * (1 + (zR/z)**2)         # 波前曲率半径
zeta = np.arctan(z / zR)         # 古伊相位
关键点: 瑞利距离zR是高斯光束的“分水岭”。z < zR时,光束近似准直;z > zR时,光束开始明显发散。记住这个,后面分析模式的时候很有用。

4.4 构建二维网格与光场

要模拟二维光场,先得建一个网格。我建议网格范围取到束腰的3~5倍,不然边界效应会干扰结果。

# 网格参数
N = 512                   # 网格点数
L = 5 * w0                # 网格半宽
x = np.linspace(-L, L, N)
y = np.linspace(-L, L, N)
X, Y = np.meshgrid(x, y)
R2 = X**2 + Y**2

# z=0处的光场(复振幅)
E0 = np.exp(-R2 / w0**2)

# z处的光场
E = (w0/wz) * np.exp(-R2/wz**2) * \
    np.exp(-1j * k * z - 1j * k * R2/(2*Rz) + 1j * zeta)

这里用np.meshgrid生成二维坐标网格,R2是径向距离的平方。注意E是复数——振幅和相位都包含在里面了。

注意: 网格点数N最好取2的幂次(256、512、1024),这样后面做FFT时效率最高。我曾经因为取了500点,FFT慢了将近一倍,排查了半天才发现是这个问题。

4.5 可视化:让光束“现形”

光场算出来了,得画出来看看。我一般同时看三个东西:光强分布、一维截面、相位分布。

import matplotlib.pyplot as plt

# 光强
I = np.abs(E)**2

plt.figure(figsize=(12, 4))

# 子图1:二维光强
plt.subplot(131)
plt.imshow(I, extent=[-L, L, -L, L], cmap='hot')
plt.colorbar(label='Intensity')
plt.title('Intensity at z=0.5m')

# 子图2:一维截面(取y=0)
plt.subplot(132)
plt.plot(x, I[N//2, :])
plt.xlabel('x (m)')
plt.ylabel('Intensity')
plt.title('Cross-section (y=0)')
plt.grid(True)

# 子图3:相位
plt.subplot(133)
phase = np.angle(E)
plt.imshow(phase, extent=[-L, L, -L, L], cmap='hsv', vmin=-np.pi, vmax=np.pi)
plt.colorbar(label='Phase (rad)')
plt.title('Phase at z=0.5m')

plt.tight_layout()
plt.show()

运行这段代码,你会看到:光强是高斯形状的圆斑,截面是一条钟形曲线,相位则呈现弯曲的波前——这就是高斯光束的“指纹”。

4.6 传播模拟:角谱法

上面我们用了解析公式。但如果光束不是理想高斯形,或者遇到复杂孔径,解析解就不存在了。这时候要用角谱传播法。

核心思想很简单:把光场分解成平面波,每个平面波传播一段距离,再合起来。数学上就是两次傅里叶变换。

from scipy.fft import fft2, ifft2, fftshift, ifftshift

def angular_spectrum_propagation(E0, wavelength, dx, z):
    """
    角谱法传播
    E0: 初始光场
    dx: 网格间距
    z:  传播距离
    """
    N = E0.shape[0]
    # 频率坐标
    fx = np.fft.fftfreq(N, d=dx)
    fy = np.fft.fftfreq(N, d=dx)
    FX, FY = np.meshgrid(fx, fy)
    F2 = FX**2 + FY**2
    
    # 角谱传递函数
    k = 2 * np.pi / wavelength
    H = np.exp(1j * k * z * np.sqrt(1 - wavelength**2 * F2))
    # 注意:当1 - wavelength**2 * F2 < 0时,对应倏逝波,这里直接置零
    H[np.imag(H) > 0] = 0
    
    # 传播
    A0 = fftshift(fft2(E0))
    A = A0 * H
    E = ifft2(ifftshift(A))
    
    return E

调用也很简单:

dx = 2*L / N
E_prop = angular_spectrum_propagation(E0, wavelength, dx, z=0.5)

对比一下解析解和角谱法的结果——你会发现它们几乎完全重合。这就是数值仿真的魅力:用通用方法验证特解。

经验之谈: 角谱法对网格间距有要求。dx必须小于λ/2,否则高频成分会混叠。我一般取dx ≈ λ/5,既保证精度,又不会让计算量太大。

4.7 本章知识结构

下面这张图把本章的核心逻辑串起来了。你可以把它当作一个“思维导图”来用:

第4章 知识结构:光束模式仿真基础 Python环境搭建 NumPy 数组运算 SciPy FFT Matplotlib 可视化 高斯光束参数:w₀, λ, zR, w(z), R(z), ζ(z) 解析法:直接代入公式 数值法:角谱传播 (FFT) 输出:光强分布 | 相位分布 | 截面曲线 应用:模式识别 | 光束质量分析 核心逻辑:环境 → 库 → 参数 → 方法 → 输出 → 应用,环环相扣

4.8 避坑指南

最后,分享几个我踩过的坑:

  • 网格间距太小或太大: 太小导致计算量爆炸,太大导致采样不足。我一般取dx = w0/10作为起点。
  • 忘记归一化: 角谱法传播后,总能量可能会漂移。记得检查np.sum(np.abs(E0)**2)和np.sum(np.abs(E)**2)是否一致。
  • 相位缠绕: np.angle返回的是主值(-π, π],如果相位变化超过2π,画出来会有跳变。可以用np.unwrap解缠绕。
  • 边界反射: 如果网格边界处光场没有衰减到0,FFT会产生伪影。解决办法是加一个孔径光阑或者扩大网格范围。

嗯,环境搭好了,代码能跑了,光束也画出来了。接下来你就可以拿着这套工具,去分析各种光束模式了。记住,仿真不是目的,理解物理才是。