3. 自由曲面斜率计算与曲率分析
任务定义
本案例针对自由曲面光学设计中面形质量评估的核心环节:给定一组离散面形数据(x, y, z),计算各点处的斜率(一阶导数)与曲率(二阶导数),并可视化曲率分布以识别局部面形异常。输入数据采用旋转对称非球面叠加正弦波纹的解析面形,便于与理论值对比验证算法正确性。
数值方法
采用五点中心差分格式计算偏导数。对于均匀网格间距 h,一阶偏导为:
∂z/∂x ≈ (-z(x+2h) + 8z(x+h) - 8z(x-h) + z(x-2h)) / (12h)
二阶偏导及混合偏导类似。平均曲率 H 和高斯曲率 K 由下式得到:
H = ((1+zx²)zyy - 2zx·zy·zxy + (1+zy²)zxx) / (2(1+zx²+zy²)^(3/2))
K = (zxx·zyy - zxy²) / (1+zx²+zy²)²
边界点采用单侧差分,内部点全部使用中心差分以保证精度。
代码实现
import numpy as np
import matplotlib.pyplot as plt
# ========== 1. 解析面形定义 ==========
def surface(x, y):
"""旋转对称非球面(二次曲面) + 正弦波纹扰动"""
c = 0.02 # 曲率半径倒数 (1/mm)
k = -0.5 # 圆锥系数
r2 = x**2 + y**2
# 非球面 sagitta
z_base = c * r2 / (1 + np.sqrt(1 - (1+k) * c**2 * r2))
# 正弦波纹: 幅值 2μm, 空间频率 0.5/mm
z_ripple = 0.002 * np.sin(2 * np.pi * 0.5 * x) * np.cos(2 * np.pi * 0.5 * y)
return z_base + z_ripple
# ========== 2. 理论导数(解析求导) ==========
def surface_derivatives(x, y):
"""返回 (zx, zy, zxx, zyy, zxy) 理论值"""
r2 = x**2 + y**2
c, k = 0.02, -0.5
denom = np.sqrt(1 - (1+k) * c**2 * r2)
# 非球面部分一阶导
zx_base = c * x / denom
zy_base = c * y / denom
# 非球面部分二阶导
zxx_base = c / denom + (1+k) * c**3 * x**2 / denom**3
zyy_base = c / denom + (1+k) * c**3 * y**2 / denom**3
zxy_base = (1+k) * c**3 * x * y / denom**3
# 波纹部分
wx, wy = 2*np.pi*0.5, 2*np.pi*0.5
A = 0.002
zx_rip = A * wx * np.cos(wx*x) * np.cos(wy*y)
zy_rip = -A * wy * np.sin(wx*x) * np.sin(wy*y)
zxx_rip = -A * wx**2 * np.sin(wx*x) * np.cos(wy*y)
zyy_rip = -A * wy**2 * np.sin(wx*x) * np.cos(wy*y)
zxy_rip = -A * wx * wy * np.cos(wx*x) * np.sin(wy*y)
return (zx_base+zx_rip, zy_base+zy_rip, zxx_base+zxx_rip,
zyy_base+zyy_rip, zxy_base+zxy_rip)
# ========== 3. 数值差分计算 ==========
def numerical_derivatives(Z, h):
"""五点中心差分计算一阶/二阶偏导,边界用单侧差分"""
ny, nx = Z.shape
zx = np.zeros_like(Z); zy = np.zeros_like(Z)
zxx = np.zeros_like(Z); zyy = np.zeros_like(Z); zxy = np.zeros_like(Z)
# 内部点(五点中心差分)
for i in range(2, ny-2):
for j in range(2, nx-2):
zx[i,j] = (-Z[i,j+2] + 8*Z[i,j+1] - 8*Z[i,j-1] + Z[i,j-2]) / (12*h)
zy[i,j] = (-Z[i+2,j] + 8*Z[i+1,j] - 8*Z[i-1,j] + Z[i-2,j]) / (12*h)
zxx[i,j] = (-Z[i,j+2] + 16*Z[i,j+1] - 30*Z[i,j] + 16*Z[i,j-1] - Z[i,j-2]) / (12*h**2)
zyy[i,j] = (-Z[i+2,j] + 16*Z[i+1,j] - 30*Z[i,j] + 16*Z[i-1,j] - Z[i-2,j]) / (12*h**2)
zxy[i,j] = (Z[i+1,j+1] - Z[i+1,j-1] - Z[i-1,j+1] + Z[i-1,j-1]) / (4*h**2)
# 边界点(单侧差分,二阶精度)
# 上下边界 (i=0,1,ny-2,ny-1)
for i in [0, 1, ny-2, ny-1]:
for j in range(2, nx-2):
if i == 0:
zy[i,j] = (-3*Z[i,j] + 4*Z[i+1,j] - Z[i+2,j]) / (2*h)
zyy[i,j] = (2*Z[i,j] - 5*Z[i+1,j] + 4*Z[i+2,j] - Z[i+3,j]) / h**2
elif i == 1:
zy[i,j] = (Z[i+1,j] - Z[i-1,j]) / (2*h)
zyy[i,j] = (Z[i+1,j] - 2*Z[i,j] + Z[i-1,j]) / h**2
elif i == ny-2:
zy[i,j] = (Z[i+1,j] - Z[i-1,j]) / (2*h)
zyy[i,j] = (Z[i+1,j] - 2*Z[i,j] + Z[i-1,j]) / h**2
else: # i == ny-1
zy[i,j] = (3*Z[i,j] - 4*Z[i-1,j] + Z[i-2,j]) / (2*h)
zyy[i,j] = (2*Z[i,j] - 5*Z[i-1,j] + 4*Z[i-2,j] - Z[i-3,j]) / h**2
# 边界处 x 方向仍用中心差分
zx[i,j] = (-Z[i,j+2] + 8*Z[i,j+1] - 8*Z[i,j-1] + Z[i,j-2]) / (12*h)
zxx[i,j] = (-Z[i,j+2] + 16*Z[i,j+1] - 30*Z[i,j] + 16*Z[i,j-1] - Z[i,j-2]) / (12*h**2)
zxy[i,j] = (Z[i+1,j+1] - Z[i+1,j-1] - Z[i-1,j+1] + Z[i-1,j-1]) / (4*h**2)
# 左右边界 (j=0,1,nx-2,nx-1),内部 i 范围
for j in [0, 1, nx-2, nx-1]:
for i in range(2, ny-2):
if j == 0:
zx[i,j] = (-3*Z[i,j] + 4*Z[i,j+1] - Z[i,j+2]) / (2*h)
zxx[i,j] = (2*Z[i,j] - 5*Z[i,j+1] + 4*Z[i,j+2] - Z[i,j+3]) / h**2
elif j == 1:
zx[i,j] = (Z[i,j+1] - Z[i,j-1]) / (2*h)
zxx[i,j] = (Z[i,j+1] - 2*Z[i,j] + Z[i,j-1]) / h**2
elif j == nx-2:
zx[i,j] = (Z[i,j+1] - Z[i,j-1]) / (2*h)
zxx[i,j] = (Z[i,j+1] - 2*Z[i,j] + Z[i,j-1]) / h**2
else: # j == nx-1
zx[i,j] = (3*Z[i,j] - 4*Z[i,j-1] + Z[i,j-2]) / (2*h)
zxx[i,j] = (2*Z[i,j] - 5*Z[i,j-1] + 4*Z[i,j-2] - Z[i,j-3]) / h**2
zy[i,j] = (-Z[i+2,j] + 8*Z[i+1,j] - 8*Z[i-1,j] + Z[i-2,j]) / (12*h)
zyy[i,j] = (-Z[i+2,j] + 16*Z[i+1,j] - 30*Z[i,j] + 16*Z[i-1,j] - Z[i-2,j]) / (12*h**2)
zxy[i,j] = (Z[i+1,j+1] - Z[i+1,j-1] - Z[i-1,j+1] + Z[i-1,j-1]) / (4*h**2)
# 四个角点(双线性外推近似)
for (i,j) in [(0,0),(0,nx-1),(ny-1,0),(ny-1,nx-1)]:
zx[i,j] = zx[i, 2] if j == 0 else zx[i, nx-3]
zy[i,j] = zy[2, j] if i == 0 else zy[ny-3, j]
zxx[i,j] = zxx[i, 2] if j == 0 else zxx[i, nx-3]
zyy[i,j] = zyy[2, j] if i == 0 else zyy[ny-3, j]
zxy[i,j] = 0.0
return zx, zy, zxx, zyy, zxy
# ========== 4. 曲率计算 ==========
def compute_curvature(zx, zy, zxx, zyy, zxy):
"""计算平均曲率 H 和高斯曲率 K"""
E = 1 + zx**2 + zy**2
H = ((1+zx**2)*zyy - 2*zx*zy*zxy + (1+zy**2)*zxx) / (2 * E**1.5)
K = (zxx*zyy - zxy**2) / E**2
return H, K
# ========== 5. 主程序 ==========
# 网格参数
L = 10.0 # 口径 10mm × 10mm
N = 101 # 网格点数
h = L / (N-1) # 网格间距
x = np.linspace(-L/2, L/2, N)
y = np.linspace(-L/2, L/2, N)
X, Y = np.meshgrid(x, y)
# 采样面形
Z = surface(X, Y)
# 数值导数
zx_num, zy_num, zxx_num, zyy_num, zxy_num = numerical_derivatives(Z, h)
# 数值曲率
H_num, K_num = compute_curvature(zx_num, zy_num, zxx_num, zyy_num, zxy_num)
# 理论导数与曲率(用于验证)
zx_theo, zy_theo, zxx_theo, zyy_theo, zxy_theo = surface_derivatives(X, Y)
H_theo, K_theo = compute_curvature(zx_theo, zy_theo, zxx_theo, zyy_theo, zxy_theo)
# ========== 6. 误差评估 ==========
# 排除边界两圈(边界差分精度较低)
inner = slice(2, -2)
err_zx = np.max(np.abs(zx_num[inner,inner] - zx_theo[inner,inner]))
err_zy = np.max(np.abs(zy_num[inner,inner] - zy_theo[inner,inner]))
err_H = np.max(np.abs(H_num[inner,inner] - H_theo[inner,inner]))
err_K = np.max(np.abs(K_num[inner,inner] - K_theo[inner,inner]))
print(f"内部区域最大斜率误差 zx: {err_zx:.2e}")
print(f"内部区域最大斜率误差 zy: {err_zy:.2e}")
print(f"内部区域最大平均曲率误差 H: {err_H:.2e}")
print(f"内部区域最大高斯曲率误差 K: {err_K:.2e}")
# ========== 7. 可视化 ==========
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 面形
im0 = axes[0,0].imshow(Z, extent=[-L/2,L/2,-L/2,L/2], origin='lower', cmap='viridis')
axes[0,0].set_title('Surface Sagitta z(x,y) [mm]')
plt.colorbar(im0, ax=axes[0,0])
# 平均曲率
im1 = axes[0,1].imshow(H_num, extent=[-L/2,L/2,-L/2,L/2], origin='lower', cmap='RdBu_r')
axes[0,1].set_title('Mean Curvature H [1/mm]')
plt.colorbar(im1, ax=axes[0,1])
# 高斯曲率
im2 = axes[1,0].imshow(K_num, extent=[-L/2,L/2,-L/2,L/2], origin='lower', cmap='RdBu_r')
axes[1,0].set_title('Gaussian Curvature K [1/mm²]')
plt.colorbar(im2, ax=axes[1,0])
# 曲率误差分布
im3 = axes[1,1].imshow(np.abs(H_num - H_theo), extent=[-L/2,L/2,-L/2,L/2],
origin='lower', cmap='hot')
axes[1,1].set_title('|ΔH| Numerical vs Theoretical')
plt.colorbar(im3, ax=axes[1,1])
plt.tight_layout()
plt.savefig('curvature_analysis.png', dpi=150)
plt.show()
运行与验证
依赖环境:Python 3.8+,NumPy,Matplotlib。运行前确认已安装:pip install numpy matplotlib。
执行方式:将上述代码保存为 curvature_analysis.py,在终端运行 python curvature_analysis.py。
预期输出:程序打印内部区域(排除边界两圈)的数值误差,并生成 curvature_analysis.png 四联图。由于网格间距 h=0.1mm,五点中心差分截断误差为 O(h⁴)=1e-4 量级,二阶差分误差为 O(h²)=1e-2 量级,因此预期误差范围:斜率误差约 1e-6~1e-5,曲率误差约 1e-4~1e-3。具体数值取决于浮点舍入与边界处理,但应满足:斜率误差 < 1e-4,曲率误差 < 1e-2。
判定标准:
- 程序无异常退出,四联图正常显示并保存;
- 打印的斜率误差与曲率误差在预期量级范围内;
- 平均曲率图中可清晰看到正弦波纹引起的周期性曲率变化(波纹频率 0.5/mm,对应图上约 5 个周期);
- 误差分布图显示最大误差集中在边界附近,内部区域颜色接近黑色(误差极小),说明中心差分精度良好。
若误差显著偏大(如斜率误差 > 1e-3),检查网格间距 h 是否与 x/y 数组一致,或边界处理代码是否存在索引越界。