三维光子晶体能带计算:COMSOL弱形式PDE从方程到后处理全流程
2026/9/15 4:21:20 网站建设 项目流程

三维光子晶体的能带计算,我一开始被COMSOL自带的电磁波频域接口折腾到怀疑人生:网格加密到百万级仍然有大量伪模式,Floquet边界条件设置稍有不慎结果就飘,换成弱形式PDE之后整个思路清晰了,求解效率和稳定性反而更好。这篇就把我最终跑通的完整流程写出来,包括方程推导、COMSOL里的具体配置、后处理技巧和踩过的坑,适合已经会用COMSOL但想深入光子晶体能带计算、尤其是想摆脱“黑盒感”的朋友参考。

现代光子器件的研发基本离不开能带结构分析,无论是光子晶体波导、慢光结构还是超表面单元设计,能带图都是判断带隙、模式色散和耦合特性的第一手依据。三维情况比二维麻烦得多,一是网格体量大,二是矢量场自由度是三维的,三是布里渊区扫描路径长。用弱形式直接控制方程和边界条件,虽然上手有一点门槛,但换来的是对求解过程的完全掌控,也让我理解了为什么某些“开箱即用”接口会给出错误结果。

1. 为什么三维光子晶体能带计算不能只依赖“开箱即用”接口

很多人拿到这个需求,第一反应是打开COMSOL的电磁波频域模块,设置特征频率研究,扫描波矢k点。对于二维光子晶体,这个流程确实能跑通,网格上万量级就能出漂亮结果。但三维情况一上来,事情就变味了。

第一个麻烦是自由度暴涨。三维矢量电场或磁场有三个分量,意味着每个网格节点上要同时求解三个物理量,再加一个散度约束。COMSOL的频域接口为了通用性,默认处理的是完整的Maxwell方程组,其中电场形式的方程在特征值求解时会引入大量的零特征值解和杂散模。这些杂散模式不消耗太多内存,但会把特征值列表塞得满满当当,你要在一堆噪声里找真实的光学模式,还得靠肉眼判断场分布,非常浪费时间。

第二个麻烦在边界条件。光子晶体能带计算需要Bloch周期性边界条件,也就是晶胞相对面上的场满足相位差exp(i·k·a)。这个条件在COMSOL自带的接口里是有对应设置,但参数化扫描波矢时很容易出问题:批量扫描时偶尔有某个k点解不出来、边界条件方向标错、重复面冲突这类隐蔽错误。排查起来要反复检查几何对和网格,效率极低。

第三个麻烦是方程本身。三维光子晶体用磁场形式比电场形式更干净,因为磁场满足的方程是旋度形式,散度条件∇·H=0自动被满足,不会引入额外的标量自由度。可在COMSOL的电磁波接口里切换H场和E场并没有那么自由,特别是跨版本时接口选项变了,找起来很费劲。如果你对“为什么要用H而不是E”没有强烈意识,很容易一直用默认E场,然后在伪模式里挣扎。

于是我想:既然我要算的本质上是一个广义特征值问题,方程形式和边界条件我都清楚,为什么不绕过那些封装好的物理接口,直接在弱形式PDE里写方程?这样散度约束、边界相位、材料参数、网格规模的控制权全部回到自己手里。第一次跑通之后,我最大的感受是:弱形式不是给理论物理学家摆弄的玩具,而是避开商用软件黑盒不确定性的一条捷径。

2. 从旋度算符到弱变分:Bloch展开这一步决定了成败

写弱形式的第一步是把光子晶体的本征方程推导到可以直接“抄进COMSOL”的程度。这里我把关键推导完整走一遍,后面建模时很多设置就是从这里来的。

2.1 磁场旋度方程是三维光子晶体的起点

在无源、非磁、线性介质中,从Maxwell方程出发,对磁场做旋度消元,可以得到:

∇ × [(1/εr(r))∇ × H] = (ω/c)² * H

