题目思考:这题到底在问什么
背景:为什么要测一层薄膜
碳化硅(SiC)是第三代半导体的代表材料,用于新能源汽车的功率器件、射频模块这些「很硬核」的场景。器件的核心结构是衬底上外延生长的一层碳化硅薄膜(外延层)——它的厚度直接决定击穿电压、导通电阻这些关键指标。测厚的方法是红外干涉法:外延层和衬底因掺杂浓度不同而折射率不同,红外光射进去,一部分从外延层表面反射,一部分钻进外延层、从衬底界面反射回来,两束光叠加产生干涉条纹。条纹里藏着厚度信息。
题目给了四个附件:附件 1/2 是同一块碳化硅晶圆在入射角 10° 和 15° 下的反射光谱(7469 个数据点,波数 400~4000 cm⁻¹);附件 3/4 是同一块硅晶圆在两个角度下的光谱。三问层层递进:
flowchart TD
A["红外反射光谱<br/>波数与反射率"] --> B
subgraph S1["问题一 · 理论建模"]
B["只考虑界面单次反射透射<br/>双光束干涉"] --> C["推导厚度的显式公式"]
end
C --> D
subgraph S2["问题二 · 算法与数据"]
D["用问题一的模型处理附件 1/2<br/>算出厚度并检验可靠性"]
end
D --> E
subgraph S3["问题三 · 模型的边界"]
E["多次反射形成多光束干涉<br/>推导必要条件"] --> F["判断附件 3/4 是否出现<br/>建立硅的测厚模型"]
F --> G["若碳化硅数据也受影响<br/>设法消除并修正"]
end
我当时的理解:这是一道「正问题 → 逆问题 → 自检」的题
拿到题的第一晚,我们没有急着写代码,而是把三问翻译成了自己的语言:
- 问题一是一个正问题:给定 (外延层折射率)、(入射角)、(波数)和干涉级次,厚度 是多少?本质是推导一个显式公式,物理课本里的薄膜干涉搬过来,认真处理半波损失和折射角就行。
- 问题二是一个逆问题:手上只有「波数—反射率」表格,、干涉级次 都不直接已知,要从光谱里把它们连同 一起反演出来。这是全题的核心难点:题目特意提醒「折射率不是常数,与掺杂浓度、波长有关」,等于明示「别把 当常数」。
- 问题三是对模型适用性的质问:问题一的「双光束」假设真的成立吗?光在薄膜里其实会弹射很多次。第三问要求推导多光束干涉的必要条件、判断硅晶圆数据里它是否出现、并回头审视碳化硅的结果要不要修正。
没发现的关键点
复盘的时候我才发现到这道题一个很重要的地方:
条纹间距 只能给出折射率与厚度的乘积。 和 是简并的——不知道折射率,就不可能知道绝对厚度。所以这道题真正的战场是:如何把 尽量准确地独立确定下来。我们当时在这个问题上的处理(后文「反思」一节会详谈)恰好是全文最薄弱的一环。
问题一:双光束干涉模型与灵敏度分析
物理图景
只考虑两条路径的光:
- 第一束:在外延层上表面直接反射;
- 第二束:折射进入外延层,在衬底界面反射,再折射出来。
第二束比第一束多走了 的几何路程(斜着走,有效光程是 )。两束光相遇时,相位差取决于这段光程差——同相则亮(相长),反相则暗(相消)。扫描波数,就会看到反射率周期性的起伏,这就是干涉条纹。
模型推导
第一步:折射角。 由斯涅尔定律(空气折射率 ):
第二步:光程差与半波损失。 几何光程差为 。此外,光从光疏介质射向光密介质界面反射时会附加 相位突变(半波损失):
- 若 :两个界面的反射都有半波损失,总效果相互抵消,修正量为 0;
- 若 :只有空气—外延层界面有半波损失,需要补 。
第三步:干涉极值条件。 反射率出现极值(亮纹)时总光程差为波长的整数倍。引入波数 ( 单位 cm),令 为干涉级次(正整数),统一写成:
其中干涉级次修正项 :
这就是问题一的答案:一个四参数显式公式。代入选定参数(,,,,):
灵敏度分析:这个公式怕什么
公式有了,但它是「理想世界」的。真实测量里 和 都有误差,于是我加了一节灵敏度分析——对厚度公式求偏导:
代入上面的参数:( 每偏 1,厚度偏约 0.72 μm;即折射率 1% 的误差会带来约 1% 的厚度误差),(波数标定偏 1 cm⁻¹,厚度偏约 1.9 nm)。
两个有趣的小发现:
- 波数的相对灵敏度恰好是 :,波数错 1%,厚度就错 1%,分毫不差——因为 是纯幂次关系。
- 对 的相对灵敏度是 ,几乎也是 。这说明折射率的误差会一比一地传导进厚度,与前文关于参数简并的分析殊途同归。
顺便暴露一个当时就存在、但论文里没有展开的细节:上图中橙色虚线处()曲线不连续——因为 在这里从 跳变为 。物理上, 意味着衬底界面消失、干涉条纹本身不复存在,所以这个跳变是「模型外推到自己适用域边界」的信号,并非真实行为。灵敏度公式本身也默认 固定,在跳变点附近失效。当时我们没有处理这个奇点,复盘时我给它补了个标注(就是图里那行橙色的字)。
代码讲解:Q1.py
问题一的代码是一个不到 200 行的类 EpilayerThicknessCalculator,把「参数 → 厚度 → 灵敏度 → 可视化」封装成流水线。核心部分如下:
class EpilayerThicknessCalculator:
def __init__(self, n1=2.65, n2=2.68, theta0_deg=15, nu_tilde=1000, m=1):
self.n1, self.n2, self.m = n1, n2, m
self.cos_theta1 = self._calc_cos_theta1() # 预计算,避免重复求值
self.k = self._calc_k()
def _calc_cos_theta1(self):
# 斯涅尔定律 + 三角恒等式
theta0_rad = np.deg2rad(self.theta0_deg)
sin_theta1 = np.sin(theta0_rad) / self.n1
return np.sqrt(1 - sin_theta1 ** 2)
def _calc_k(self):
# 半波损失修正:n2>n1 时两次 π 突变抵消,k=m;否则 k=m-0.5
return self.m if self.n2 > self.n1 else (self.m - 0.5)
def calculate_thickness(self):
# 核心公式:d = k / (2·n1·cosθ1·ν)
self.d = self.k / (2 * self.n1 * self.cos_theta1 * self.nu_tilde)
return self.d
def calculate_sensitivity(self):
# 对 n1 的偏导(含 cosθ1 对 n1 的隐式依赖,链式法则推导)
self.d_dn1 = -self.k / (2 * self.nu_tilde * self.n1**2 * self.cos_theta1**3)
# 对波数的偏导
self.d_dnu = -self.k / (2 * self.n1 * self.cos_theta1 * self.nu_tilde**2)
return self.d_dn1, self.d_dnu设计上有觉得满意的地方:
- 把 的判断收敛到一个函数里。半波损失的分析只写一次,主公式永远是统一形式,避免了「明纹暗纹两套公式」的分裂;
- 灵敏度直接用解析偏导而不是数值差分—— 里 对 的隐式依赖容易在差分里被漏掉,解析式(分母上的 )把它显式地暴露出来了。
可视化部分(plot_sensitivity)围绕默认值做 的参数扫描画曲线——就是上面那张图的原型,代码很常规就不贴了。
问题二:从 7469 个光谱点到一层厚度
问题二是全篇工作量最大的一问。算法以「数据预处理 → 动态参数计算 → 厚度求解 → 结果验证」为逻辑链。
先看数据长什么样
附件 1 的原始数据:7469 个点,波数从 399.67 到 4000.12 cm⁻¹,反射率单位是 %。直接画出来会发现两个问题:
- 背景漂移:反射率整体有个缓慢起伏的「底」,不是平的——这是仪器背景和材料色散的叠加;
- 异常值:存在负反射率、超过 100% 的点(第一行数据原始反射率是 0,基线 16.2,校正后直接 ,物理上显然不对)。
处理分两层:
清洗规则(简单粗暴但有效):负值置 0.0001, 置 99.99,NaN 填 1%,波数 填默认值 1000 cm⁻¹。
ALS 基线校正(在 MATLAB 里用 test.mlx 完成,输出「基线校正结果表」给 Python 用)。ALS(Asymmetric Least Squares,非对称最小二乘)的思想是:拟合一条「只会从下方包住数据」的柔性基线 ,目标函数带两个拉锯项:
第一项是贴合(残差),第二项是平滑(二阶差分惩罚, 取 );权重 迭代更新——残差为正(数据在基线上方,大概率是干涉峰)的点权重调低,让基线「绕开」峰。迭代几轮后,基线就贴着光谱的谷底走了。校正后干涉振荡变得干净对称,为后续峰值检测打了好基础。
折射率:从常数升级为柯西色散
题目明示折射率随波长变化,所以问题二摒弃了问题一的「常数折射率」假设,改用柯西色散公式:
正常透明介质的折射率随波长增加而缓慢下降,柯西公式是它的一阶近似。代码里取 、、,适用范围 0.4~2.0 μm,超范围回落到默认值 1.5。
(先按下不表:这套系数到底是哪种材料的,后文「反思」一节会把它变成一个大型「案发现场」。)
干涉级次怎么定:峰值检测 + 傅里叶交叉验证
公式 里还剩一个未知的 。我的方案是「先测条纹周期,再反推级次」:
- 对反射率序列用
scipy.signal.find_peaks(distance=5, prominence=0.01)找峰,相邻峰波数差的中位数作为主周期; - 再对整条反射率曲线做 FFT,取频谱主峰对应的周期做交叉验证——两个来源的周期如果吻合,说明条纹周期测得可靠;
- 由周期换算干涉级次 。
这一步的物理直觉是:条纹周期 与厚度的关系是刚性的(),而单个峰的绝对级次是柔性的(依赖从哪个峰开始数)。所以「周期定厚度、级次只定基准」比「逐峰数级次」稳健得多。
分偏振的菲涅尔反演
入射角非 0 时,反射率依赖偏振态。我们对三种场景分别建模:
垂直入射(菲涅尔公式的特例):
斜入射 s 偏振 / p 偏振(菲涅尔方程):
斜入射时方程里 同时出现在两边(通过 耦合),没有闭式解,我用不动点迭代:先用垂直入射的结果做初值,迭代 10 次、收敛阈值 ,同时用 MIN_SINR / MAX_SINR 钳位防止全反射附近的数值爆炸。
最后统一代入厚度反演公式:
代码讲解:Q2_10degree.py
问题二的主脚本约 700 行(10° 与 15° 各一份),骨架如下。
数据清洗与读取——把所有「物理上不可能」的值拦在门外:
def load_and_clean_data(file_path):
df = pd.read_excel(file_path, sheet_name="Sheet1")
# 反射率:NaN→1%,负→0.0001,≥100→99.99
df["反射率_(%)"] = df["反射率_(%)"].fillna(DEFAULT_REFLECTANCE * 100)
df["反射率_(%)"] = df["反射率_(%)"].apply(
lambda x: 0.0001 if x < 0 else (99.99 if x >= 100 else x))
df["反射率_R(小数)"] = df["反射率_(%)"] / 100
# 波数≤0→默认值;波长(cm)=1/波数,再转 μm
df["波长_λ(μm)"] = (1 / df["波数_q(cm-1)"]) * 1e4
return df柯西色散折射率——带适用范围守卫与物理钳位:
def calculate_n2_cauchy(lambda_μm):
if not (lambda_min <= lambda_μm <= lambda_max): # 超出适用范围
return DEFAULT_N2
n2 = cauchy_A + cauchy_B / lambda_μm**2 + cauchy_C / lambda_μm**4
return max(1.0001, n2) # 物理合理性:n>1
斜入射的不动点迭代——把「解方程」变成「猜—算—再猜」:
def calculate_n1_polarized(R_polar, lambda_μm, i_rad, is_s_polar=True):
n2 = calculate_n2_cauchy(lambda_μm)
sqrt_R = np.sqrt(max(0.0001, min(R_polar, 0.9999)))
cosi = np.cos(i_rad)
n1_prev = calculate_n1_vertical(R_polar, lambda_μm)[0] # 垂直入射解作初值
for _ in range(iter_max): # 最多迭代 10 次
sinr = (n1_prev * np.sin(i_rad)) / n2
sinr = max(MIN_SINR, min(sinr, MAX_SINR)) # 防全反射
cosr = np.sqrt(1 - sinr ** 2)
denominator = (cosi - sqrt_R * cosr) if is_s_polar else (sqrt_R * cosi + cosr)
n1_curr = n2 * (sqrt_R * cosr + cosi) / denominator if is_s_polar \
else n2 * (cosi + sqrt_R * cosr) / denominator
if abs(n1_curr - n1_prev) < iter_tol: # 收敛即停
break
n1_prev = n1_curr
return max(n1_min, min(n1_prev, n1_max)), n2条纹周期检测与级次估计——峰值中位数周期 + FFT 交叉验证:
def detect_interference_period(df, wave_col="波数_q(cm-1)", reflectance_col="反射率_R(小数)"):
q = df.sort_values(by=wave_col)[wave_col].values
R = df.sort_values(by=wave_col)[reflectance_col].values
peak_indices = signal.find_peaks(R, distance=5, prominence=0.01)[0]
main_period = np.median(np.diff(q[peak_indices])) # 中位数抗异常峰
# FFT 交叉验证主周期
freq = fftfreq(len(q), d=np.mean(np.diff(q)))
fft_period = 1 / freq[freq > 0][np.argmax(np.abs(fft(R))[freq > 0])]
print(f"峰值检测周期:{main_period:.2f},傅里叶验证周期:{fft_period:.2f}")
return main_period, q[peak_indices]这个函数是我在问题二里最喜欢的一段:用两种独立的方法测同一个周期,中位数 + FFT 各自抗不同的噪声模式,互相印证。遗憾的是它当时只负责「打印验证」,结果没有真正反馈到级次决策里(这个后文再吐槽)。
结果与可靠性分析
分「垂直入射 / s 偏振 / p 偏振」三种场景计算,部分结果(10° 入射角):
| 波数 (cm⁻¹) | 垂直入射 e (μm) | s 偏振 e (μm) | p 偏振 e (μm) |
|---|---|---|---|
| 399.6747 | 8.3401 | 8.4742 | 8.4688 |
| 400.1569 | 8.3301 | 8.9782 | 8.4586 |
| 400.6390 | 8.3200 | 8.9404 | 8.4484 |
| … | … | … | … |
| 406.4244 | 8.2016 | 8.6866 | 8.3281 |
可靠性从三个角度验证:
| 检验维度 | 指标 | 结果 |
|---|---|---|
| 一致性 | 同一晶圆 10°/15° 结果偏差 | ≤ 3.75% |
| 残差分析 | 10° 残差均值 / 标准差 | −0.027664 / 0.027341 |
| 残差分析 | 15° 残差均值 / 标准差 | −0.056438 / 0.048978 |
| 拟合优度 | 垂直入射 R² | ≥ 0.968 |
结论:多角度结果高度吻合、残差标准差接近 0、拟合优度达标,垂直入射厚度均值约 8.27 μm。当时我们对这个数字相当满意。


