Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第15章:评价函数:软件实际在优化什么

优化是光学设计软件最能让人感到神奇的地方。

你修改几个设置,选一些变量,点击优化按钮。系统更新。点列图改善,MTF上升。镜头看起来比之前更“设计”了。

这是一种强大的体验。

但这也很容易被误解。

优化并不意味着软件像人一样知道什么是好的镜头。除非你将你的关心用数值表达出来,否则软件不知道你关心什么。它不知道镜头应该可制造、可负担、紧凑、明亮、稳定、优雅或适合特定传感器,除非这些要求出现在模型、约束或评价目标中。

在数值层面,优化通常做的事简单得多:

change variables
→ evaluate a merit function
→ try to make that merit function smaller

就是这样。

难点不在于这句话,难点在于决定评价函数应该衡量什么。

一个糟糕的评价函数可以用完全错误的方式让光学系统变得“更好”。

一个好的评价函数不会取代光学判断,而是将光学判断足够谨慎地编码成数字,让优化器能够处理它。

本章揭开这个计算过程。


1. 优化不是“自动改进”

“优化”这个词让人安心。它暗示软件正在将设计推向某种客观的优秀。

但优化器并不优化“优秀”。

它优化一个定义好的量。

如果这个量是RMS光斑半径,优化器可能会减小RMS光斑半径。

如果是波前RMS,它就会减小波前RMS。

如果是在选定频率和视场点上的MTF误差,它就会改善这些选定的MTF值。

如果这个量忽略了畸变,优化器可能会产生畸变很差的设计。

如果忽略了透镜厚度,优化器可能产生不切实际的形状。

如果忽略了制造公差,优化器可能找到一个只在纸面上可行的脆弱设计。

这是本章最重要的一句话:

优化器跟随评价函数,而不是你的意图。

你的意图必须被翻译成变量、操作数、目标、权重、约束和边界。

这个翻译就是光学设计工作。

软件按钮执行的,是已经编码在评价函数里的判断。


2. 什么是评价函数

评价函数是为设计打分的单一数字。

通常值越低越好。

一个常见的最小二乘形式如下:

M(x)=iwifi(x)2 M(\mathbf{x}) = \sum_i w_i f_i(\mathbf{x})^2

其中:

  • x\mathbf{x} 是设计变量向量;
  • fi(x)f_i(\mathbf{x}) 是第 ii 个评价项误差;
  • wiw_i 是该评价项的权重;
  • M(x)M(\mathbf{x}) 是总评价函数值。

函数 fif_i 通常是一个操作数误差:

fi(x)=qi(x)ti f_i(\mathbf{x}) = q_i(\mathbf{x}) - t_i

其中:

  • qi(x)q_i(\mathbf{x}) 是计算得到的光学量;
  • tit_i 是目标值。

例如:

计算焦距 - 目标焦距
计算RMS光斑半径 - 目标RMS光斑半径
计算畸变 - 目标畸变
计算50线对/毫米处的MTF - 目标MTF

优化器不需要知道这些量的情感含义。它只需要评估 MM,然后寻找能减小 MM 的变量 x\mathbf{x}

所以评价函数是一个翻译层:

光学设计目标
→ 数值误差
→ 加权和
→ 一个标量分数

优化器看到的就是这个标量分数。


3. 变量、操作数、目标、权重、边界

在写代码之前,我们需要一些术语。

变量

变量是优化器允许改变的设计参数。

例子:

  • 面半径;
  • 厚度;
  • 空气间隔;
  • 玻璃选择;
  • 非球面系数;
  • 光阑位置;
  • 像面位置;
  • 透镜元件间隔。

如果一个参数不是变量,优化器就不能改变它。

这听起来显而易见,但很重要。如果焦点位置固定,优化器就无法重新对焦。如果玻璃固定,它就无法通过选择其他材料来减少色差。如果曲率固定,它就不能以不同方式弯曲透镜。

优化器的能力受限于你给出的变量。

操作数

操作数是评价函数中使用的测量量。

例子:

  • 有效焦距;
  • RMS光斑半径;
  • 光线交点误差;
  • OPD RMS;
  • 畸变;
  • 主光线角度;
  • 在选定频率处的MTF;
  • 总长;
  • 最小边缘厚度。

一个操作数本身不一定是设计目标。当它与目标值和权重配对时,就成为目标的一部分。

目标

目标是操作数的期望值。

例子:

EFL应为50毫米
畸变应为0
RMS光斑应尽可能小
50线对/毫米处的MTF应尽可能高
透镜厚度应保持在1毫米以上

有些目标是精确的,有些是软性的偏好。

权重

权重告诉优化器每个操作数有多重要。

如果一个操作数权重很大,优化器会尽力满足它,甚至不惜牺牲其他操作数。

如果一个操作数权重极小,优化器可能很大程度上忽略它。

权重不仅仅是技术设置,它们是用数字书写的设计优先级。

边界

边界定义允许的范围。

例子:

半径必须保持在-200毫米到200毫米之间
厚度必须保持在1毫米到20毫米之间
像面位移必须保持在-2毫米到2毫米之间

边界防止优化器逃向荒谬或不可能的设计。

没有边界,优化器可能找到一个数学上方便、但没有人能制造、装配甚至解释的解。


