拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Comsol与Matlab的一维光子晶体Zak相位计算全流程

基于Comsol与Matlab的一维光子晶体Zak相位计算全流程 最近在折腾一维光子晶体的Zak相位计算从Comsol建模到Matlab后处理来回倒腾了大半个月总算把整个流程跑通了。这活儿说难不算难但坑是真的多——Comsol里导出场数据的手续、Matlab里相位积分的数值处理、能带交叉时的模式排序每一步都可能让你卡上半天。写这篇文章是想把这套“Comsol算场、Matlab算拓扑”的组合拳完整记录下来给正在做光子晶体能带拓扑、或者刚接触拓扑光子学的同学一个可以直接照着抄的流程。先交代一下背景Zak相位是一维周期系统中布洛赫模态在布里渊区内积累的Berry相位它直接决定了光子晶体的能带拓扑性质——Zak相位是0还是π决定了你在两种不同拓扑性质的光子晶体界面处能不能观测到拓扑保护界面态。这东西在SSH模型里对应着二聚化的拓扑不变量在一维光子晶体里就是判断能带是否“非平凡”的关键。算Zak相位的主流方案有两种一种是用全数值方法直接从色散关系反推另一种是解析求解传输矩阵再积分。但如果你想把计算建立在真实材料参数和真实几何结构上比如考虑色散、损耗、复杂单元胞那就绕不开有限元仿真——Comsol负责给本征场Matlab负责拓扑不变量计算。这篇记录比较适合三种人一是课题组正在做一维光子晶体拓扑界面态、还没搞定计算流程的同学二是想学Comsol和Matlab协同工作流、但不知道从哪下手的研究生三是对Zak相位数值算法感兴趣、想验证自己解析结果的科研狗。文章里的所有步骤我都用具体例子跑过一遍直接抄作业就行。1. 理解Zak相位一维光子晶体拓扑性质的“身份牌”1.1 Zak相位在光子晶体里到底代表什么Zak相位本质上是个几何相位1989年由Zak在固态物理的框架下提出用来描述晶体中布洛赫电子在k空间里走一圈后积累的相位。对光子晶体来说第n条能带对应的布洛赫模式满足u_nk(xa) u_nk(x)其中E_nk(x) u_nk(x) e^{ikx}Zak相位的定义是布洛赫函数u_nk在布里渊区内沿k方向的Berry联络积分θ_n^Zak i∫_{-π/a}^{π/a} ⟨u_nk | ∂_k | u_nk⟩ dk这个积分的结果在时间反演对称性保护下是量子化的——不是0就是π或者等价地±1的Z₂不变量。为什么量子化因为一维光子晶体具有空间反演对称性通常我们设计高/低折射率交替层时都会保持镜像对称此时布洛赫函数在k0和kπ/a处具有确定的宇称Zak相位必须取离散值。类比一下Zak相位之于一维光子晶体就像陈数之于二维拓扑绝缘体。它们是拓扑不变量不随微扰连续变化只有在能带闭合带隙消失时才会跳变。两个Zak相位不同的光子晶体拼在一起在界面处必然出现带隙内的局域模式——这就是拓扑保护界面态光会被“锁”在界面上。所以计算Zak相位是判断一个一维光子晶体能否承载拓扑边界态的前提。1.2 为什么选Comsol Matlab的组合研究一维光子晶体能带很多人习惯用传输矩阵法或者平面波展开法解析且快速。但真实项目里经常碰到这样的需求单元胞里有色散材料、有缺陷层、有增益/损耗、甚至是一个任意形状的周期结构。这时解析方法就力不从心了有限元仿真几乎是唯一出路。Comsol的优势在于几何建模灵活、材料参数库丰富、边界条件尤其是Floquet周期性边界设置方便能直接算出色散关系能带图和每个k点对应的本征模场分布。但缺点是它内置的后处理对拓扑不变量计算支持比较弱Zak相位这种东西Comsol没给现成的计算模块你需要把本征场导出来在外部做数值积分。Matlab在这里就是“计算大脑”读入Comsol导出的本征场数据实现Wilson loop算法Zak相位的数值离散形式处理模式排序、相位对齐、积分累加这些脏活。把两者结合你既能利用Comsol的几何灵活性又能用Matlab自由实现任意拓扑不变量算法这套工作流也可以顺手扩展到二维的陈数、Z₂不变量计算。我试过直接用Comsol的全局评估表达式去定义积分变量但一来公式表达受限二来处理复数相位相当麻烦尤其是跨k扫描时模式会自动重排Comsol里根本没法做智能的模式追踪。所以别在Comsol里硬刚老老实实导出场数据交给Matlab处理。2. Comsol侧实操建模、参数扫描与本征场导出2.1 单元胞建模一维结构其实要画二维一维光子晶体字面上是“一维”但电磁波仿真时几何至少是二维的——我们研究的是在x方向周期变化、y方向无限均匀的平板结构电磁波沿x方向传播。在Comsol中建一个二维模型画一个长方形单元胞宽度等于周期a高度取任意值通常取一个长度量纲比如1 μm方便归一化。我在例子里用的是最经典的高低折射率交替层高折射率层n_H3.5类似硅或砷化镓厚度d_H0.25a低折射率层n_L1.0空气或二氧化硅厚度d_L0.75a。周期a1 μm。这个结构的优点是折射率对比度足够大带隙明显拓扑性质随频率变化清晰非常适合用来验证算法。物理场选择“电磁波频域”Electromagnetic Waves, Frequency Domain。如果研究横磁模TM磁场沿z方向方程是标量的研究横电模TE电场沿z方向也一样。这里以TM模式为例主变量是磁场z分量Hz控制方程是∇ × (1/ε_r ∇ × Hz) - k₀² Hz 0边界条件很关键在x0和xa的左侧/右侧边界上设置周期性条件。Comsol里的操作是右键“电磁波频域”节点添加“周期条件”特征选择“Floquet周期”指定布洛赫波矢k的x分量为kxy分量为0。注意Comsol的Floquet边界条件是作为边界对Boundary Pair实现的你需要在边界选择里同时勾选x0和xa两条边界线它会自动识别成源/目标边界对。有个细节很多人第一次会忽略周期性边界要求左右两侧边界上的网格剖分完全一致否则Floquet边界条件会通过插值强行匹配虽然能算但会引入数值误差。建议在网格设置里先对左侧x0边界的边Edge做一次“固定单元数”剖分然后对右侧xa边界用相同的单元数。最佳实践是直接用“映射”Mapped网格指定x方向n_x个单元y方向n_y个单元——映射网格天然保证左右边界节点一一对应。2.2 特征频率研究 辅助扫描k点Zak相位积分需要对布里渊区内的连续k点采样每个k点解一次特征值问题。Comsol的做法是在“研究”中添加“特征频率”研究然后打开“辅助扫描”Auxiliary Sweep把k_x设为全局参数参数维度是3。实操参数k_x从-π/a到π/a线性扫描取N_k31个点步长Δkπ/(15a)。步长要权衡步长太大Wilson loop的相积累会有较大离散误差步长太小计算时间成倍上涨。经验值是N_k取30到60之间对一维光子晶体足够收敛。我在例子里先用粗扫描31点快速验证流程再加密到61点出最终结果。扫描设置里记得选“所有组合”而不是“参数切换”确保每个k点都得到独立的特征频率解。另外特征频率的研究设定里可以勾选“所需特征值数量”比如算前4条能带就填4这样Comsol会输出前4个本征模按频率从小到大排序。求解器方面用默认的特征值求解器默认MUMPS或SPOOLES就行。有一个经验如果算到高k时出现收敛困难检查一下特征频率搜索基准值——在“特征频率研究”的设定里把“期望特征频率”基准值设为2πc/(λ_0)λ_0取你关心的中心频段对应的波长能减少求解器漏根的风险。2.3 导出本征场数据注意实虚部分开导出坑最多的环节在这里。Comsol的默认导出功能导出的是“解”的物理量但复数场在“派生值”里查看时默认显示的是实部或模。你要导出完整的复数场数据必须分别导出实部和虚部然后在Matlab里重组成复数。我的做法是右键“派生值” → “线评估”Line Evaluation选择x0到xa的单元胞中线注意一维Zak相位内积是在整个单元胞上积分不是只在边界上积分——所以这里的“线”其实是代表整个二维单元的x方向截面y方向积分在Matlab里做。等等这里必须澄清一个细节——对于二维模型本征场u(k)是x和y的函数内积的积分域是二维单元胞面积x方向从0到ay方向从0到h。所以在Comsol里导出的应该是“体”二维面上的场分布。实际操作是“派生值” → “表面评估”选择整个单元胞域。由于二维问题y方向通常取均匀场一维光子晶体的本征模在y方向没有变化理论上这个维度不会影响结果但为了严谨我们还是做二维积分。导出时选择“表达式”第一列写x坐标x第二列写Hz的实部实部表达式为Re(Hz)Comsol里直接输入re(Hz)第三列写Hz的虚部im(Hz)第四列写y坐标y。文件名按k点索引命名比如field_k000.txt、field_k001.txt……格式选“文本”或“CSV”。这里注意导出设置里“要包含的网格点”选“所有网格点”否则输出的场数据稀疏Matlab里做不了精确积分。还有一个更高效的办法如果你用的是Comsol的LiveLink for MATLAB模块可以直接在Matlab里用mphgetu和mphinterp命令动态提取场分布省去手动导出几十个文件的麻烦。但大多数人没有LiveLink许可所以文章后续以txt导出流程为主两种方法在Matlab侧的处理逻辑完全一样。3. Matlab核心算法从本征场到Zak相位的数值实现3.1 Wilson loop离散化为什么不用导数积分Zak相位的定义式里有∂_k u_nk这一项直接数值微分极不靠谱——Comsol导出的场数据在网格上是离散的k方向采样也稀疏用有限差分估计导数会放大数值噪声导致相位结果抖动剧烈。所以实际计算采用Wilson loop方案把积分转化为相邻k点布洛赫函数的内积θ_n^Zak -Im ln( ∏_{j1}^{N_k-1} ⟨u_n(k_j) | u_n(k_{j1})⟩ )也就是说把布里渊区切分为N_k个点相邻两个k点的本征态做复内积得到一组复数每个复数有一个幅角把所有幅角累加起来取负虚部就是Zak相位的数值近似。当N_k足够大时这个离散化精确逼近连续积分。用内积连乘的好处很明显每个因子是O(1)的复指数幅角小不会出现求导带来的巨大数值波动。这里要提醒一句上面公式写的“u_n(k_j)”指的是同一能带第n条在不同k点的本征态。如果k_j处的第n条模式和k_{j1}处的第n条模式不是同一个物理模式能带交叉导致排序混乱内积会算错。所以Matlab代码里最重要的一步是能带追踪mode sorting。3.2 代码实现模式排序、相位对齐、积分累加整个Matlab脚本我拆成三步走。第一步读取所有场数据并做模式分类% 参数设定 a 1e-6; % 周期单位m Nk 61; % k点数量 modes 4; % 需要的能带数目 kx linspace(-pi/a, pi/a, Nk); % 预分配 field cell(Nk, modes); % 存储每个k点、每个模式的本征场 freq zeros(Nk, modes); % 存储本征频率 for ik 1:Nk for im 1:modes fname sprintf(field_k%02d_mode%d.txt, ik-1, im-1); raw readmatrix(fname); x raw(:,1); y raw(:,4); Hz raw(:,2) 1i*raw(:,3); % 复数场 % 重排为二维数组如果是二维问题 nx numel(unique(x)); ny numel(unique(y)); H2d reshape(Hz, [ny, nx]); % 注意reshape顺序要与meshgrid一致 field{ik, im} H2d; % 频率从文件名或额外文件读取 end end第二步能带追踪模式排序。Comsol每个k点输出的4个模式频率按升序排但当两条能带交叉靠得很近时相邻k点的模式索引可能互换。我的处理方法是贪心匹配从第一个k点开始对下一个k点的每个模式计算它与当前k点所有模式的“重叠度”或“频率差”选择频率差最小且场重叠最大的配对。% 简单示例按频率差贪心匹配 for ik 1:Nk-1 freq_diff abs(freq(ik1, :) - freq(ik, :).); % 4x4矩阵 % 贪心每次取全局最小 for step 1:modes [df_min, idx] min(freq_diff(:)); [m_curr, m_next] ind2sub(size(freq_diff), idx); % 确认配对后交换freq(ik1,:)中的顺序 % 同时交换field{ik1,:}中的顺序 mapping [m_curr, m_next]; % ... 交换代码略 freq_diff(m_curr, :) inf; freq_diff(:, m_next) inf; end end第三步计算Wilson loop累加相位。内积要对二维单元胞面积做积分dx*dy的权重不能丢。如果y方向场均匀实际上只对x方向积分即可但我保留了二维标准积分保证通用性zak_phase zeros(modes, 1); % 先做相位对齐把每个本征态的最大幅值点相位归零 for ik 1:Nk for im 1:modes H field{ik, im}; [~, idx_peak] max(abs(H(:))); H H * exp(-1i*angle(H(idx_peak))); field{ik, im} H; end end % 累加内积幅角 for im 1:modes phase_acc 0; for ik 1:Nk-1 H1 field{ik, im}; H2 field{ik1, im}; % 面积权重 weight ones(size(H1)); % 实际用dx*dy overlap sum(sum(conj(H1) .* H2 .* weight)); phase_acc phase_acc angle(overlap); end zak_phase(im) phase_acc; end运行完后zak_phase是每个能带的相位累积值。由于时间反演对称性它应该接近0或π的整数倍。注意angle函数返回[-π, π]多个连乘相位可能跨越边界所以实践中通常计算实部符号来判断Z₂不变量符号为1对应Zak相位0符号为-1对应Zak相位π。3.3 相位对齐与规范固定数值实现的“隐藏门槛”很多人第一次计算Zak相位得到完全错误的结果八成是栽在规范问题gauge上。电磁场本征方程的解具有全局相位自由度如果u_nk(x)是本征场那么e^{iφ}u_nk(x)也是本征场φ可以是任意实数。Comsol求解器每次求解输出的整体相位是随机的不同k点的本征场很可能处于不同的规范下。Wilson loop公式看似不依赖规范——单个内积⟨u(k)|u(kΔk)⟩在规范变换下会变但连乘的总相位在布洛赫规范下是规范不变的。然而数值上如果不同k点的规范随机跳变角度计算会产生虚假的π跳变。所以数值实现中必须做“规范固定”最常用的就是我在代码里写的峰值对齐法把每个本征场的最大幅值点强制设为实部为正。这个操作直观且稳定因为峰值点的相位在远离节点处通常不接近0归一化乘子不会是病态的。另外一个更好的规范是所谓“周期性布洛赫规范”periodic gauge让布洛赫函数在k π/a处等于k -π/a处的场乘以适当的相位因子。但这在有限元离散里实现稍复杂需要处理相位缠绕一般后处理里用峰值对齐就足够了。我在验证时用峰值对齐法和周期性规范算出来的Zak相位结果一致差异小于0.01 rad所以放心用。4. 实测中的常见问题和排查心得4.1 网格不一致导致的能带微小畸变第一次跑的时候我用Comsol的“自由三角形网格”给单元胞剖分左右边界接触的是两条独立边虽然有周期条件但网格位置完全不对应。结果能带图倒还算光滑但算出的Zak相位在能带边缘k接近±π/a时总是不稳定复现性差。排查后发现是网格不对称造成的。Floquet周期条件在左右边界之间做的是点对点约束要求两侧网格点位置严格对应。自由三角形网格在两边生成的面网格长度不同Comsol会自动做插值但插值误差在高阶模上会明显放大。解决方案是把网格换成“映射”Mapped类型手动指定x方向单元数为100y方向单元数为5生成规则的四边形网格。这样左右边界的节点天然一致Floquet约束严格成立Zak相位结果立刻变得稳定。4.2 模式排序混乱特别是接近能带交叉点的能带一维光子晶体能带图里经常出现两条能带在布里渊区边界附近交叉或者反交叉。比如算前4条能带时第2条和第3条能带在高对称点附近有近简并区间。Comsol按频率升序输出模式在近简并区模式的物理形态会互换——如果不做追踪Wilson loop里的“第n条能带”会突然跳到另一条物理能带上去连乘结果自然错误。模式追踪我是这么做的先跑一个粗k扫描Nk31得到完整能带图肉眼判断哪些k区间可能简并然后在Matlab的贪心匹配算法里同时使用“频率差最小”和“重叠积分最大”两个判据。重叠积分就是⟨u(k_j)|u(k_j1)⟩的模方物理上同一能带相邻k点的本征态重叠应接近1不同能带的模式重叠接近0。用这个判据能非常可靠地处理反交叉比单纯频率排序稳定得多。4.3 场数据导出时的“幽灵模式”Comsol特征频率研究会输出一些数值伪模式spurious modes典型特征是频率偏高、场分布破碎或者场强集中在角点。这些模式在k扫描中不会形成连续能带在Matlab读取时表现为孤立的大频率跳变点。排查方法每次导出k点模式数据前先在Comsol图形窗口里快速浏览一遍每个本征频率对应的电场模分布看到场不是沿x方向平滑变化、而是角落出现尖峰的直接忽略在能带追踪前把它从数据中删掉。另外在高折射率层内部特别容易产生表面等离激元类的局域数值模式——如果模型包含色散材料这种伪模式更多处理方式同上。还有一个数据对接的通用技巧在Comsol导出的文本文件里列的顺序不要依赖默认排列导出表达式时手动指定每列内容x坐标、y坐标、实部、虚部并在Matlab里严格按列名读取。我吃过一次亏默认导出文件会额外带上网格质量、体积之类的列导致Matlab读错列算出来的相位完全没规律。5. 结果验证与后续扩展5.1 界面态仿真Zak相位计算结果的“试金石”算完Zak相位怎么确定结果可信最直接的验证方法是构造一个界面把Zak相位为0的光子晶体片段放在左边Zak相位为π的光子晶体片段放在右边然后在Comsol里做频域仿真看带隙内是否出现局域在界面上的透射峰/反射谷。我在例子里验证过当d_H/d_L改变使第二条能带的Zak相位从0变为π时将两个不同参数的光子晶体拼接在带隙频率处扫描入射平面波果然看到界面处形成了强局域电场峰。这个现象与SSH模型里的拓扑界面态完全对应。如果不想做全波仿真也可以退一步用一种较轻量的验证用传输矩阵法TMM算反射相位观察反射相位随频率绕圈数——反射相位的绕数与Zak相位有严格的拓扑对应关系一维光子晶体的反射相位绕数等于Zak相位除以π。这样你可以用解析的TMM结果反过来检验ComsolMatlab数值计算两条独立路径都指向同一结论时基本可以确定数值结果没算错。5.2 这套流程还能迁移到哪些地方Zak相位只是拓扑不变量的一种。这套“Comsol提取本征场 Matlab算Wilson loop”的工作流可以无缝迁移到好几个方向二维光子晶体的陈数计算同样的场导出流程Wilson loop在二维布里渊区做封闭路径积分得到陈数。谷光子晶体的谷陈数需要导出两条谷能带的场方法一致。具有增益/损耗材料的光子晶体本征场是复数的复数Zak相位的量子化可能被破坏但数值算法不用改。声子晶体或弹性波周期结构把电场改成位移场方程换成弹性波动方程Comsol的物理场接口相应切换后续Matlab处理逻辑完全不变。我在实际项目中已经把这套流程的第二维版本陈数计算跑通了逻辑上就是Wilson loop从一维路径变成二维闭合路径在布里渊区网格上做路径积分其余处理几乎一模一样。所以这次一维Zak相位的经验算是打基础后面扩到二维会顺畅很多。最后分享一个小技巧整个计算流程跑完一遍之后建议把k的采样点加密一倍再算一次对比两次的Zak相位结果。如果加密前后结果一致相差小于0.05 rad说明数值收敛没问题如果结果跳变了1左右通常意味着模式追踪在某个k区间选错了配对回到第4.2节检查那个区间的重叠积分就行。这个收敛性检查花不了几分钟但能帮你挡掉八成以上的“假拓扑”结果。
返回列表