其中εr(r)是随空间周期变化的相对介电常数,ω是角频率,c是真空光速。这个方程的右边系数λ= (ω/c)²就是我们要找的特征值。相比电场形式,磁场形式最大的好处是:任何旋度场的散度恒为零,所以∇·H=0自动满足,求解过程中不会出现由散度条件引起的费米子倍增问题,伪模式数量大幅减少。

这个性质在三维计算里是刚需。如果你用E场形式,有限元离散后散度约束不会自动满足,需要额外引入拉格朗日乘子或罚函数,方程规模更大,求解也更容易出现零特征值。我建议做过三维光子晶体的人,都用H场起步,别在E场上死磕。

2.2 Bloch展开把周期性问题变成包络函数问题

光子晶体具有周期性,直接在全空间求解不现实。利用Bloch定理,磁场可以写成:

H(r) = u_k(r) * e^(i·k·r)

其中u_k(r)是与晶格同周期的包络函数,k是Bloch波矢。代入旋度方程之前,要先处理旋度算符。因为e^(i·k·r)不是常数,旋度作用之后会多出一个i·k×u项。处理完后,原来的旋度算符被替换为:

(∇ + i·k)× u = ∇×u + i·(k×u)

于是方程变成:

(∇ + i·k)× [p(r)(∇ + i·k)× u] = λ·u

其中p(r)=1/εr(r),λ=(ω/c)²。到了这一步,u是周期的,所以边界条件就变成了简单的周期性边界条件,原来的Bloch相位差被吸收进了方程里的i·k项。这一步是整个方法的核心,也是弱形式相对于频域接口最有优势的地方——你不需要在边界上处理复相位,只需要在域内方程中带上k的分量。

2.3 弱变分形式:从微分方程到积分方程

对上面的算子方程,两边点乘测试函数v并在整个晶胞体积内积分,再对旋度项做一次分部积分。在周期性边界条件下,边界项恰好相互抵消,最终得到:

∫ p(r) [(∇+i·k)×u] · [(∇+i·k)×v]* dV = λ·∫ u·v* dV

这里的“*”表示复共轭。左边是p乘以旋度展开项的模方,右边是标准的H内积。这个形式非常干净:左边是刚度项,右边是质量项,正好构成广义特征值问题。COMSOL的弱形式PDE接口正是要以这种“积分表达式”的形式接收方程。

2.4 为什么必须显式区分实部虚部

由于方程中带有i·k项,即使k是实数,算子也不是实算子。如果直接用复数因变量,COMSOL也能解,但特征值求解器在处理复因变量时,默认会搜索复特征值和特征向量,计算量比实数问题大不少。更关键的是,无损耗介质的光子晶体模式频率是实数,你更希望特征值求解器在实数域内强制求解,这样能省一半自由度。

解决办法是把u拆成实部和虚部:u = u_r + i·u_i。代入方程后,实部和虚部会耦合在一起,形成一个2倍大小的实数特征值系统。这看起来增加了变量数,但特征值求解器从复变退化到实变,整体内存和求解时间往往反而下降。我在COMSOL里对比过,同样的网格和同样的k点,实拆法比复变量法快大约30%到50%。

3. COMSOL弱形式建模全流程:几何到研究步骤逐步落地

下面进入实操环节。我以简单立方晶格为例,晶胞内放置一个高介电常数的介质球,背景为空气,这个模型虽然简单,但跑通之后换成任何复杂结构(反蛋白石、十字孔、椭球粒子阵列)只是改几何和表达式的事。

3.1 几何与材料参数准备

在COMSOL中建立一个立方体晶胞,边长设为晶格常数a。介质球的位置在立方体中心,半径取0.3a。a的具体数值先用归一化单位,设为1就好,因为能带结构最终用无量纲频率ωa/2πc表示,归一化后所有结构参数都是相对值,方便后续缩放尺寸。

材料参数用变量定义而不是直接选材料库,这样后面扫描参数改起来方便。我习惯在“全局定义-参数”里写入:

  • a = 1 (晶格常数)
  • eps_b = 1 (背景相对介电常数)
  • eps_s = 12.0 (介质球相对介电常数,硅在近红外波段常用值)
  • r_s = 0.3*a (球半径)