4. 一个微小的评价函数

我们从可能最简单的评价函数开始。

假设我们想让数字 xx 变成3。

误差是:

f(x)=x3 f(x)=x-3

评价函数是:

M(x)=(x3)2 M(x)=(x-3)^2

代码:

def merit_scalar(x):
    return (x - 3.0) ** 2

最小值出现在:

x=3 x=3

因为此时:

M(3)=0 M(3)=0

这还不是光学设计,但展示了形式。

现在假设我们想要两件事:

  • xx 应为3;
  • yy 应为10。

那么:

M(x,y)=w1(x3)2+w2(y10)2 M(x,y)=w_1(x-3)^2+w_2(y-10)^2

代码:

def merit_two_variables(v):
    x, y = v

    w1 = 1.0
    w2 = 1.0

    error_x = x - 3.0
    error_y = y - 10.0

    return w1 * error_x**2 + w2 * error_y**2

如果增大 w2,优化器会更在意匹配 y=10y=10

这已经是许多光学评价函数的基本结构。唯一的区别在于,真实操作数通常不是简单的变量,它们是通过光线追迹、波前分析、PSF、MTF、几何关系及约束计算得到的。


5. 一个简单的优化器循环

在使用SciPy之前,让我们手工编写最基本的优化器思路。

我们会尝试不同的值并保留最好的。

import numpy as np


def merit_scalar(x):
    return (x - 3.0) ** 2


candidate_values = np.linspace(-10.0, 10.0, 401)

best_x = None
best_m = np.inf

for x in candidate_values:
    m = merit_scalar(x)

    if m < best_m:
        best_m = m
        best_x = x

print("Best x:", best_x)
print("Best merit:", best_m)

这种暴力搜索对于高维问题很慢,但它很诚实。它从概念上展示了优化在做什么:

尝试变量值
计算评价函数
保留较低的评价函数

真正的优化器更高效,它智能地选择下一个尝试值。但它仍然是在尝试减小一个标量评价函数。

记住这一点很重要,它可以防止我们将优化视为某种神秘的智能。


6. 使用SciPy进行最小化

对于实际的Python示例,我们可以使用 scipy.optimize.minimize

import numpy as np
from scipy.optimize import minimize


def merit_two_variables(v):
    x, y = v

    w1 = 1.0
    w2 = 1.0

    error_x = x - 3.0
    error_y = y - 10.0

    return w1 * error_x**2 + w2 * error_y**2


initial_guess = np.array([0.0, 0.0])

result = minimize(
    merit_two_variables,
    initial_guess,
    method="Nelder-Mead"
)

print(result.x)
print(result.fun)

结果应该接近:

x = 3
y = 10

这仍然是一个玩具示例。但现在我们有了与真实设计脚本相同的外层结构:

定义变量
定义评价函数
选择初始猜测
调用优化器
检查结果

光学设计只是在评价函数内部增加了更昂贵的计算。


7. 优化焦点位置

现在让我们让示例光学化。

我们将优化像面位置。

这是光学中最容易理解的优化问题之一:

给定经过镜头后的光线束,
像面应该放在哪里以最小化光斑尺寸?

我们将使用一个简化的光线模型。

想象光线在 z=0z=0 处离开镜头。每条光线有:

  • 高度 yy
  • 斜率 uu

在距离 zz 处的像面上,光线高度是:

yimage=y+zu y_\text{image}=y+zu

如果所有光线交于同一点,光斑就小。

如果它们落在不同高度,光斑就大。

让我们生成一个具有类似球差行为的光线束。边缘光线将相对近轴光线略有不同的聚焦。

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize


def generate_teaching_rays(n_rays=41, pupil_radius=1.0, f0=50.0, spherical=4.0):
    """
    Generate a 1D teaching ray bundle after a lens.

    Parameters
    ----------
    n_rays : int
        Number of rays across the pupil.
    pupil_radius : float
        Maximum ray height at the lens.
    f0 : float
        Base paraxial focal distance.
    spherical : float
        Amount of pupil-dependent focal shift.

    Returns
    -------
    y : 1D array
        Ray heights at z=0.
    u : 1D array
        Ray slopes after the lens.
    """
    y = np.linspace(-pupil_radius, pupil_radius, n_rays)

    # A simple pupil-dependent focus distance.
    # Marginal rays focus at a slightly different distance.
    focus_distance = f0 + spherical * y**2

    # Slope chosen so each ray would cross y=0 at its own focus distance.
    u = -y / focus_distance

    return y, u

这不是一个完整的镜头模型,而是一个受控的教学模型。

它给出的光线并不都严格共享同一个焦点。

现在定义在候选像面上的光线交点:

def ray_heights_at_plane(y, u, z_image):
    return y + z_image * u

在这个一维示例中定义RMS光斑半径:

RMS=1Njyj2 \mathrm{RMS}=\sqrt{\frac{1}{N}\sum_j y_j^2}

def rms_spot_1d(y_image):
    return np.sqrt(np.mean(y_image**2))

评价函数可以是RMS光斑的平方:

def focus_merit(v, y, u):
    """
    Merit function for focus optimization.

    v[0] is the image plane position.
    """
    z_image = v[0]

    y_image = ray_heights_at_plane(y, u, z_image)

    rms = rms_spot_1d(y_image)

    return rms**2