(上面两幅是当时的输出图:厚度随波数的分布、三种入射场景的对比。当时觉得「曲线平滑、结果收敛」就是好结果;一年后我知道这张图里其实藏着本文最重要的疑点——厚度随波数系统性漂移,而物理上厚度应该是个常数。伏笔,「反思」一节见。)
问题三:多光束干涉的必要条件、判断与修正
必要条件的推导
问题一的「双光束」是理想化:光在薄膜两个界面间其实会反复弹射,每次弹到下界面就漏一份光出来,产生一整列透射/反射光束。它们相干叠加,就是多光束干涉。
产生可观测多光束干涉的必要条件,我们分两层表述:
相干条件(对光的要求)——各束光频率相同、振动方向相同、相位差恒定。同一束入射光分振幅而来,前两条天然满足;相位差恒定要求薄膜厚度与折射率在光斑范围内稳定,即界面平行度好、材料均匀。
强度条件(对界面的要求)——如果界面反射率太低,第二次反射的光强就衰减到噪声水平以下,「多光束」退化回「双光束」。定量地说,相邻两次反射振幅衰减因子是反射系数 ,高反射率界面(重掺杂衬底在红外波段的等离子体反射正是如此)才能让十几束光都有存在感。
相邻透射光束的相位差(含两次穿越薄膜的几何光程):
把 束振幅按等比数列衰减、相位按 递增的复数求和起来,透射光强就是经典的 Airy 公式:
多光束干涉的「指纹」是细锐的条纹: 越大,条纹越窄越深(法布里–珀罗干涉仪就是这个原理做到极致的产物)。
附件 3/4 出现多光束干涉了吗
判断依据就是上面的「指纹」。看硅晶圆的光谱(下图):反射率在 405 cm⁻¹ 附近冲到 78%(15° 时 89%),谷底却压到 1% 以下,振荡又深又宽又平滑——这是典型的高反射率界面 + 高精细度的多光束干涉形貌。反观碳化硅数据(问题二),振荡幅度小、叠在一个缓慢的背景上,更接近双光束近似。所以结论:硅晶圆(附件 3/4)出现了明显的多光束干涉,碳化硅数据中该效应微弱。