然后在“组件-定义-变量”中定义空间相关的相对介电常数表达式:

  • eps_r = eps_b + (eps_s - eps_b) * (r <= r_s)

这个写法利用布尔表达式(r <= r_s)自动生成几何区域的指示函数,省去手动选择域的操作。注意这个表达式在球面上会产生阶梯跳变,只要网格分辨率足够,计算精度完全够用。

3.2 弱形式PDE接口的初始化

新建模型时,选择“模型向导-二维”或“三维”?这里明确选“三维”,然后在“添加物理场”中选择“数学-弱形式PDE (w)”。在因变量设置里,把因变量个数设为6个,分别命名:

  • Hrx, Hry, Hrz:磁场包络函数u的实部三个分量
  • Hix, Hiy, Hiz:磁场包络函数u的虚部三个分量

这一步你可能觉得6个变量太夸张了,但前面分析过,实拆法是三维矢量问题的稳定选择。如果你只是验证方程结构,可以先在二维里用2个变量跑通再上三维。

进入物理场设置后,会看到默认的“弱形式PDE 1”节点。在这个节点里,需要把6个因变量对应的弱表达式分别写上去。COMSOL的弱形式PDE接口默认按空间维度生成多个方程槽,每个槽对应一个因变量。你可以在一个“弱贡献”里把所有变量的表达式都写上,也可以用6个弱贡献分别写,后者更清晰。

3.3 弱表达式的具体写法

先定义一些辅助中间量,在“定义-变量”里添加:

  • p_eps = 1/eps_r
  • kx = k * kdir_x (k为当前波矢大小,kdir_x为方向分量)
  • ky = k * kdir_y
  • kz = k * kdir_z

其中kdir_x、kdir_y、kdir_z在参数扫描中设置,取值由布里渊区路径上的归一化k点决定。后面扫描时,可以直接用“辅助扫描”扫描这三个分量。

在弱形式PDE节点中,把6个变量对应的弱贡献写入。这里直接给一个完整的COMSOL弱表达式模板(以Hrx的方程为例):

-(p_eps*(d(Hrz,y)-d(Hry,z))*(d(test(Hrx),y)-d(test(Hrx),z)) + ...)

这写法手写太容易错了。实际上COMSOL支持在“弱贡献”中用通量和梯度的方式,但更直观的做法是先把旋度展开项定义为变量,再在弱表达式里引用。我推荐这样做:

在“变量”中定义以下内容(这里用COMSOL的求导符号d(f,x)表示∂f/∂x):

  • curl_rx = d(Hrz,y) - d(Hry,z) + (kyHiz - kzHiy)
  • curl_ix = d(Hiz,y) - d(Hiy,z) - (kyHrz - kzHry)
  • curl_ry = d(Hrx,z) - d(Hrz,x) + (kzHix - kxHiz)
  • curl_iy = d(Hix,z) - d(Hiz,x) - (kzHrx - kxHrz)
  • curl_rz = d(Hry,x) - d(Hrx,y) + (kxHiy - kyHix)
  • curl_iz = d(Hiy,x) - d(Hix,y) - (kxHry - kyHrx)

这里的推导需要解释一下:实部旋度项∇×u_r加上i·k×u之后,实部是∇×u_r - k×u_i(因为i·k×u = i·(k×(u_r+i·u_i)) = i·(k×u_r) - (k×u_i),实部是-k×u_i,虚部是k×u_r)。所以在curl_rx中,∇×u_r的x分量是d(Hrz,y)-d(Hry,z),然后减去(k×u_i)的x分量(kyHiz - kzHiy)。这个符号很多人会搞反,我一开始就在这里栽过跟头,后面排查章节专门讲。