现在运行优化:

y, u = generate_teaching_rays(
    n_rays=41,
    pupil_radius=1.0,
    f0=50.0,
    spherical=4.0
)

initial_guess = np.array([50.0])

result = minimize(
    focus_merit,
    initial_guess,
    args=(y, u),
    method="Nelder-Mead"
)

best_focus = result.x[0]

print("Best focus position:", best_focus)
print("Best merit:", result.fun)

最佳焦点将靠近基础焦距,但并不完全等于每条光线的焦点。因为不同光瞳区域偏好不同的焦平面,优化器选择一个折衷方案。

这已经是一个真实的光学设计概念:

最佳焦点往往是一个折衷,而不是每条光线都变为完美的地方。

这句话很重要。优化常常是平衡相互竞争误差的过程。


8. 绘制评价函数与焦点位置的关系

查看评价函数景观图很有帮助。

z_values = np.linspace(45.0, 58.0, 300)
merit_values = []

for z in z_values:
    merit_values.append(focus_merit([z], y, u))

merit_values = np.array(merit_values)

plt.figure(figsize=(6, 4))
plt.plot(z_values, merit_values)
plt.axvline(best_focus, linestyle="--", label="Optimized focus")
plt.xlabel("Image plane position z")
plt.ylabel("Merit: RMS spot squared")
plt.title("Focus merit function")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()

这张图很有用,因为它消除了神秘感。

优化器不是在“思考焦点”。它是沿着一条曲线移动,寻找更低的值。

对于这个单变量情况,评价函数景观很简单。真实的光学设计问题通常不简单:

  • 多个变量;
  • 多个视场;
  • 多个波长;
  • 多个表面;
  • 非线性光线追迹;
  • 约束;
  • 局部最小值;
  • 性能与可制造性之间的权衡。

但基本思想相同。

优化器搜索一个评价函数景观。
景观由你的光学模型和评价函数创建。

9. 优化前后光斑图

让我们比较一个猜测的焦点和优化后的焦点。

def plot_ray_intercepts(y, u, z_before, z_after):
    y_before = ray_heights_at_plane(y, u, z_before)
    y_after = ray_heights_at_plane(y, u, z_after)

    ray_index = np.arange(len(y))

    plt.figure(figsize=(7, 4))
    plt.plot(ray_index, y_before, "o", label=f"Before: z={z_before:.2f}")
    plt.plot(ray_index, y_after, "o", label=f"After: z={z_after:.2f}")
    plt.axhline(0.0, linewidth=1)
    plt.xlabel("Ray index")
    plt.ylabel("Ray height at image plane")
    plt.title("Ray intercepts before and after focus optimization")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.show()


plot_ray_intercepts(
    y=y,
    u=u,
    z_before=50.0,
    z_after=best_focus
)

优化后的焦点应减小光线交点的散布。

但请注意它不能做什么。如果光线束包含类似球差的行为,它不能使所有光线都完美。移动像面只改变一个变量,它不能消除底层的像差。

这是另一个重要教训:

优化器不能使用对误差无能为力的变量来修复该误差。

如果只能移动像面,优化器只能重新调焦。它不能重新设计镜头。

要减少球差,可能需要曲率变化、非球面项、更多元件、孔径变化、玻璃变化或其他变量。

变量选择决定了优化器能做什么。


10. 添加权重:近轴光线与边缘光线

在光学设计中,并非每条光线或每个操作数都需要相同的权重。

假设我们更关心中心光瞳光线而非边缘光线。或者我们更关心边缘光线,因为它们揭示与孔径相关的像差。

权重改变折衷。

让我们添加光线权重。

def weighted_rms_spot_1d(y_image, weights):
    weights = np.asarray(weights)
    weights = weights / np.sum(weights)

    return np.sqrt(np.sum(weights * y_image**2))


def focus_merit_weighted(v, y, u, weights):
    z_image = v[0]
    y_image = ray_heights_at_plane(y, u, z_image)

    rms = weighted_rms_spot_1d(y_image, weights)

    return rms**2

现在创建强调边缘光线的权重:

pupil_coordinate = y / np.max(np.abs(y))
marginal_weights = 1.0 + 4.0 * pupil_coordinate**2

result_marginal = minimize(
    focus_merit_weighted,
    initial_guess,
    args=(y, u, marginal_weights),
    method="Nelder-Mead"
)

best_focus_marginal = result_marginal.x[0]

print("Best focus, equal weights:", best_focus)
print("Best focus, marginal weighted:", best_focus_marginal)

最佳焦点可能发生偏移。

这不是错误,而是设计优先级变得可见。

权重不是中立的。

它们告诉优化器该关心什么。

这就是为什么在不理解的情况下复制别人的评价函数可能很有风险。你可能会同时复制别人的优先级。


11. 优化薄透镜曲率

现在让我们优化更接近透镜设计的东西。

我们将使用空气中的薄透镜公式:

Φ=(n1)(c1c2) \Phi = (n - 1)(c_1 - c_2)

其中:

  • Φ\Phi 是光焦度;
  • nn 是折射率;
  • c1=1/R1c_1 = 1/R_1 是第一面曲率;
  • c2=1/R2c_2 = 1/R_2 是第二面曲率。

