
做光学仿真这几年FDTD时域有限差分应该是我用得最多、也最常推荐给别人入门的算法了。无论是做超表面、光子晶体、纳米天线还是简单的多层膜结构Lumerical FDTD 这套工具几乎都能覆盖。但很多刚接触这行的人拿着软件教程一步步点下来能跑通一个案例却根本不清楚为什么边界条件要这样设、网格为什么不能乱加密、仿真发散到底是谁的锅。这篇文章我就把自己实际跑仿真这些年积累的经验从原理到实操再到踩坑一次性整理清楚。这篇文章适合正在学习 Lumerical FDTD、做超表面或微纳光学仿真、或者被“仿真发散”折磨得想摔键盘的朋友。我会用尽量直白的语言把 FDTD 的核心原理讲明白然后给出一套可以直接照着做的完整仿真流程最后把最常见的报错、发散问题和排查方法整理成一张速查表。1. 先搞明白FDTD到底在算什么越基础的东西越别跳过去1.1 从麦克斯韦方程组到网格上的“接力赛”FDTD 的全称是 Finite-Difference Time-Domain翻译过来就是时域有限差分。很多教程一上来就甩出麦克斯韦方程组、Yee 网格、Courant 稳定性条件这些名词把初学者直接劝退。其实它的核心思想非常朴素空间被切成一个个小方格时间被切成一小步一小步然后让电磁场在网格上一个格子一个格子往下“接力”演变。打个比方你在操场上画了很多格子然后在第一格放一个初始振幅规则是每个格子下一时刻的值由它周围几个格子的当前值算出来。一开始只有第一格有信号跑一步邻近的格子有了微小响应再跑一步信号像水波一样向外扩散。FDTD 做的就是这件事只是它算的不是操场上的人而是电场 E 和磁场 H 在空间网格上的分布。这就是为什么它叫“时域”方法直接在时间轴上一步步推进。Yee 网格是这个方法的关键。它把电场和磁场交错放置E 在棱边上H 在面心上空间上差半个网格时间上也差半个时间步。这样做的好处是麦克斯韦方程组的旋度方程可以被自然地离散化成一组差分方程而不会产生额外的数值色散。1.2 为什么“跑得稳”比“算得准”更优先考虑稳定性条件用 FDTD 仿真首先要保证数值稳定。这个稳定指的是随着时间步推进场值不会越算越大最终变成 NaN 或者永远发散。Yee 算法有一个著名的 Courant-Friedrichs-LevyCFL稳定性条件c * dt ≤ 1 / sqrt(1/dx² 1/dy² 1/dz²)翻译成人话就是时间步不能太大否则在一个时间步内电磁波走得比一个网格还远信息“跑过头”了数值上就会崩掉。三维均匀网格下粗略估算dt 要小于等于 dx 除以 c 乘根号三。这就是为什么仿真步数经常要跑几千甚至几万步因为每一步的物理时间极短。很多人在做 Lumerical FDTD 学习的时候习惯直接改模拟时间 simulation time发现跑很久还不收敛就把时间加大。但如果网格很细而时间步没有跟着调你会发现怎么跑都不稳。好在这个问题 Lumerical 会自动根据 CFL 条件设置 dt所以大多数情况下不会因为 dt 出错。但如果你自己写 FDTD 代码或者在别的软件里手动调 dt这是第一道坎。1.3 收敛判断监视器是“眼睛”不是看花里胡哨的场图仿真的最终目标是让电磁场演化到稳定状态。什么算稳定对监视器monitor来说就是它记录的场值随时间不再发生变化。Lumerical 里有 auto shut-off 功能当所有监视器里的场强衰减到一定阈值以下默认好像是 1e-5 级别自动停止仿真这样不用傻傻跑完整个时间。如果你发现仿真时间设得很长但结果总是出现奇怪的振荡说明结构里的能量衰减不够快。常见原因是高 Q 腔体或者强谐振结构能量在里面来回震荡需要很长时间才能衰减下去。这时候可以适当增加 simulation time或者检查监视器是否放在了正确位置。2. 仿真之前先定方案三步搭出一个靠谱的 FDTD 模型2.1 建模之前先想清楚三个问题很多新手一上来就打开 Lumerical FDTD急着画结构、放光源。我建议先花十分钟想清楚三个问题你在算什么物理量结构周期是多少工作波长附近材料的折射率实部和虚部是多少这三个问题直接决定了后续所有设置。第一个问题决定监视器和分析组的类型。算反射率和透射率用平面波光源加上频率监视器就可以了算远场散射分布需要用到远场投影算模式场分布则需要模式展开监视器或者 flux 监视器加模式分解。第二个问题决定模拟区simulation region的大小。对于周期性结构仿真区域只需要一个周期大小然后设置周期边界条件。对于非周期结构比如孤立纳米粒子需要在周围加 PML 边界并且模拟区要留出足够的空间避免结构边界与 PML 距离太近。第三个问题决定材料模型的准确性。Lumerical 里材料库默认的 Si、SiO2、Au、Ag 等参数在可见光和近红外波段是可靠的。但如果是自己拟合的材料数据比如某种掺杂半导体或钙钛矿薄膜务必检查介电常数实部和虚部的符号是否正确。材料的虚部如果是正的但特别大意味着损耗高仿真容易收敛慢。2.2 边界条件的本质PML 不是简单的“吸收”PML 是完美匹配层Perfectly Matched Layer的缩写它的作用是把射向边界的光“吃掉”不产生反射模拟开放空间。但 PML 并非完美它在低频斜入射时会产生一定反射。为了避免这种反射结构应该和 PML 层之间至少留出半个波长的距离。一个很容易踩的坑在使用 Bloch 边界条件时很多人不小心把边界类型设成了 PEC 或 PMC。PEC 是理想导体电场在边界上强制归零PMC 是理想磁壁磁场归零。如果你的结构是周期性的却用了 PEC结果肯定不对。特别是做超表面仿真时周期边界设置的波矢方向必须与入射角度、偏振方向对应起来。更有迷惑性的是 Lumerical 里的 periodic、bloch、symmetric/anti-symmetric 这几个选项。对称边界可以减小一半甚至四分之一的仿真区域但前提是你的结构和光源也满足对应的对称性。用对称边界的时候光源位置要特别注意不是所有模式都能在对称面上放对偏振。2.3 网格设置精度和内存的博弈网格是 FDTD 仿真中最核心的精度控制参数。网格越细数值色散越小结果越准确但内存和时间也随之暴涨。三维仿真一个常见的问题是内存占用网格数量每增加一倍内存会大致增加八倍。在实际仿真中我通常的做法是先粗跑一遍把整个结构的透射谱大致趋势算出来再用更细的网格精跑。Lumerical 支持 mesh override region可以在局部加密。比如说金属纳米颗粒附近需要处理等离子体共振场分布变化剧烈那就应该在颗粒外延 10~20 nm 的范围内加密网格而基底部分折射率变化平缓就没必要用同样细的网格。具体网格尺寸怎么定一个经验法则是每个波长至少划分 15~20 个网格也就是 dx ≈ lambda / (20 * n)。对于高折射率材料n 大波长在介质中变短网格要更细。如果有曲率较大或尖锐边缘的结构网格还得进一步加密。一个保守的验证方法是把网格从粗到细扫描看关键波长的物理量比如透射率是否收敛。比如用 5 nm、3 nm、2 nm 分别跑一次如果后两者结果几乎一致说明网格已经收敛。3. 实操走一遍用 Lumerical FDTD 做一个超表面单元仿真3.1 从结构建模到光源设置细节全步骤拆解我以超表面最常见的硅纳米柱阵列为例演示一个完整的 FDTD 仿真流程。运行环境是 Lumerical FDTD Solutions 2020 之后的版本其他版本界面略有不同但逻辑一致。第一步是定义单位。进入 Model 窗口后先在布局中设置好材料。选择 Si或自己导入折射率数据在结构库里添加一个圆柱体或者长方体放置在基底上。注意 Lumerical 默认的单位是微米如果你习惯用纳米可以在布局编辑器的单位设置里改。第二步设置仿真区域。select simulation region 后把 x 和 y 的尺寸设为一个周期比如 500 nm x 500 nmz 方向从基底下方 100 nm 延伸到柱顶上方 500 nm。这样设置的好处是x/y 方向用 periodic 边界z 方向用 PML仿真区域足够容纳入射光和结构散射场。第三步放置光源。用 plane wave 光源从结构上方垂直入射。设置 wavelength range 为你要计算的谱宽比如 400 nm~800 nm。入射方向默认是向 -z偏振方向设为 x。这一步经常有人搞错偏振方向等到分析偏振转换效率时才发现数据不对。第四步放置监视器。透射监视器放在结构下方基底内反射监视器放在光源上方远场监视器则放在结构外部并设置好半径和采样角度。如果你要算吸收谱还需要额外放置一个包围整个结构的 box 监视器用于计算净流入的功率。第五步设置 mesh。我习惯加一个 mesh override region 覆盖纳米柱周围 50 nm 的范围网格步长设为 3 nm其余区域用默认网格加上 auto non-uniform 的选项。这样既保证了结构区域的精度又避免了全区域细网格导致内存爆炸。第六步运行仿真。在检查完所有边界条件和材料后按下 run。如果一切顺利监视器里的场会在几千步内收敛。3.2 分析模式展开与透射谱提取为什么要用 mode expansion跑完仿真并输出场后默认的透射率监视器只是简单地计算通过一个面的总功率流。但对于超表面来说我们通常更关心透射光中有多少是 0 级衍射多少是高阶衍射以及各阶次的偏振态。这时候就需要用到 mode expansion monitor也就是把监视器上的电磁场分解成各个模式的叠加。Lumerical 的 mode expansion 功能会在你选定的频点或频带内计算各阶模式的透射系数。具体操作是在监视器上右键选择 mode expansion然后设置频点数、模式数以及需要分解的模式阶次。对于周期结构模式阶次对应不同的衍射级次对于波导结构则对应不同的导波模式。这个分析功能非常实用。有一次我在做介质超表面透镜总透射率挺高但聚焦效率偏低。用 mode expansion 一看发现很大一部分能量跑到了一阶衍射级而不是集中在 0 级的透射光里。这就是设计的相位分布有问题而不是实验加工的问题。如果没有 mode expansion我可能还在那里怀疑是金属损耗还是基底反射。3.3 参数扫描与脚本控制别把所有结构都手动跑一遍做超表面参数优化时通常需要扫描纳米柱的半径或者高度。手动一个个改参数再去运行效率极低。Lumerical 支持参数扫描和脚本批处理。最简单的做法是在 layout 里把结构参数定义为变量比如 r 和 h然后在脚本里用 for 循环改变变量并逐次运行保存结果。一个典型的扫描脚本结构是定义变量数组比如半径从 60 nm 到 140 nm步长 10 nm。循环内修改结构属性 setnamed(cylinder, radius, r)。运行仿真读取各个监视器的结果把透射反射数据存成文件。循环结束后把所有结果汇总画出光谱图。这种做法比手动快很多而且方便后续导入 MATLAB 或 Python 进行后处理。不过要注意脚本扫描时仿真区域和网格最好保持不变否则不同半径的结果之间就缺乏可比性。4. 仿真发散不收敛这个排查流程救了我很多次4.1 发散并不总是“算错了”先区分数值发散和物理发散很多朋友遇到 Lumerical 报发散错误divergence detected第一反应是自己模型建错了。其实发散分两类。一类是真正的数值不稳定表现为场强随时间步增加快速增大很快变成 NaN另一类是物理上能量集中在结构里比如高 Q 腔谐振仿真时间不够导致结果看起来“很怪”。数值发散的原因通常是材料模型出了问题。Lumerical 内置的介电常数模型特别是 Drude 模型或者色散材料模型在某些频点可能出现介电常数虚部为负的情况这在数值上会产生增益而不是吸收导致场强指数增长。解法是限制材料的仿真频率范围或者改用更简单的常折射率模型来测试。另一个常见的数值发散原因是 PML 设置不当。如果 PML 层数太少或者结构离 PML 太近反射波会和入射波相互作用产生驻波严重时也会表现为不收敛。实际操作中把 PML 层数从默认的 8 增加到 16或者把仿真区域稍大一些往往就能解决。4.2 监视器结果跳动先看能量衰减曲线如果你没有遇到发散但得到的透射谱很毛糙、有抖动一般是仿真没有跑够时间就提前停了。这时去查看监视器里的 time signal看能量的包络是不是已经衰减到接近零。如果没有手动增大 simulation time。这在高 Q 超表面、回音壁模式、光子晶体缺陷腔中特别常见。有一次我做氮化硅微环谐振腔Q 值预估有 1e5但仿真时间只设了 10000 fs结果谐振峰的透射率低得离谱。后来把时间加到 200000 fs谐振峰的深度和宽度才稳定下来。代价是计算时间显著增加这类结构需要用谐振腔专用的计算方法比如把连续光源换成窄脉冲再配合滤波器提取稳态场。4.3 常见问题排查速查表现象可能原因排查方向仿真直接发散材料损耗为负数值不稳定检查材料实部虚部符号限制频率范围透射率总和不等于 1监视器放在结构内部把透射监视器移到均匀介质区域谐振峰不对仿真时间不够增大 simulation time观察时域信号衰减结果随网格变化大网格未收敛做网格收敛性测试局部加密关键区域远场图异常远场监视器离结构太近将远场监视器放到结构外至少一个波长处周期结构无衍射峰边界条件设错检查 Bloch 周期边界确认波矢方向和偏振这六个问题覆盖了日常 FDTD 仿真绝大多数的坑。当然还有更复杂的情况比如多层介质结构中的自干涉、布里渊区折叠等但先排查上面几项能够解决大部分“仿真结果对不上实验”的怀疑。4.4 材料模型要追根溯源我的一次金纳米天线“翻车”经验去年我用 FDTD 仿真金纳米天线的局域表面等离激元共振结果无论是共振波长还是近场增强倍数都和文献差得离谱。排查了很久最后发现是金的介电常数数据版本不对。Lumerical 材料库中金的模型有好几套有些是 Palik 的实验数据有些是 Johnson Christy 的经典数据不同数据之间在可见光波段差别很大。从那之后我每次仿真金属结构都会先跑一个简单的消光光谱和文献对比。如果偏差超过几个纳米马上换材料数据源而不是继续微调结构参数。这件事也提醒我FDTD 仿真的下限是算法上限是材料参数的准确性。算法再精确输入的材料数据如果不对结果就没有任何意义。5. 用 FDTD 能做什么三个我实际做过且推荐练习的场景5.1 超表面周期边界与相位调控超表面算是 FDTD 最典型的应用场景之一。我最早做超表面聚焦透镜时就是靠 FDTD 一个一个扫描纳米柱的半径和高度得到相位库再根据透镜的相位分布把纳米柱排布在基底上。整个过程最核心的仿真部分就是单个周期单元的透射相位和振幅随结构参数的变化。这里有个容易忽视的坑周期单元仿真默认入射光是平面波而实际超表面透镜中每个单元的入射光夹角并不完全一样。对于小数值孔径的透镜这个近似是成立的但对于大数值孔径可能需要用布洛赫边界条件来模拟斜入射或者直接用更大的超胞仿真把邻近单元的耦合效应算进去。FDTD 在超表面仿真中的优势是能够处理任意形状的单元结构和复杂的层状材料劣势是计算大尺寸结构的开销很大。5.2 光子晶体波导能带与透射谱光子晶体是另一个非常适合 FDTD 的场景。FDTD 可以通过在结构里激发一个宽谱脉冲然后观察透射谱来提取光子禁带位置。也可以利用 FDTD 的 periodic 边界与模式展开功能计算特定波矢下的本征模式频率从而绘制能带图。需要注意的是光子晶体中的高阶模式往往具有较小的群速度这意味着场在结构中的驻留时间长FDTD 仿真需要更长的时间步才能收敛。我在做光子晶体慢光波导时仿真时间设到几百飞秒才稳定对内存和 CPU 的要求不低。5.3 等离激元纳米结构近场增强与吸收光谱等离激元纳米结构的 FDTD 仿真核心在于金属材料的频散模型。这类结构的近场增强往往出现在纳米尺度的缝隙或尖端附近因此网格必须非常细局部区域网格尺寸要小于 1 nm。一个常见问题是近场增强对网格极其敏感稍微改变网格尺寸场增强因子可能从 100 跳到 300。这说明计算还未收敛。面对这种问题别指望一次性算得准。先用粗网格扫描几何参数的趋势选定几个候选结构后再用细网格精算并报告收敛趋势。6. 仿真效率心得与个人工作流总结6.1 先从 2D 开始别直接上 3D很多人学 Lumerical FDTD一上来就想着算三维的复杂结构结果内存不够跑不动又不知道简化模型。实际上很多结构可以先在 2D 里验证物理趋势。比如对一个无限长的圆柱纳米线可以先做 2D 仿真得到透射反射趋势再扩展到 3D。2D 仿真的网格数量少几个数量级几分钟就能跑完对调试模型和排查错误特别高效。我个人的工作流是2D 粗网格快速扫描设计参数锁定大致范围3D 半精度验证效果最后在最优点附近做高精度仿真得到最终的谱线和场分布。这样既保证了效率又兼顾了可靠性。6.2 多核并行和内存分配Lumerical FDTD 支持 MPI 并行但并行效率不是线性的。网格规模较小的时候用 8 核并行快不到哪里去网格规模大的时候128 核并行能获得明显加速。我自己常用的分配规则是内存小于 32 GB 时用 4 核64 GB 左右用 8~16 核超过 128 GB 时再上 32 核甚至 64 核。核数开得太多而内存不够反而会导致频繁换页性能下降。另外Lumerical 有 GPU 加速选项但 FDTD 的 GPU 加速并不像直觉那样适用于所有场景。我试过的感觉是GPU 加速在单一仿真域、网格数量大的情况下有明显提升如果仿真域小、频繁切换任务CPU 并行可能更合适。6.3 保存好每一步版本管理和结果命名规范我见过太多同学跑了一堆仿真最后文件命名乱七八糟连自己都分不清哪个结果对应哪组参数。建议在最开始就建立规范的命名方式比如 “period500_r100_h300_simtime100fs.mat” 这样把所有关键参数直接写到文件名里后续整理和分析会省下大量时间。Lumerical 的脚本还可以自动生成日志记录每次运行的参数、时间和结果摘要。配合 MATLAB 或 Python 后处理脚本能够做到一次运行批量出图。把仿真和分析流程固化下来才是长期做研究的正确姿势。我在实际使用 FDTD 的过程中最大的体会是不要迷信一次仿真就能得到正确结果。每次仿真都是一次数值实验需要对材料、网格、边界逐个验证。特别是当你发现结果和预期不符时不要急着改结构参数先检查基础设置是否正确。网格收敛性测试和材料模型校验应该像实验中的空白对照一样成为每次仿真前的默认动作。做到这一点FDTD 就能成为你的得力工具而不是一团看不见摸不着的黑盒。