物理上这也说得通:测试所用硅晶圆的衬底重掺杂,自由载流子使衬底在红外波段呈现类金属的高反射,界面反射比高,多光束效应显著;而碳化硅外延层与衬底同为 SiC、折射率接近(2.55 vs 2.68),界面反射弱,双光束就是很好的近似。
硅外延层厚度:波数差法
对多光束干涉的条纹,相邻峰对应的光程差相差恰好一个波长,于是有非常干净的波数差法:
硅取 ,对相邻峰的波数差逐对计算厚度再取平均。这段代码是全比赛我们写得最快乐的 20 行——问题二折腾了七百行,这里四两拨千斤:
def calculate_thickness(df):
n = 3.42 # 硅的红外折射率
peaks, _ = find_peaks(df['反射率 (%)'].values)
peak_wave_numbers = df['波数 (cm-1)'].values[peaks]
diff_wave_numbers = np.diff(peak_wave_numbers) # 相邻峰波数差
thickness = 1 / (2 * n * diff_wave_numbers) # d = 1/(2n·Δν)
return np.mean(thickness) * 1e4 # cm → μm
论文报告的结果:10° 入射角下平均厚度 1.50 μm(标准差 0.016 μm),15° 下 1.52 μm(标准差 0.018 μm),两角度一致性很好。
这边还有一条峰值波长法的支线:先 Savitzky–Golay 滤波(窗口 5、二阶多项式)去噪,再 find_peaks(height≥10, distance≥15, prominence≥1) 找峰,对每个峰按 赋级次算厚度,取中位数。数据处理上用了 IQR 准则剔异常值(低于 或高于 的反射率视为离群点):
q1, q3 = df_temp['反射率 (%)'].quantile([0.25, 0.75]) # IQR 异常值过滤
iqr = q3 - q1
df_valid = df_temp[(df_temp['反射率 (%)'] >= q1 - 1.5*iqr)
& (df_temp['反射率 (%)'] <= q3 + 1.5*iqr)]
reflectance_smoothed = savgol_filter(reflectance, window_length=5, polyorder=2)
peak_indices, _ = find_peaks(x=reflectance_smoothed,
height=min_reflectance, # 默认 10%
distance=min_peak_distance, # 默认 15
prominence=1)
def calculate_thickness_v2(lambda_μm, n=1.5, k=1):
thickness_μm = (k - 0.5) * lambda_μm / (2 * n) # 半波损失 → k-0.5
return thickness_μm if 0.01 <= thickness_μm <= 10 else np.nan