有效焦距近似为:

f=1Φ f = \frac{1}{\Phi}

这是一个近轴薄透镜模型,并非真正的厚透镜优化。它有用之处在于变量-操作数-目标的结构可见。

假设我们想要焦距为50毫米,玻璃指数 n=1.5n=1.5

我们可以优化 c1c_1c2c_2

但有无穷多种曲率对可以产生相同的焦度。例如,很多 (c1c2)(c_1-c_2) 组合能给出相同焦距。

所以我们添加一个弯曲偏好:保持曲率适中。

这就创建了一个有两个目标的评价函数:

  1. 匹配目标焦距;
  2. 避免不必要的强曲率。
import numpy as np
from scipy.optimize import minimize


def thin_lens_efl(c1, c2, n=1.5):
    """
    Thin lens effective focal length in air.

    Curvatures are in 1/mm.
    Focal length is in mm.
    """
    power = (n - 1.0) * (c1 - c2)

    if np.abs(power) < 1e-12:
        return np.inf

    return 1.0 / power


def curvature_merit(v, target_efl=50.0, n=1.5):
    """
    Merit function for a toy thin-lens curvature optimization.

    Variables
    ---------
    v[0] : c1, first surface curvature [1/mm]
    v[1] : c2, second surface curvature [1/mm]
    """
    c1, c2 = v

    efl = thin_lens_efl(c1, c2, n=n)

    # Operand 1: focal length error
    efl_error = (efl - target_efl) / target_efl

    # Operand 2: curvature penalty
    # This discourages extremely curved surfaces.
    curvature_penalty = c1**2 + c2**2

    w_efl = 100.0
    w_bend = 1.0

    merit = w_efl * efl_error**2 + w_bend * curvature_penalty

    return merit

现在用边界优化:

initial_curvatures = np.array([0.02, -0.02])

bounds = [
    (-0.1, 0.1),  # c1
    (-0.1, 0.1),  # c2
]

result = minimize(
    curvature_merit,
    initial_curvatures,
    method="L-BFGS-B",
    bounds=bounds
)

c1_opt, c2_opt = result.x
efl_opt = thin_lens_efl(c1_opt, c2_opt, n=1.5)

print("Optimized c1:", c1_opt)
print("Optimized c2:", c2_opt)
print("Optimized EFL:", efl_opt)
print("Final merit:", result.fun)

这个例子刻意简化了,它不能取代真实的透镜优化。

但它展示了一个核心设计事实:

如果评价函数只要求焦距,
许多设计可能都满足。
额外的操作数会塑造优化器更偏好哪种解。

在真实的光学设计中,评价函数可能包括光斑尺寸、波前误差、畸变、厚度、远心度、主光线角度、MTF、玻璃约束和可制造性限制。

这些操作数不是装饰,它们定义了设计。


12. 硬约束与惩罚

有两种常见的方式处理设计要求。

硬约束

硬约束表示:

该值必须保持在允许的范围内。

例如:

bounds = [
    (-0.1, 0.1),
    (-0.1, 0.1),
]

优化器不允许跳出这些边界。

这对以下变量很有用:

  • 曲率限制;
  • 厚度限制;
  • 空气间隔限制;
  • 调焦行程限制;
  • 孔径约束。

软惩罚

软惩罚在某个量变得不理想时增加代价。

例如:

curvature_penalty = c1**2 + c2**2

这并不禁止曲率,只是不鼓励大曲率。

另一个软惩罚可能是:

thickness_penalty = max(0.0, minimum_allowed - thickness)**2

这会惩罚低于最小值的厚度。

硬约束和软惩罚服务于不同目的。

硬约束定义搜索区域。软惩罚在该区域内表达偏好。

一个成熟的评价函数通常同时使用两者。


13. 评价函数是一种设计论述

人们很容易把评价函数看作一个技术文件。

它不止于此。

评价函数是一个关于什么重要的论述。

如果你给轴上性能的权重重、轴外性能的权重轻,你是在说视场中心更重要。

如果你只优化一个波长,你是在说色差校正不属于这次优化。

如果你目标MTF在10线对/毫米和30线对/毫米,但不管80线对/毫米,你是在说某些细节尺度比其他更重要。

如果你处罚总长,你是在说紧凑性重要。

如果你处罚强曲率,你是在说可制造性或灵敏度重要。

优化器不会质疑这些选择。

它全盘接受。

所以当优化后的设计让你意外时,先问自己:

我实际上让优化器去改善什么?

而不是:

为什么优化器不理解我?

它足够精准地理解了评价函数,并照着执行。

可能是评价函数没有理解你。


14. 局部最小值与起点

许多光学评价函数是非线性的。

这意味着景观中可能存在多个山谷。

优化器可能找到一个附近的局部最小值,而不是全局最佳设计。

初始设计很关键。

这就是为什么光学设计通常从一个合理的起始形式开始:

  • 已知的镜头类型;
  • 近轴布局;
  • 缩比后的现有设计;
  • 大致已经工作的简单设计;
  • 设计者选择的结构。

优化功能强大,但不能替代选择一个合理的起始结构。

一个糟糕的起点可能导致:

  • 差的局部最小值;
  • 不可能实现的几何形状;
  • 光线追迹失败;
  • 极端的曲率;
  • 不可制造的面形;
  • 不稳定的设计;
  • 浪费计算。