同理定义测试函数的旋度展开量(以test(Hrx)为测试分量):

  • tcurl_rx = d(test(Hrz),y) - d(test(Hry),z) + (kytest(Hiz) - kztest(Hiy))
  • tcurl_ix = d(test(Hiz),y) - d(test(Hiy),z) - (kytest(Hrz) - kztest(Hry)) ...以及y、z分量。

然后,6个弱方程统一在一个表达式里写:

强制贡献(对应所有6个变量):

-(p_eps*(curl_rx*tcurl_rx + curl_ix*tcurl_ix + curl_ry*tcurl_ry + curl_iy*tcurl_iy + curl_rz*tcurl_rz + curl_iz*tcurl_iz))

质量项(对应特征值λ):

+(lam* (Hrx*test(Hrx) + Hix*test(Hix) + Hry*test(Hry) + Hiy*test(Hiy) + Hrz*test(Hrz) + Hiz*test(Hiz)))

这里lam就是COMSOL特征值求解器里的特征值变量,默认为lambda。当“研究-特征值”求解时,弱形式方程中的λ会自动被识别为待求特征值。这样设置后,特征值λ实际等于(ω/c)²,后处理时取sqrt(λ)就是归一化角频率。

需要注意,由于C0MSOL的弱形式PDE接口默认方程形式是0 = 弱贡献,所以刚度项和质量项之间的符号写成了负号+正号。这个符号约定我在第一次设置时因为没有仔细看帮助文档,导致算出来的频率全是虚数,排查了整整一个下午。

3.4 周期性边界条件的处理方式

因为已经做了Bloch展开,u_k是周期函数,所以晶胞六个相对面上的边界条件就是普通的周期性边界条件。这里不需要再设Floquet条件,直接给相对面施加“周期性条件”即可,COMSOL的“周期条件”特征里选“连续性”就行。

实际操作时要注意:弱形式PDE接口不自带“周期性条件”这个边界特征,需要到物理场设置里的“边界”子节点中添加。找到“周期条件”特征,选择两对相对面,类型设为“连续性”。COMSOL内部会自动处理网格对应节点的自由度耦合。三维中有三对相对面,需要分别添加三组周期条件,每次都要仔细选择源面和目标面,且要保证网格在这两个面上一致。

这里有个很关键的技巧:如果在COMSOL里使用扫掠网格并勾选“对面映射”,两个相对面的网格节点会自动一一对应。如果出现“目标边界上的网格顶点数与源边界不匹配”的报错,多半是网格对面生成失败,解决办法是改成自由四面体网格然后利用“复制面网格”功能,或者检查几何有没有多余的面分裂。

3.5 特征值研究步骤设置

研究类型选“特征值”,特征值搜索基准设成“围绕中心”,中心值设0.05附近(取决于你关注的频段)。对于简单立方晶格介质球模型,基模通常在(ωa/2πc)≈0.2到0.4之间的范围,所以特征值λ=(ω/c)²对应的数量级大约是(2π*0.2/a)²≈1.58,要提前估算一下,否则搜索区间设置不对会漏解。

求解器里选择“特征值求解器-MUMPS”,因为特征值问题生成的矩阵是非对称的稀疏矩阵,MUMPS在非对称稀疏问题上表现最稳。特征值最大数量建议先从20个开始,跑通整个流程后再根据能带图的稀疏程度调整。k点扫描通过“研究-步骤-特征值-辅助扫描”添加,扫描变量选kdir_x、kdir_y、kdir_z,扫描列表手动输入布里渊区路径上的点。注意kdir三个方向需要同时扫描,COMSOL“辅助扫描”支持多参数组合扫描,但要在“扫描类型”里选“所有组合”,并预先定义好路径列表。

4. 能带图的后处理与判读:把特征值变成一张能说明问题的曲线

能带图不是一个自动生成的高级图表,它本质上是把不同k点上的特征频率按顺序连成折线。这一步数据整理的工作量反而被很多人低估了,尤其在三维情况下,模式数量多、交叉复杂,处理不当会画出混乱的天线丛。

4.1 把特征值转换为归一化频率