多光束干涉模拟:把「弹跳的光」画出来
为了验证多光束理论模型本身,我们写了 Q5.py 做正向模拟:给定折射率 、厚度 、波长,把 束透射光的振幅全部算出来相干叠加。菲涅尔系数手工实现:
def Rs(i, r): return -np.sin(i - r) / np.sin(i + r) # s 波反射系数
def Rp(i, r): return np.tan(i - r) / np.tan(i + r) # p 波反射系数
def Ts(i, r): return 2*np.sin(r)*np.cos(i) / np.sin(i + r) # s 波透射系数
def Tp(i, r): return 2*np.sin(r)*np.cos(i) / (np.sin(i+r)*np.cos(i-r)) # p 波
def calculate_interference(N, lambda_, n, h, Ai, a, theta_max, delta_theta, fixed_angle):
i1 = np.arange(-theta_max, theta_max + delta_theta, delta_theta)
r1 = np.arcsin(np.sin(i1) / n) # 折射角
delta = 4 * np.pi / lambda_ * n * h * np.cos(r1) # 相邻光束相位差
As = Asi * Ts(i1, r1) * Ts(r1, i1) # 各级透射振幅
Ap = Api * Tp(i1, r1) * Tp(r1, i1)
# 向量化:用幂矩阵代替循环,rs^(2k)·e^(ikδ) 一步到位
powers = np.arange(N)
rs_pows = Rs(r1, i1)[:, np.newaxis] ** (2 * powers)
rp_pows = Rp(r1, i1)[:, np.newaxis] ** (2 * powers)
exp_terms = np.exp(1j * delta)[:, np.newaxis] ** powers
It_1 = np.abs((As[:, np.newaxis] * rs_pows * exp_terms).sum(axis=1)) ** 2 \
+ np.abs((Ap[:, np.newaxis] * rp_pows * exp_terms).sum(axis=1)) ** 2
# 教材 Airy 公式作对照(不区分偏振)
It_2 = (1-p)**2 / ((1-p)**2 + 4*p*np.sin(delta/2)**2) * Ai**2
return i1, It_1, It_2, ...两个实现细节值得记一笔:
- 向量化求和:各级光束的衰减因子 与相位因子 都是等比数列,用 numpy 的广播幂运算拼成矩阵一次求和,比写 15 次循环快一个量级,代码也干净;
- 双轨验证:分 s/p 偏振严格求和(
It_1)与教材上不区分偏振的 Airy 公式(It_2)同时计算、画在同一张图上对比——两条曲线基本重合,说明「不区分偏振」的简化在我们的参数下是安全的。