一句有用的实用语句:

优化在你给它的设计空间内改进设计。
它不会自动发明正确的设计空间。

这就是为什么第16章将使用库克三片式镜头,而不是要求优化器从零创造镜头。

库克三片式已经是一个有意义的架构,优化可以在此基础上精炼。


15. 评价函数可能自我冲突

真实的评价函数包含许多操作数。

这些操作数可能相互冲突。

例如:

  • 改善边缘视场MTF可能损害中心性能;
  • 减小畸变可能增加场曲;
  • 减小球差可能增加彗差;
  • 让镜头更快可能恶化像差;
  • 减小总长可能增加面曲率;
  • 改善一个波长可能损害另一个波长。

优化器不会通过智慧解决冲突。它通过权重和可用变量解决冲突。

如果两个目标互相斗争,优化器会根据评价函数找到一个折衷。

那个折衷可能是也可能不是你想要的。

这就是为什么有经验的光学设计师不会简单地按一次优化就接受结果。他们会检查设计,调整评价函数,更改变量,添加约束,有时改变架构,然后再次优化。

这个循环是人与数值交互的:

定义目标
→ 优化
→ 检查结果
→ 修订目标或变量
→ 再次优化

那个循环不是自动化失败。它是数值优化成为设计工作的方法。


16. 评价函数的尺度很重要

假设一个操作数以毫米为单位,另一个以波长为单位。

一个误差可能自然在:

0.001

附近,而另一个可能在:

10

附近。

如果你平方两者再未缩放相加,数值较大的量可能占主导地位,即使它在光学上并非更重要。

这就是为什么操作数常常被归一化。

例如:

fEFL=EFLEFLtargetEFLtarget f_\text{EFL} = \frac{\mathrm{EFL} - \mathrm{EFL}\text{target}}{\mathrm{EFL}\text{target}}

这将以相对误差表示焦距误差。

对于光斑尺寸,你可能会除以一个允许值:

fspot=RMS spotallowed RMS f_\text{spot} = \frac{\mathrm{RMS\ spot}}{\mathrm{allowed\ RMS}}

对于畸变:

fdistortion=distortionallowed distortion f_\text{distortion} = \frac{\mathrm{distortion}}{\mathrm{allowed\ distortion}}

这样评价函数更容易解释:

M=iwifi2 M = \sum_i w_i f_i^2

其中每个 fif_i 是无量纲的或至少经过缩放。

良好的缩放有助于优化器和设计师。

糟糕的缩放会让权重产生误导。

当操作数本身的自然量级差异极大时,权重为10的意义很小。


17. 直接优化MTF

既然我们花了数章计算PSF和MTF,自然会问:

能否将MTF直接放入评价函数?

概念上可以。

例如,假设我们希望某频率处的MTF至少为0.5。

一个简单的惩罚可以是:

f=max(0,0.5MTF(f0)) f = \max(0, 0.5 - \mathrm{MTF}(f_0))

然后:

MMTF=wf2 M_\text{MTF} = w f^2

代码:

def mtf_target_penalty(mtf_value, target=0.5):
    error = max(0.0, target - mtf_value)
    return error**2

只有当MTF低于目标时,这才惩罚设计。

但直接MTF优化可能代价昂贵,因为每次评价函数评估可能需要:

光线追迹
→ OPD或光瞳
→ PSF
→ OTF
→ MTF

它还可能对采样选择敏感或有噪声。

这不意味着它是错的,而是说我们应该谨慎。

许多光学工作流使用混合操作数:

  • 基于光线的操作数用于速度和稳健性;
  • 波前操作数用于衍射相关行为;
  • MTF操作数用于最终的成像质量目标;
  • 几何操作数用于可制造性和封装。

一个实用的优化策略往往从较简单的操作数开始,稍后再转向更针对成像质量的操作数。

具体策略取决于设计任务。

对本书而言,重要的是更简单的内容:

只有在正确计算了MTF之后,才能优化它。

这就是为什么第14章在本章之前。


18. 一个同时包含光斑和焦距的玩具评价函数

让我们在代码中组合两个光学目标。

假设我们有:

  1. 一个焦点位置变量;
  2. 目标焦点在50毫米左右;
  3. RMS光斑应尽可能小;
  4. 焦点不应偏离目标太远。

这就给出一个评价函数:

M=wspot(RMS)2+wfocus(zztargetztarget)2 M = w_\text{spot}(\mathrm{RMS})^2 + w_\text{focus}\left(\frac{z-z_\text{target}}{z_\text{target}}\right)^2

代码:

def focus_merit_with_position_penalty(v, y, u, z_target=50.0):
    z_image = v[0]

    y_image = ray_heights_at_plane(y, u, z_image)
    rms = rms_spot_1d(y_image)

    focus_error = (z_image - z_target) / z_target

    w_spot = 1.0
    w_focus = 0.2

    return w_spot * rms**2 + w_focus * focus_error**2

运行:

result_penalized = minimize(
    focus_merit_with_position_penalty,
    initial_guess,
    args=(y, u),
    method="Nelder-Mead"
)

best_focus_penalized = result_penalized.x[0]

print("Best focus without focus penalty:", best_focus)
print("Best focus with focus penalty:", best_focus_penalized)

