从一条反射光谱,反推多层光学薄膜的每一层厚度;再从生产记录里找出系统性偏差的规律。
给镀膜工程师用的计算工具。单文件网页,双击即开,所有计算在本机浏览器里完成,不向任何服务器发送数据(工业光谱数据通常是保密的)。
👉 在线试用:https://y-ian-0729.github.io/coating-reverse/
镀完一批膜,用分光光度计扫一条反射曲线。曲线的形状由每层厚度和折射率共同决定。已知材料顺序和折射率,能不能把厚度反解出来?
数学上这是非线性最小二乘。难点不在「算」,而在「算出来的数可不可信」:7 层以上时代价函数有大量局部极小值,随便起个点往下走就会掉进一个「也能拟合得不错」的假解——这是这类工具最常见的坑。
所以这个工具的重点是不给你假的自信:
| 情况 | 工具的反应 |
|---|---|
| 拟合不上 | 🔴 红色,并说明该改什么(层数、层序、折射率、基底) |
| 有多组厚度同样能拟合 | 🟡 黄色,把每一组都列出来,建议补一个斜入射测量 |
| 某几层没被数据定住 | 🟡 黄色,指出是第几层、为什么不唯一 |
| 全部定住 | 🟢 绿色 |
做法是三层防护:多起点 + 盆地跳跃(跳局部坑)、定向搜索竞争解(主动把单层推开再收敛)、逐层可信区间扫描(残差在噪声水平内的厚度范围)。
单次偏差好查,反复出现的偏差难查。把每炉的目标厚度和实测厚度记下来,工具会分离出:
- 整体偏差——有一半偏厚,是监控标定还是速率漂移
- 夹具偏差——某个夹具恒定偏厚(这是最常见的偏差来源,逐夹具单独统计)
- 监控方式差异——晶振 vs 光学,两者偏得不一样说明其中一种标定有问题
- 时间漂移——随炉次/日期单向变化
- 层位置偏差——总是第 1 层或最后一层出问题
- 材料偏差——TiO₂ 偏而 SiO₂ 不偏,指向折射率假设
少于 8 条记录时它只显示「还差 N 条」,不出任何结论、不画图。 从几个点里归纳规律必然是想当然,宁可不说。
不需要安装任何东西,不需要联网。
- 下载
index.html - 双击打开
- 点「填入示例光谱」,再点「开始反推」
示例数据是模拟生成的(页面上有标注),不是真实测量。它用同一套膜系计算代码正向生成,所以能验证反推结果是真能收敛回真值的。
支持三种输入:
- 粘贴文本:每行
波长,反射率,波长单位 nm 或 μm(自动识别),逗号/空格/制表符分隔 - 导入 CSV / TXT:直接选文件
- 手动输入膜系:逐层选材料和初值,加/减层
这是实测结论,不是免责声明。
能做到:7 层 TiO₂/SiO₂ 膜系,在 0.42–0.90 μm、96 个点、0.3% 测量噪声下,单角度正入射反推误差约 1 nm 量级。
做不到:
- 不能替代椭偏仪。反射率只有幅度没有相位,椭偏测两个量,信息量是它的两倍以上。
- 不能反推折射率。折射率必须给定,它只解厚度。而且库里的数据是电子束蒸发非晶膜;如果你用溅射或离子辅助,实际折射率会差 1~3%,这个误差会原样传导成厚度误差。拟合总是差一点点又下不去时,先怀疑折射率而不是算法。
- 层数未知时不能猜层数。材料顺序必须由你声明。
- 厚吸收层精度有限。k 值误差同样会传导。
- 经验库的结论不是因果。它只能说「这批数据里 F3 夹具系统性偏厚 1.8%」,不能证明是夹具造成的——因果要回产线验证。
来自 refractiveindex.info 数据库(CC0 公共领域,Polyanskiy, Sci. Data 11, 94 (2024)),已下载并压缩为本地文件,共 32 个材料条目,覆盖 0.2–25 μm。
包含 TiO₂、SiO₂、Ta₂O₅、Nb₂O₅、Al₂O₃、ZrO₂、HfO₂、Y₂O₃、MgF₂、ZnS、CaF₂、AlN、Si、Ge、BK7、熔石英、钠钙玻璃,以及 Ta/Al/Ag/Au/Cr/Ti/Cu/Ni/W/Pt 等金属。
注意:同一材料常有多个条目(例如 TiO₂ 有 Siefke 电子束膜和 Franta 两个版本,SiO₂ 有熔石英和溅射膜两个版本),它们的色散曲线不同,选错会算出不同的厚度。下拉框按材料分组,条目名保留原始文献信息。
app/core.js 特征矩阵法前向计算(多层膜反射率/透射率)· 零依赖
app/solve.js 反推求解器:多起点 + 盆地跳跃 + LM + 竞争解搜索 + 可信区间
app/diagnose.js 生产记录偏差分析(含 t 分布区间估计)
app/ui.js 光谱解析、绘图、格式化
app/app.js 界面装配
src/template.html 页面骨架
tools/build.js 把所有 JS 和数据内联成单文件 index.html
tools/fetch_nk.py 从 refractiveindex.info 抓取并压缩折射率数据
tools/make-demo.js 生成模拟示例数据
tools/screenshot.js 驱动页面跑一遍示例并截图(README 里的图)
构建:
node tools/build.js # → index.html(单文件,约 356 KiB)
node tests/verify-tmm.mjs # 前向计算 10 项校验
node tests/verify-solve.mjs # 反推求解器 9 项校验
node tests/diagnose.test.js # 记录分析 79 项校验
node tests/verify-page.mjs # 无头驱动页面 22 项校验不是「跑起来没报错」就算对。tests/verify-tmm.mjs 校验的是物理:
- 裸界面反射率 == Fresnel 公式
((n₁−n₂)/(n₁+n₂))²(误差 < 1e-12) - 60° 入射分别等于 Rs 和 Rp(< 1e-10)
- 非偏振 == (s+p)/2(< 1e-14)
- MgF₂ 四分之一波长减反膜的最低反射点落在设计波长 ±10 nm
- 无损耗单层 R + T == 1(< 1e-9)
- 厚吸收层不产生 R > 1(能量守恒)
- 正入射下半波层不改变反射率,四分之一波层必须显著改变
其中「厚吸收层 R > 1」这个检查抓到过一个真实 bug:特征矩阵的相位符号用错,导致厚吸收膜内部指数增长。这个错误在薄层上看不出来,只有把 Si 做成 2000 nm 才暴露。
早期版本的测试把真实厚度直接当迭代起点传给求解器,于是「7 层内误差 < 2 nm」是假绿——真值都喂进去了自然能收敛。现已重写:所有测试从空白或故意偏离的初值出发,并带反向断言(例如「声明 7 层但数据只有 5 层时必须报失败」)。
- 单文件、无构建、零依赖。代价是
index.html356 KiB(内联了折射率库),换来的是双击即开、断网可用、不会因为 CDN 挂了而白屏。 - 求解在浏览器主线程跑,7 层约 1.6 秒,期间界面会卡住(已提前一帧渲染提示)。
- 折射率不外推。超出数据波长范围时返回
null而不是猜一个值。
MIT。折射率数据来自 refractiveindex.info,CC0。