回头修正碳化硅的结果
最后一问:碳化硅数据里那「微弱」的多光束干涉要不要修?我们的回答是要——用 Airy 模型对照模拟表明,多光束效应会使双光束模型算出的条纹位置有轻微偏移;扣除该偏移后,修正结果与原双光束结果的偏差在 2% 以内。换句话说:对附件 1/2 而言,双光束近似已经是够好的近似,多光束修正锦上添花。
(「反思」一节会对这段「修正」做一次不留情面的自我解剖——它其实是全文里「看起来最像修正、实际上最没落地」的部分。)
全题结果一览
| 问题 | 对象 | 核心方法 | 结果 |
|---|---|---|---|
| 问题一 | 理论模型 | 双光束干涉 + 灵敏度分析 | ;示例参数下 μm |
| 问题二 | 附件 1/2(SiC) | ALS 基线校正 + 柯西色散 + 菲涅尔反演 | 垂直入射均值约 8.27 μm;一致性偏差 ≤ 3.75%,R² ≥ 0.968 |
| 问题三 | 附件 3/4(Si) | 波数差法() | 10°:1.50 μm(σ = 0.016);15°:1.52 μm(σ = 0.018) |
| 问题三 | 附件 3/4(Si) | 峰值波长法 + Airy 模拟验证 | 多光束干涉显著;与波数差法互证 |
| 问题三 | 附件 1/2(SiC) | 多光束修正 | 修正后与双光束偏差 ≤ 2% |
flowchart LR
A["原始光谱"] --> B["清洗 + ALS 基线校正"]
B --> C["柯西色散<br/>动态折射率"]
C --> D["峰值检测 + FFT<br/>估计干涉级次"]
D --> E["菲涅尔反演<br/>垂直 / s / p"]
E --> F["厚度求解"]
F --> G["一致性 · 残差 · R²"]
G --> H["多光束修正<br/>Airy 对照"]
反思:一年之后,我想按住当时那只手
这是全文我写得最认真的部分。比赛成绩出来后,论文成了「过去时」;但复盘的价值恰恰在于把漂亮结论掀开,看看底下垫着什么。下面每一条,我都尽量给出当时的现场逻辑、以及一年后的诊断。
假设清单和实际实现,是两篇不同的论文
论文「模型假设」第 2 条写着:「碳化硅外延层折射率为常数 2.55,忽略载流子浓度与波长的影响」。
而实际的代码:问题一默认 ;问题二用柯西色散动态计算折射率(约 1.46~1.5),与「常数」假设直接矛盾;同一篇论文里 2.55、2.65、1.5、1.46、3.42 五个折射率各司其职。
诊断:假设清单是写作时才补出来的,建模阶段没人看过它——比赛后期的典型症状,先有结果后补假设,谁也不认识谁。假设本该是建模的起点和约束,最后却成了论文的装饰品。
如果重来:开题第一小时就冻结符号表和参数表,假设清单里每一条后面直接标注「在代码何处生效」。假设变了,清单跟着变,而不是代码先行、清单追认。
材料参数张冠李戴:柯西系数与折射率的「案发现场」
这是全文最实质的物理错误,现在我才发现:
其一,问题二柯西色散用的系数 、——这是**熔融石英(SiO₂)**的经典柯西系数,代码注释里甚至写着「SiO₂ 在 0.4–2.0 μm 范围可忽略」。用它算出来的「外延层折射率」约 1.46,而碳化硅在红外波段的折射率约为 2.55~2.65。相当于给一块碳化硅贴上了玻璃的身份证。
其二,问题三的峰值波长法里 calculate_thickness_v2(lambda, n=1.5, k=1) ——默认折射率 1.5 又是玻璃;同一个小节里,波数差法用的却是硅的正确折射率 3.42。同一个问题、同一份论文、两种折射率并存。
诊断:厚度与折射率成反比(),折射率错一个倍数,厚度就错一个倍数。参数来源没有溯源机制,「能跑出数」掩盖了「数从哪来」。
如果重来:建立一张「材料参数溯源表」:每个常数 → 来源(文献/标准/估算)→ 适用条件。凡是找不到 SiC 专属数据的(红外波段 SiC 色散其实有文献值),宁可做参数扫描给出区间,也不拿别的材料的系数硬顶。
最戏剧性的一幕:两个错误几乎完美抵消
复盘时我重新跑了附件 1/2 的数据(滑动平均去基线后检测条纹峰):
- 碳化硅光谱高波数区的条纹间距中位数约为 Δν ≈ 232 cm⁻¹,两个入射角一致;
- 代入 ,取 SiC 合理折射率 ,得 d ≈ 8.3~8.5 μm。
论文报告的垂直入射均值是 8.27 μm——和稳健估计几乎一致。但实际上还是有问题:
当时厚度公式的实际代入是 ,其中 (SiO₂ 系数),且在谱段左端()锚定 。而「真实」的干涉级次应该是
于是:
级次低估的 1.75 倍,恰好被折射率低估的 1.73 倍抵消了。 本来就是简并的(前文分析过),我们等于是在错误的一组 上拟合出了接近正确的 比值。最终数字好看,纯属参数简并送给我们的巧合——如果真实厚度是 6 μm 或 12 μm,这条流水线会给出面目全非的结果而我们毫无察觉。
诊断:一个模型给出「看起来合理」的数字,不构成它正确的证据。参数简并的模型里,错误可以互相伪装。
如果重来:凡是 这类简并参数,必须做敏感性/退化性检查:固定 扫 ,画出「解空间」,明确告诉读者「绝对厚度完全依赖折射率的先验准确性」。同时用 GB/T 42905-2023 的 FFT 方法作为独立对照——两个方法原理不同的算法给出一致结果,才敢说「可靠」。
硅厚度的 1.50 μm:一个无法复现的结果
同样在复盘时,我重新处理了附件 3/4:对光谱做滑动平均平滑后,用突出度条件筛掉噪声峰,只找到 3~4 个可靠的宽峰(约 405、750、1105、1519 cm⁻¹,相邻间距约 345~410 cm⁻¹)。代入波数差法():
——和论文里的 1.50 μm 对不上。问题出在峰的挑选上:原始数据里纯粹由噪声造成的局部极大值有一千多个(相邻「峰」间距仅 1 cm⁻¹ 量级),find_peaks 不加筛选时会被它们淹没;而当时峰值法的参数组合(限制波段、height≥10、distance=15、prominence=1)恰好筛出一小撮峰,再配上经验性的级次赋值(),得到了 1.5 μm 附近的结果。
诊断:峰值检测类算法的结果强烈依赖滤波窗口、突出度阈值、距离参数、波段范围这组超参数,而当时没有任何一组合适性论证。1.50 与 1.52 的「两角度一致」给了我们虚假的信心——两份数据的噪声结构相似,同一套超参数自然给出相似的错误。
如果重来:条纹分析改用频域方法(对反射率做 FFT,主频即条纹频率),它对单个噪声峰天然免疫;峰值法只作辅助交叉验证。所有超参数做 ±30% 扰动的稳健性检验,结果随参数剧变时,诚实地报告「不稳定」而不是挑一组好看的。
「修正了多光束干涉」——其实没有
论文摘要写「修正碳化硅数据中微弱多光束干涉影响后,与原双光束模型偏差 ≤ 2%」,正文也有「多光束干涉使计算误差较双光束模型降 25%–30%」这样的表述。拆开看:
Q5.py的模拟参数是 、、——一套演示性参数,与碳化硅实测体系(、、红外波段)完全无关。它只能验证「多光束叠加公式实现正确」这一层;「附件 1/2 里的多光束效应已被消除」,从未被验证过;- 真正的「消除影响」应该是:用 Airy 反射率模型(含正确参数)正向拟合附件 1/2 的光谱,与双光束模型的拟合结果比较厚度差异——这一步从未发生;
- 「25%–30%」「12%–15%」这些百分比,没有可复现的对照实验流程支撑。
诊断:赶 deadline 的写作里,「模拟验证了理论」很容易被悄悄升格成「数据完成了修正」。读者(和评审)看到的结论动词,比实际做的事情强了一个等级。
如果重来:每个定量结论后面强制挂一个「证据指针」:哪个脚本、哪个输出文件、哪两张图的对比支撑了它。没有证据链的数字,一律从摘要里删掉。
被题目剧透了
题目第一段就写明:「外延层与衬底因掺杂载流子浓度的不同而有不同的折射率」。这其实是在提示自由载流子的 Drude 色散模型:
重掺杂衬底的折射率在红外波段会随波长显著变化(这正是硅衬底呈现「类金属」高反射、产生强多光束干涉的原因,也是 SiC/SiC 界面反射率随掺杂变化的定量来源)。我们的模型从头到尾用「柯西色散 + 查表常数」糊弄了过去,等于把题目埋的这条物理主线整段跳过。
同样被简化的还有:SiC 在 794~970 cm⁻¹ 的强声子吸收带(Reststrahlen 带——附件 1/2 在这个区间的反射率剧烈摆动,0.8% 到 95%,我们只是当作「异常区」绕开了,没有解释)、表面粗糙度散射、有限光斑与相干长度。这些每一个都可以是模型改进的小节,当时全部让位于时间压力。
诊断:题干里每个看似背景介绍的句子,都可能是评分点。「因载流子浓度不同」这半句话,是这道题物理深度的钥匙,我们从它旁边走过而没有推门。
如果重来:开题时把题干逐句编号,每句标注「信息 / 提示 / 要求」。被标记为「提示」的句子,必须在论文里有正面回应(哪怕只是敏感性讨论)。
工程债:复制粘贴出来的四个脚本
Q2_10degree.py、Q2_15degree.py、Q3_10degree.py、Q3_15degree.py 四个文件,两两之间 90% 相同,差异只是入射角和文件名;魔法常数(MIN_SINR = 0.0001、m_default = 1、distance = 15……)散落在各处;单位在 cm / μm / nm 之间反复横跳,每次换算都是一次开错方次的风险。
还有一个今天看来很可惜的细节:detect_interference_period 里 FFT 验证周期算出来了,却只是 print 出来给人类看,没有参与任何自动决策——写了正确的零件,没有把它装进机器。
诊断:短期看复制粘贴最快;但它让「改一个参数」变成「改四个文件」,也让复盘时的代码考古变得痛苦。三天赛制不是拒绝工程的理由,恰恰是工程的理由。
如果重来:单一入口 + 配置驱动:solve(attachment=1, angle=10, method="vertical"),四份脚本变成四行配置。FFT 验证直接驱动级次选择,构成「检测 → 验证 → 决策」闭环。单位统一在读取层归一化(全部转 cm),只在展示层格式化。
反思汇总
| # | 问题 | 一句话诊断 | 重来的做法 |
|---|---|---|---|
| 1 | 假设与实现脱节 | 假设清单是补写的装饰品 | 参数表与假设同步冻结,逐条挂代码指针 |
| 2 | 材料参数错配 | SiO₂ 柯西系数算 SiC,n=1.5 算 Si | 参数溯源表,无源参数只做区间扫描 |
| 3 | 简并参数的侥幸 | m 与 n 的错误恰好抵消出「正确」数字 | 解空间扫描 + 独立方法(FFT/国标)对照 |
| 4 | 峰值挑选敏感 | 1.50 μm 无法稳健复现 | 频域主方法 + 超参数扰动稳健性检验 |
| 5 | 修正未落地 | 演示模拟被写成「消除了影响」 | 结论-证据链强制挂钩 |
| 6 | 错过物理主线 | Drude 色散被题干剧透仍错过 | 题干逐句编号,「提示」必回应 |
| 7 | 工程债 | 四份复制脚本 + 魔法常数 | 配置驱动单入口,FFT 进决策环 |
结语
认真数一数,这篇复盘里「如果重来」出现了七次。但让我意外的是,重读论文时最先想起的竟是第一晚白板前那四十分钟的争论——我们用光路图互相说服,最后一起写出统一公式 的那个瞬间。那个公式只有五行推导;但「先把物理吵清楚、再让代码服从物理」的工作方式,是这场比赛真正留在身上的东西。
数学建模教会我的,大概是这么一种诚实:模型永远是对真实世界的粗糙近似。三天里我们造了一个能自圆其说的世界,一年后拆开看,有的梁是运气撑住的。而发现「运气撑住的梁」并把它换掉的过程,和当年搭建它一样有乐趣——所以这篇复盘大概还会续写:等哪天我把 Drude 色散补进模型、用 FFT 方法重算出那块晶圆的厚度,再来更新结论。
愿下一束入射的红外光,往返的都是被理解的道路。(´。• ᵕ •。`)
论文与支撑材料清单
- 论文:《基于薄膜干涉模型与灵敏度模型确定碳化硅外延层厚度》(2025 年高教社杯全国大学生数学建模竞赛 B 题提交论文)
- 代码:Q1.py(双光束模型与灵敏度)、Q2_10degree.py / Q2_15degree.py(碳化硅厚度反演)、Q3_分析附件3和附件4.py(波数差法)、Q3_10degree.py / Q3_15degree.py(峰值法)、Q5.py(多光束干涉模拟)、test.mlx(ALS 基线校正)
- 数据:附件 1-4(波数—反射率光谱,各 7469 点,400~4000 cm⁻¹)
- 参考文献:GB/T 42905-2023《碳化硅外延层厚度的测试 红外反射法》等