结果可能向目标焦点位置移动。

这个例子显示了评价函数如何编码权衡。

如果焦点惩罚低,优化器主要最小化光斑尺寸。

如果焦点惩罚高,优化器就会更靠近目标焦点,即使光斑尺寸稍差。

两者没有哪个是绝对正确的。

这取决于设计目标。


19. 观察优化路径

为了教学目的,记录优化器的路径很有帮助。

SciPy允许对许多方法使用回调函数。

history = []


def callback(v):
    history.append(float(v[0]))


history.clear()

result = minimize(
    focus_merit,
    initial_guess,
    args=(y, u),
    method="Nelder-Mead",
    callback=callback
)

history = np.array(history)

plt.figure(figsize=(6, 4))
plt.plot(history, "o-")
plt.xlabel("Iteration")
plt.ylabel("Focus position")
plt.title("Optimization path")
plt.grid(True)
plt.tight_layout()
plt.show()

这张图通常不会出现在最终光学报告中,但对学习非常有用。

它提醒我们优化是一系列尝试,而非一次性动作。

对于一个简单的单变量问题,路径很容易理解。对于有许多变量的真实镜头,路径更难可视化,但原理不变:

优化器反复评估设计并在变量空间中移动。

当优化出现异常时,历史记录能帮助诊断。

是否碰到了边界?是否跳到了一个荒谬的区域?评价函数是否停止改善?变量是否剧烈变化?某个操作数是否改善而另一个变糟?

这些问题比简单地说“优化失败”更有用。


20. Optiland在此阶段的贡献

Optiland在这里很有价值,因为它将真实光学系统数据与分析及优化工具连接起来。

真正的优化工作流不使用我们的玩具函数 generate_teaching_rays()。它使用一个光学系统:

表面
材料
孔径
视场
波长
变量
分析操作数
优化方法

从概念上讲,Optiland工作流看起来像这样:

# Version-checked workflow outline.
# Check your installed Optiland version for exact API names.

# 1. Build or load an optical system.
# optic = ...

# 2. Select variables:
# - surface radii
# - thicknesses
# - image plane position
# - aspheric coefficients
# optic.set_variable(...)

# 3. Define merit operands:
# - effective focal length target
# - RMS spot size
# - distortion
# - MTF targets
# - thickness constraints
# merit = ...

# 4. Choose optimizer and settings.
# optimizer = ...

# 5. Run optimization.
# result = optimizer.optimize()

# 6. Analyze before and after:
# - layout
# - spot diagram
# - wavefront
# - PSF
# - MTF
# - geometry constraints

准确的API可能跨版本变化,但概念是稳定的。

Optiland不仅仅是按下一个优化按钮。它是一个Python环境,你可以在其中检查允许哪些变量改变、包含了哪些操作数、使用了什么权重,以及设计如何变化。

这种可检查性具有教学价值。

封闭的工作流可能只显示最终的评价函数值。而可检查的工作流让你能够询问这个值意味着什么。


21. 现实的优化报告应显示的不只是评价函数值

较低的评价函数值是不够的。

优化之后,你应该从多个角度检查设计。

至少包括:

优化前后的布局
优化前后的处方
点列图
光线像差曲线
波前图
PSF
MTF
畸变
场曲
面曲率
厚度和空气间隔
孔径间隙

为什么这么多?

因为评价函数值是压缩的。

它可能隐藏了哪些操作数改善、哪些变差。

设计可能减小了RMS光斑但增加了畸变。可能改善了轴上MTF但损害了视场性能。可能改善了一个波长但损害了另一个。可能在光学上看起来不错但厚度不可能实现。

所以不要只问:

评价函数值下降了吗?

而要问:

设计对于实际成像任务真的变好了吗?

第二个问题需要光学判断。

这是软件强大但不够充分的又一处。


22. 示例:分别报告操作数

在我们玩具般的焦点示例中,我们可以报告优化前后的RMS光斑。

def report_focus_solution(y, u, z_values):
    for label, z in z_values:
        y_image = ray_heights_at_plane(y, u, z)
        rms = rms_spot_1d(y_image)

        print(f"{label}")
        print(f"  focus position: {z:.6f}")
        print(f"  RMS spot:       {rms:.6f}")
        print()


report_focus_solution(
    y,
    u,
    [
        ("Initial focus", 50.0),
        ("Optimized focus", best_focus),
        ("Penalized optimized focus", best_focus_penalized),
    ]
)

这比只报告最终评价函数值更好。

对于真实镜头,你会报告每个重要操作数:

EFL误差
各视场RMS光斑
各视场RMS波前
选定频率处的MTF
畸变
最小厚度
总长

没有操作数分解的最终评价函数值,就像一场考试只看总分而不看哪些题做错了一样。

它可能有用,但不足以理解设计。


23. 优化你喜欢的图的风险

一旦我们知道如何计算MTF,就很容易想要不断优化直到MTF图看起来更好。

这是可以理解的,但我们需要小心。

一张图是选定数据的视图。它可能代表:

  • 一个波长;
  • 一个视场点;
  • 一个调焦设置;
  • 一个方向;
  • 一个频率范围;
  • 一种归一化;
  • 一种孔径;
  • 一个采样条件。

