光学衍射现象在自然界中无处不在,从阳光透过树叶形成的斑驳光影,到现代半导体制造中的光刻技术,衍射效应都扮演着关键角色。理解衍射不仅对物理学家至关重要,对工程师和程序员来说,能够用代码模拟这些现象同样具有巨大价值。本文将带你用Python从零构建完整的光学衍射模拟系统,通过可运行的代码直观展示从惠更斯原理到夫琅禾费衍射的完整过程。
惠更斯原理认为波前的每个点都可以看作新的球面波源,而菲涅尔补充了相干叠加的概念。用数学表达,波前上点Q对观察点P的贡献为:
python复制def huygens_fresnel(Q, P, wavelength):
r = np.linalg.norm(P - Q)
return np.exp(1j * 2*np.pi * r / wavelength) / r
这个函数计算了单个次级波源的贡献,其中:
Q是波前上的点坐标P是观察点坐标wavelength是光的波长1j表示虚数单位角谱理论将衍射视为频域中的滤波过程。传播过程可以表示为:
code复制Uₚ(fₓ, fᵧ) = U_q(fₓ, fᵧ) × exp(ikz√[1-(λfₓ)²-(λfᵧ)²])
Python实现这一过程的关键步骤:
python复制def angular_spectrum_propagation(U, wavelength, dx, z):
# 计算频率坐标
nx, ny = U.shape
fx = np.fft.fftfreq(nx, dx)
fy = np.fft.fftfreq(ny, dx)
FX, FY = np.meshgrid(fx, fy)
# 计算传递函数
H = np.exp(1j * 2*np.pi * z *
np.sqrt(1/wavelength**2 - FX**2 - FY**2))
# 频域传播
U_fft = np.fft.fft2(U)
U_prop = np.fft.ifft2(U_fft * H)
return U_prop
首先建立我们的Python环境:
python复制import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft2, ifft2, fftshift, ifftshift
# 参数设置
wavelength = 500e-9 # 500nm光波
pixel_size = 1e-6 # 1μm像素尺寸
grid_size = 2048 # 模拟区域大小
定义一个圆形孔径函数:
python复制def create_circular_aperture(radius, size, pixel_size):
x = np.arange(-size//2, size//2) * pixel_size
y = np.arange(-size//2, size//2) * pixel_size
X, Y = np.meshgrid(x, y)
R = np.sqrt(X**2 + Y**2)
aperture = np.zeros((size, size), dtype=complex)
aperture[R <= radius] = 1 + 0j
return aperture
基于卷积方法的菲涅尔衍射实现:
python复制def fresnel_diffraction(U_in, wavelength, z, dx):
k = 2*np.pi / wavelength
nx, ny = U_in.shape
# 创建坐标网格
x = np.arange(-nx//2, nx//2) * dx
y = np.arange(-ny//2, ny//2) * dx
X, Y = np.meshgrid(x, y)
# 计算菲涅尔核
kernel = np.exp(1j * k/(2*z) * (X**2 + Y**2))
kernel *= np.exp(1j * k * z) / (1j * wavelength * z)
# 计算衍射场
U_out = ifft2(fft2(U_in) * fft2(kernel))
return U_out
当观察距离足够大时,菲涅尔衍射过渡到夫琅禾费衍射。其核心公式简化为傅里叶变换:
python复制def fraunhofer_diffraction(U_in, wavelength, z, dx):
k = 2*np.pi / wavelength
nx, ny = U_in.shape
# 预计算参数
scale = wavelength * z / (nx * dx)
# 计算衍射场
U_out = fftshift(fft2(ifftshift(U_in)))
U_out *= np.exp(1j * k * z) / (1j * wavelength * z)
return U_out
这个实现的关键点:
fftshift和ifftshift调整频谱位置python复制# 创建单缝
slit_width = 10e-6 # 10微米缝宽
aperture = np.zeros((grid_size, grid_size), dtype=complex)
aperture[:, grid_size//2 - int(slit_width/pixel_size//2):
grid_size//2 + int(slit_width/pixel_size//2)] = 1
# 计算衍射
z = 0.1 # 观察距离0.1米
U_fraunhofer = fraunhofer_diffraction(aperture, wavelength, z, pixel_size)
intensity = np.abs(U_fraunhofer)**2
# 可视化
plt.figure(figsize=(10,6))
plt.imshow(intensity, cmap='hot', extent=[-1,1,-1,1])
plt.title('单缝夫琅禾费衍射图样')
plt.colorbar(label='光强')
plt.show()
我们可以比较三种衍射情况:
| 衍射类型 | 适用条件 | 计算复杂度 | Python实现特点 |
|---|---|---|---|
| 菲涅尔衍射 | z³ ≫ (Δx)⁴/λ | 中等 | 需要二次相位因子 |
| 夫琅禾费衍射 | z ≫ (Δx)²/λ | 低 | 直接傅里叶变换 |
| 严格衍射 | 任意距离 | 高 | 需要数值积分 |
让我们尝试一个更复杂的六边形孔径:
python复制def create_hexagon_aperture(size, pixel_size, radius):
from matplotlib.path import Path
hexagon = Path.unit_regular_polygon(6)
x = np.arange(-size//2, size//2) * pixel_size
y = np.arange(-size//2, size//2) * pixel_size
X, Y = np.meshgrid(x, y)
coords = np.vstack((X.flatten(), Y.flatten())).T / radius
mask = hexagon.contains_points(coords)
return mask.reshape((size, size))
hex_aperture = create_hexagon_aperture(grid_size, pixel_size, 50e-6)
U_hex = fraunhofer_diffraction(hex_aperture, wavelength, 0.5, pixel_size)
对于大型模拟,我们可以使用以下技巧加速计算:
python复制def optimized_fresnel(U_in, wavelength, z, dx):
# 使用单精度浮点减少内存
U_in = U_in.astype(np.complex64)
# 预先计算并重用指数项
k = 2*np.pi / wavelength
phase = np.exp(1j * k * z) / (1j * wavelength * z)
# 使用FFT预分配内存
return phase * ifft2(fft2(U_in) * fft2(fresnel_kernel))
通过叠加不同平面的结果,我们可以模拟三维衍射:
python复制def propagate_3d(U_in, wavelength, z_list, dx):
results = []
for z in z_list:
U_z = fresnel_diffraction(U_in, wavelength, z, dx)
results.append(np.abs(U_z)**2)
return np.stack(results)
使用CuPy库可以将计算转移到GPU:
python复制import cupy as cp
def gpu_fresnel(U_in, wavelength, z, dx):
# 将数据转移到GPU
U_gpu = cp.asarray(U_in)
# GPU上的FFT计算
kernel_gpu = cp.exp(1j * cp.pi/(wavelength*z) *
(cp.square(cp.arange(-N/2,N/2)*dx)[:,None] +
cp.square(cp.arange(-N/2,N/2)*dx)[None,:]))
U_out = cp.fft.ifft2(cp.fft.fft2(U_gpu) * cp.fft.fft2(kernel_gpu))
return cp.asnumpy(U_out) # 传回CPU
光学系统的分辨率受衍射极限限制。我们可以模拟两个点源的分辨情况:
python复制def two_point_resolution(separation, wavelength, na):
# 创建两个点源
points = np.zeros((grid_size, grid_size), dtype=complex)
center = grid_size // 2
points[center, center - int(separation/2/pixel_size)] = 1
points[center, center + int(separation/2/pixel_size)] = 1
# 模拟衍射受限成像
u = fraunhofer_diffraction(points, wavelength, 1, pixel_size)
intensity = np.abs(u)**2
# 绘制强度剖面
plt.plot(intensity[center])
plt.title(f'两点分辨率测试(间隔={separation*1e6:.1f}μm)')
plt.xlabel('位置(μm)')
plt.ylabel('强度')
我们可以模拟简单的全息图生成过程:
python复制def generate_hologram(object_wave, reference_wave):
# 干涉记录
interference = object_wave + reference_wave
hologram = np.abs(interference)**2
# 量化模拟胶片响应
hologram = np.clip(hologram / hologram.max(), 0, 1)
return hologram
def reconstruct_hologram(hologram, reference_wave):
# 全息图重建
reconstructed = hologram * reference_wave
return fraunhofer_diffraction(reconstructed, wavelength, z, dx)
在光学模拟中常会遇到一些问题,这里列出常见问题及解决方法:
频谱混叠:
能量不守恒:
相位跳变:
np.angle的unwrap函数一个实用的调试函数:
python复制def check_energy(U1, U2, name1="前", name2="后"):
E1 = np.sum(np.abs(U1)**2)
E2 = np.sum(np.abs(U2)**2)
print(f"能量检查 - {name1}: {E1:.3e}, {name2}: {E2:.3e}, 比率: {E2/E1:.3f}")
if not np.isclose(E1, E2, rtol=0.1):
print("警告:能量不守恒超过10%!")
现代光学模拟已经发展到可以处理各种复杂场景:
一个矢量衍射的简单示例:
python复制def vector_diffraction(E_field, wavelength, z):
# 分解为x,y偏振分量
Ex = E_field[..., 0]
Ey = E_field[..., 1]
# 分别传播
Ex_prop = fresnel_diffraction(Ex, wavelength, z, pixel_size)
Ey_prop = fresnel_diffraction(Ey, wavelength, z, pixel_size)
return np.stack((Ex_prop, Ey_prop), axis=-1)
在实际项目中,我发现使用面向对象的方式组织代码能显著提高可维护性。例如创建一个DiffractionSimulator类,封装所有相关参数和方法,可以更方便地进行参数扫描和结果比较。对于需要高性能的场景,结合Numba的即时编译能进一步提升计算速度,特别是对于循环密集型的严格衍射计算。