求解完成后,在“结果-派生值-全局计算”里选择“特征值”,可以列出每个k点的所有λ。得到λ的数量级通常不是我们关心的直观量,需要转换成无量纲频率f_norm = ωa/(2πc) = a√λ/(2π)。如果你设a=1,那f_norm = √λ/(2π)。

在“结果-表格”里添加一列公式,输入:

sqrt(ev/root.ev)*(a)/(2*pi)

这样能直接看归一化频率。注意COMSOL的全局计算表格里的特征值变量名可能会随因变量名变化,如果你把因变量命名为Hrx等,特征值符号可能是root.xxx或ev。建议在“表格”属性里查看自动生成的列名,不要假设。

4.2 一维绘图组绘制多条能带

绘制能带图时,先建立一个“一维绘图组”,然后在绘图组里添加“点图”或“线图”。关键技巧是:X轴数据用扫描的k路径位置,Y轴数据用多个不同模式的特征频率分别画成独立的线。实现方式有两种:

一种是在“结果-表格”中生成数据后,导出为文本或csv,再通过外部工具(比如Python matplotlib或Origin)画图。这种方法自由度最高,也最容易控制线型和标注,适合写论文时的最终图。

另一种是在COMSOL内直接画:在派生值计算时勾选“绘制”,然后在一维绘图组中添加一个“全局”绘图,选择“特征值”数据集。把y轴表达式换成√λ/(2π),x轴表达式换成“k参数”对应的路径距离。由于特征值有多个,COMSOL会自动为每个特征值生成一条曲线。但索引顺序是乱的,你需要之后再按模式连续性手动调整颜色或线型。我的做法是先用外部工具绘图,效率高得多。

4.3 布里渊区路径的选择与标记

简单立方晶格的第一布里渊区高对称点路径,我使用的是: Γ(0,0,0) → X(0,0.5,0) → M(0.5,0.5,0) → R(0.5,0.5,0.5) → Γ(0,0,0)

这里的坐标都用归一化单位2π/a。计算时kdir分别取:

  • Γ到X段:(kx,ky,kz)依次为(0, t, 0),t从0到0.5
  • X到M段:(kx,ky,kz)依次为(t, 0.5, 0),t从0到0.5
  • M到R段:(kx,ky,kz)依次为(0.5, 0.5, t),t从0到0.5
  • R到Γ段:(kx,ky,kz)依次为(t, t, t),t从0.5到0

注意R到Γ这一段,三个分量同时变化,辅助扫描时很容易写错。我习惯先把整条路径的所有k点在Excel里生成好,然后粘贴到COMSOL辅助扫描的组合列表中,这样能减少手工输入错误。

4.4 能带图判读的基本原则

能带图中纵轴归一化频率,横轴是倒空间距离。重点关注两个东西:一是带隙是否存在,也就是在某一频率区间内没有任何模式;二是模式简并度,在Γ点或布里渊区边界上,某些模式会重合,这是对称性导致的,是验证计算是否正确的重要参考。

我第一次算出结果后,看到Gamma点附近有多条模式重合,心里还在想是不是特征值重了。后来对比文献才发现,简单立方格子Γ点的三重简并本来就是对称性要求,不是计算错误。判断方法很简单:把网格加密一倍再算,如果简并劈开,说明是网格误差;如果还是重合,那就是真实简并。

我还做了一次对照验证:把介质球的介电常数设为1,也就是全空间均匀介质。理论上能带退化为自由光子的线色散,所有模式沿k方向应该满足ω=c|k|。算出来的能带图如果偏离直线,说明方程表达式里某个符号错了,这是我在调试弱形式时最有效的自检手段。

5. 五个高频报错与结果异常:我的排查顺序和修复办法

这部分是我最想分享的,因为光看教程很容易忽略那些“看着对但实际错”的细节。以下五个问题是我在跑三维弱形式光子晶体模型时真实遇到过的,按出现频率从高到低排列。

5.1 伪模式数量爆炸:先检查散度条件和符号