改善那张图并不自动改善整个光学系统。

例如,优化某视场点的MTF可能损害另一个视场点。优化一个空间频率可能不会改善另一个。优化单色MTF可能使彩色性能很差。

所以更安全的规则是:

不要优化一个显示图。
优化一组定义好的光学需求。

显示图帮助你检查结果,但它不应无声地定义整个设计目标。


24. 优化与可制造性

一个纯粹的光学评价函数可能产生数学上诱人但实际不讨喜的设计。

例子:

  • 非常陡的曲率;
  • 极薄的元件;
  • 微小的空气间隔;
  • 过大的元件;
  • 强烈的非球面偏离;
  • 昂贵或无法获得的玻璃选择;
  • 对公差高度敏感;
  • 难以测试的面形;
  • 机械封装冲突。

可制造性不会自动包含在光学性能中。

如果它重要,就必须以约束、边界、惩罚、玻璃目录、公差分析或后期的设计评审形式出现。

镜头不会因为评价函数值低而完成。

只有当光学性能、机械可行性、制造限制、成本和公差行为整体合理时,镜头才更接近可用。

本书侧重于计算,而非完整的生产工程。然而,警告仍应放在这里,因为优化很容易产生过度自信。

数值最优并不意味着实际最优。

25. 评价函数设计是一个渐进过程

在实际工作中,你很少第一次就能写出完美的最终评价函数。

更现实的顺序是:

从简单的目标开始
→ 优化
→ 检查失效模式
→ 添加操作数或约束
→ 重新优化
→ 再次检查

例如:

  1. 首先目标焦距。
  2. 然后减小轴上光斑。
  3. 然后包含轴外视场。
  4. 然后包含多个波长。
  5. 然后约束厚度和曲率。
  6. 然后添加畸变目标。
  7. 然后添加MTF目标。
  8. 然后检查公差。

这个渐进过程并不是设计师困惑的标志,而是问题被定义清晰的方式。

随着你了解设计在获得自由度时会如何行动,评价函数也随之演变。

这是一种非常实用的心态:

第一个评价函数揭示问题。
后面的评价函数塑造设计。

26. 基于梯度与无导数优化

有许多优化方法。

在高层次上,区分两个家族很有用。

无导数方法

像Nelder-Mead这样的方法不需要梯度。

它们容易使用,对于粗略问题很有用,但随着变量数量增加可能变慢。

它们的问题:

如果我尝试附近的点,评价函数会变好吗?

基于梯度的方法

像L-BFGS-B这样的方法使用导数信息或近似导数行为。

对于平滑问题,它们可以快得多,特别是变量很多时。

它们的问题:

哪个方向看起来最快地减小评价函数?

传统的光学设计常常使用最小二乘和阻尼最小二乘风格的方法。现代的微分光学框架可以计算通过部分光学系统的梯度,这为更直接的基于梯度的优化打开了大门。

这就是通往第17章的桥梁。

目前,记住实际区别:

优化方法影响搜索如何进行。
评价函数定义了搜索试图在减少什么。

不要将两者混淆。


27. 为什么优化放在PSF和MTF之后

本章接在PSF、OTF、MTF、采样和归一化之后是有原因的。

如果你不理解度量标准,优化它就是有风险的。

假设你的MTF频率轴错了。你可能在为“50线对/毫米”优化,而实际上评估着不同的频率。

假设你的PSF归一化不一致。你可能在奖励能量缩放而不是更好的图像形成。

假设你的弧矢和子午方向标错了。你可能在改善错误的方向行为。

假设你的光瞳掩模错了。你可能在优化一个方形孔径,却以为它是圆的。

优化会放大这些错误。

它不会纠正它们。

所以安全的链条是:

理解计算
→ 验证度量
→ 然后优化该度量

这从长远看并不慢。它可以防止优雅的荒谬。


28. 一个完整的最小优化脚本

下面是一个用于焦点优化的紧凑脚本。

它足够小,可以运行、检查和修改。

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize


def generate_teaching_rays(n_rays=41, pupil_radius=1.0, f0=50.0, spherical=4.0):
    y = np.linspace(-pupil_radius, pupil_radius, n_rays)

    focus_distance = f0 + spherical * y**2

    u = -y / focus_distance

    return y, u


def ray_heights_at_plane(y, u, z_image):
    return y + z_image * u


def rms_spot_1d(y_image):
    return np.sqrt(np.mean(y_image**2))


def focus_merit(v, y, u):
    z_image = v[0]

    y_image = ray_heights_at_plane(y, u, z_image)

    rms = rms_spot_1d(y_image)

    return rms**2


def report_focus_solution(y, u, z_values):
    for label, z in z_values:
        y_image = ray_heights_at_plane(y, u, z)
        rms = rms_spot_1d(y_image)

        print(f"{label}")
        print(f"  focus position: {z:.6f}")
        print(f"  RMS spot:       {rms:.6f}")
        print()


def plot_focus_merit(y, u, best_focus):
    z_values = np.linspace(45.0, 58.0, 300)
    merit_values = np.array([
        focus_merit([z], y, u)
        for z in z_values
    ])

    plt.figure(figsize=(6, 4))
    plt.plot(z_values, merit_values)
    plt.axvline(best_focus, linestyle="--", label="Optimized focus")
    plt.xlabel("Image plane position z")
    plt.ylabel("Merit: RMS spot squared")
    plt.title("Focus merit function")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.show()