跑出来的特征值列表里,一大半模式对应的场分布杂乱无章,频率也不连续。排查时我第一个看的就是磁场散度是否近似为零。在后处理里添加一个体表达式div(field),如果值明显不为零,说明方程形式引入了错误的自由度。

实测下来,最常见的原因是弱表达式里把p的因子放错了位置。原本应该是∇×[p(∇×H)],如果你写成p(∇×∇×H)的形式,相当于假定了p在空间为常数,在介电函数快速变化的结构里会制造大量伪模式。正确写法是把p放进第二个旋度里,也就是p乘在电流密度相关的项上。检查方法很简单:在材料边界两侧看p的跳变是否被正确计入。

5.2 特征值出现虚部:符号约定没对上

弱形式PDE接口的符号约定是“弱贡献=0”,而特征值研究的默认形式是刚度项减去λ乘以质量项。如果你把刚度项的符号写反,特征值就会变成负的,开方后看起来就是虚数。

这种问题特别隐蔽,因为COMSOL不会报错。我的排查办法是算一个最简单的情况:把k设为Γ点(0,0,0),此时i·k项消失,方程退化为标准的旋度方程。如果Γ点频率合理而其他点全是虚数,那就是Bloch展开项的实部虚部分配出了问题;如果所有点都是虚数,那就是整体符号反了。

5.3 网格各向异性导致的带隙偏移

三维光子晶体计算很容易受网格取向影响。如果网格在某个方向比另一个方向粗,算出来的能带会有约1%-3%的人为偏移,带隙边界的位置也会漂移。

建议至少做一次网格收敛性验证:把网格大小减半再算一次,比较带隙边界的相对变化。如果变化超过0.5%,说明网格不够密。三维模型网格数量通常在几十万到几百万之间,我用的是自由四面体网格,介质球表面的最大单元尺寸设为a/20,整体曲率因子设0.3,这样在保持精度的同时内存也不太爆炸。

5.4 高对称点简并劈裂:别急着怀疑求解器

如果Γ点或布里渊区边界上本该简并的模式劈裂成两条非常接近但不重合的曲线,最常见原因不是物理问题,而是周期性边界条件设错:相对面的网格节点没有一一对应,导致周期连续性被弱化。

检查办法是打开“网格-统计”查看界面上的单元数量,确认两个相对面网格数量一致。如果COMSOL某些版本在自动“复制面”时把网格密度不同的面强行映射,简并就会人为劈裂。另一个原因是几何建模时立方体被分割成了多个域,周期性边界条件只加在其中一个分离面上。我的教训是:建模时尽量用一个完整立方体,不要为了好看分成多块,分完块的每个内部边界都可能成为隐藏的缺陷源。

5.5 扫描参数组合错误导致曲线断点

辅助扫描多个k参数时,如果扫描列表选择的是“所有组合”,COMSOL会对每个参数的每个值自动做笛卡尔积,产生大量没用的组合(比如kx=0.5, ky=0.5, kz=0.3这种不在路径上的点)。能带图就会变成一堆离散点,完全连不成线。

正确做法是手动指定“组合类型”为“所有组合”(如果你真的想遍历高对称路径,其实应该自己准备好组合列表),或者用“索引扫描”按顺序扫描预先定义的k点列表。我在最终版本里使用的是“索引扫描”,把每一条路径上的100个k点按顺序排好,索引从1到N,这样绘图的x轴也可以用索引距离来标注。这个方法虽然前期准备数据繁琐一点,但画出来的能带图非常干净。

最后再聊一点个人心得。COMSOL的弱形式接口看起来吓人,实际上当你把它理解为“直接把积分表达式写进去”以后,反而比找一堆物理场接口去凑方程更踏实。至少你很清楚自己在解什么方程、用什么边界条件、为什么特征值是这个数。三维光子晶体能带计算,最贵的成本从来不是COMSOL许可证,而是排查那些藏在符号和边界条件里的隐性错误。把上面这些坑提前看完,能帮你省下至少两三天的调试时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询