def plot_ray_intercepts(y, u, z_before, z_after):
    y_before = ray_heights_at_plane(y, u, z_before)
    y_after = ray_heights_at_plane(y, u, z_after)

    ray_index = np.arange(len(y))

    plt.figure(figsize=(7, 4))
    plt.plot(ray_index, y_before, "o", label=f"Before: z={z_before:.2f}")
    plt.plot(ray_index, y_after, "o", label=f"After: z={z_after:.2f}")
    plt.axhline(0.0, linewidth=1)
    plt.xlabel("Ray index")
    plt.ylabel("Ray height at image plane")
    plt.title("Ray intercepts before and after focus optimization")
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.show()


# Build teaching rays
y, u = generate_teaching_rays(
    n_rays=41,
    pupil_radius=1.0,
    f0=50.0,
    spherical=4.0
)

# Optimize image plane position
initial_focus = 50.0

result = minimize(
    focus_merit,
    np.array([initial_focus]),
    args=(y, u),
    method="Nelder-Mead"
)

best_focus = result.x[0]

# Report
report_focus_solution(
    y,
    u,
    [
        ("Initial focus", initial_focus),
        ("Optimized focus", best_focus),
    ]
)

# Plot
plot_focus_merit(y, u, best_focus)
plot_ray_intercepts(y, u, initial_focus, best_focus)

代码本身并不重要,结构才重要。

它包含:

变量:像面位置
操作数:RMS光斑半径
目标:尽可能小
评价函数:RMS光斑平方
优化器:数值搜索
报告:优化前后的RMS

这就是光学优化的骨架。

真实的光学设计添加更多变量和更多操作数,但不会跳出这个逻辑。


29. 这在完整计算链中的位置

现在我们可以将优化加入链条:

镜头处方
→ 变量
→ 光线追迹 / 波前 / PSF / MTF分析
→ 操作数
→ 评价函数
→ 优化器更改变量
→ 更新的镜头处方

这不是一条直线,而是一个循环:

处方
→ 分析
→ 评价
→ 变量更新
→ 新处方
→ 再分析

该循环不断重复,直到优化器停止。

它可能因为以下原因停止:

  • 评价函数值足够低;
  • 改善变得微小;
  • 达到迭代限制;
  • 变量碰到边界;
  • 计算失败;
  • 用户停止它。

一次完成的优化运行不自动意味着设计完成。

它是一个必须接受检查的候选设计。

这是成熟解读优化输出的方式:

优化器产生了较低的评价函数值。
现在我们必须问什么改变了,什么改善了,什么恶化了,以及结果在光学和实际意义上是否仍然合理。

30. 本章为什么做准备

第16章将用一个完整示例连接整本书。

重点将不仅仅是展示库克三片式镜头,而是走完整个计算路线:

处方
→ 布局
→ 光线
→ 光斑
→ 光线像差曲线
→ OPD
→ 光瞳函数
→ PSF
→ MTF
→ 优化
→ 前后对比

本章为我们提供了那个最终案例研究所需的词汇量。

当我们在第16章看到优化后的设计时,我们会知道该问什么:

  • 允许哪些变量改变?
  • 包含了哪些操作数?
  • 使用了什么目标?
  • 使用了什么权重?
  • 哪些约束或边界是激活的?
  • 评价函数值下降了吗?
  • 光学性能实际改善了吗?
  • 有什么东西变得不那么实际了吗?
  • 哪些图确认了改善?

这就是看着软件优化与理解优化之间的区别。


本章摘要

优化不是自动的光学智慧,而是由评价函数引导的数值搜索。

常见的最小二乘评价函数形式为:

M(x)=iwifi(x)2 M(\mathbf{x}) = \sum_i w_i f_i(\mathbf{x})^2

其中 x\mathbf{x} 包含设计变量,fif_i 是操作数误差,wiw_i 是权重。

主要组成部分是:

变量:优化器可以改变什么
操作数:设计评价什么
目标:期望值是什么
权重:什么更重要或更不重要
边界:允许什么值
约束或惩罚:什么必须被阻止或不鼓励

一个简单的焦点优化可以通过改变像面位置来最小化RMS光斑尺寸。一个简单的薄透镜示例可以优化曲率以匹配目标焦距,同时惩罚过大的曲率。这些示例不是完整的镜头设计,但它们揭示了机制。

最重要的实际教训是:

优化器跟随评价函数,而不是你的意图。

因此,设计师的任务是将光学目标转化为准确、缩放、加权、有边界且可检查的评价函数。

优化应放在PSF和MTF之后,因为糟糕的度量标准在被优化时会变得更糟。在优化之前,我们必须知道计算量意味着什么。

下一章将使用一个完整的光学示例将整个链条连接起来。

生成的验证图形

第15章评价函数检查生成的焦点评价函数曲线

来源与验证说明

本章中的最小二乘评价函数是一个教学模型。商业和研究性的镜头设计工作流可能使用多种操作数类型、约束、边界、公差项、可制造性惩罚以及算法选择。这里保留的事实主张较窄:优化器遵循明确编码的目标和约束,而不是未明说的设计意图。