
简介围绕光子晶体连续域束缚态BIC的远场偏振计算这份资料包面向从事COMSOL光学仿真、拓扑光子学及微纳光子器件研究的高年级本科生、研究生和科研人员内容涵盖BIC理论基础、k空间能带与Q值分析、远场偏振特性计算等整合了仿真模型思路与Matlab脚本可直接作为课题入门和复现计算的参考。包内共15个文件包括6篇docx研究文档理论梳理与计算说明、4个html过程记录仿真分析步骤与结果讨论、4张jpg结果图像能带/偏振图等及1份txt说明文件压缩包整体大小2.83MB结构清晰便于按需查阅。目前已有42人学习浏览借助其中的文档可掌握BIC参数扫描、Q值提取与远场偏振投影方法Matlab脚本提供能带绘制和偏振分析的具体代码图像则直观展示计算结果适合正在搭建BIC仿真框架、需要对比验证远场偏振计算路径的读者。1. 光子晶体连续域束缚态这个“不辐射”的模式为什么值得算远场偏振“光子晶体连续域束缚态远场偏振计算含空间能带值”这串关键词拆开看就是三件事设计一个周期性结构让某个模式泡在连续辐射谱里却不向外泄漏在离这个模式很近的动量点上算出它的远场偏振方向和旋转规律再把结果放回能带图上确认它落在空间能带值的哪个位置。做光芯片激光选模、超表面窄带滤波和二次谐波增强的人会被这串问题卡住BIC 的远场辐射严格为零直接在奇点上算偏振只会得到一堆数值噪声能带图也会失真。这里直接给出一条可复现的路线先用平面波展开法定位候选能带再用 Stokes 参数做远场偏振提取最后绕奇点走一圈验证拓扑荷。2. 能带计算路线从结构建模到 BIC 候选模式的完整流程2.1 为什么选 PWE 找能带、选 FDTD 算远场两条路线各管一段光子晶体能带计算最常见的两套工具是平面波展开法PWEMPB 就是代表实现和时域有限差分法FDTD。PWE 在周期性结构里求解本征频率非常快一次性把整个布里渊区路径上的能带算完特别适合先摸清模式分布但它默认结构无损、周期无限拿不到模式泄漏率自然也算不出 Q 值。FDTD 能直接激励结构再测衰减Q 值给得很实但扫一个 k 点就要一个完整仿真把整条能带扫出来成本太高。所以我的常规做法是两段式用 PWE 圈出疑似 BIC 的能带区间再用 FDTD 在那个 k 点附近做衰减测量验证 Q 值是否随分辨率增加而发散。物理模型也要先定住。这里采用二维方形晶格介质柱结构晶格常数设为 a圆柱半径 r折射率 n。取二维模型而不是三维板是为了先把 xy 平面内的辐射通道算清楚避开 z 方向 slab 模式带来的额外泄漏项。对这个模型BIC 的判据很直接某个布洛赫模式的频率落在背景模式的连续谱区间内但它的对称性与所有可辐射通道不匹配于是能量只被局域在结构里无法耦合到远场。2.2 最小建模脚本方形晶格介质柱算到 BIC 候选点用一个可跑的 MPB Python 脚本把结构建起来。这里以 meep.mpb 的 Python 接口为例不同小版本的参数名略有差异但主体流程一致。import meep as mp from meep import mpb resolution 32 # 网格分辨率单位是每个晶格常数内的采样点数 r 0.2 # 介质柱半径单位是晶格常数 a n_si 3.5 # 柱体折射率硅在近红外波段约 3.5 geometry_lattice mp.Lattice( basis1mp.Vector3(1.0, 0.0), basis2mp.Vector3(0.0, 1.0) ) geometry [ mp.Cylinder( radiusr, heightmp.inf, # 二维模型认为柱体沿 z 向无限长 materialmp.Medium(indexn_si), axismp.Vector3(0, 0, 1) ) ] ms mpb.ModeSolver( geometrygeometry, geometry_latticegeometry_lattice, resolutionresolution, num_bands6 ) ms.run_tm() # 计算 TM 极化电场沿 z 方向的能带这里有两个参数直接影响后续找 BIC 的难度。第一个是resolution建议从 16 开始一旦看到某个能带交叉点疑似出现“嵌入连续谱”的特征再提到 64 复核 Q 值趋势网格不够时BIC 会被数值泄漏伪装成普通泄漏模。第二个是r半径 0.2 只是一个起点不同半径会移动能带位置和对称性匹配条件后面做参数扫描时它是最重要的变量。run_tm()只算 TM 模是因为介质柱结构里 BIC 通常更容易出现在 TM 能带的交叉点附近TE 模也可以算但建议分开跑避免简并模式混在一起难判断。2.3 从能带图看 Q 值连续域里“不泄漏”的模式长什么样MPB 输出的是实频率不能直接读出 Q 值。判断一个点是 BIC 候选先看它的频率是否落在连续谱区间内再看它能否辐射。能带图上最直观的信号是某条能带的频率穿过其他模式形成的连续谱带但它所在的分支没有表现出明显的带隙或反交叉颜色/对称性标记与相邻分支不同。这时候才值得对它做 FDTD 复核。复核 Q 值的做法是在结构中心放一个窄带偶极子源频率对准候选模式的频点然后在结构内放一个探针记录电场衰减曲线。衰减满足 |E(t)| ∝ exp(-ωt/2Q)对包络做对数线性拟合就能得到 Q。真正的 BIC 在数值上表现为 Q 随网格分辨率持续上升没有收敛上限的趋势如果 Q 稳定在某个有限值说明它只是一个高 Q 准 BIC而不是严格 BIC。以下表格是我用来区分两者的快速对照。特征准 BIC严格 BICQ 随分辨率变化先上升后收敛持续上升无上限远场偏振幅值有限值但很小严格趋近于零绕奇点拓扑荷整数但可能混入杂散相位清晰的整数对称性要求弱对称破缺即可需要严格匹配这一步容易忽略的是对称性条件。BIC 之所以不辐射本质上是模式的远场辐射通道因对称性被禁止网格一旦把结构的旋转对称性破坏这条禁令就失效了。所以建模时要把介质柱的中心放在网格元胞的对称中心上还要保证圆柱的圆形轮廓不被矩形网格过度“切”出棱角后面第五章会专门展开这个坑。3. 远场偏振计算用 Stokes 参数从复电场里提取偏振态3.1 远场偏振为什么不能直接看近场横波条件先满足近场里的电场分量和远场传播方向并不垂直这是计算偏振时最容易犯的错误。结构表面的近场电场里有很强的 Ez 分量而远场是横波电场必须垂直于传播方向。直接拿近场复矢量去算偏振得到的结果和远场实际辐射偏振完全对不上。更麻烦的是 BIC 附近的近场振幅非常大因为它把能量紧紧锁在结构里而同一模式的远场振幅趋近于零。用幅度大的近场去推幅度小的远场数值噪声会彻底淹没真实偏振信息。正确做法是先选定远场观察方向单位波矢记为 k̂。把 k̂ 与全局坐标的 ẑ 做叉积得到投影平面的一组正交基 e1、e2再把复杂电场矢量投影到这两个基上。这一步完成后剩下的就是标准偏振分析。实际项目里BIC 的远场偏振通常是在泄漏辐射显微镜里测的测量方向正好对应布里渊区里的某个动量方向数值计算时我们就在该动量方向构造 k̂。3.2 用 Stokes 参数做偏振提取一个可以直接抄的函数Stokes 参数的优点是它完整描述偏振态包括强度、线偏振方位角和椭圆率而且不需要假设电场是线偏振的。这里给出一个已经封装好的函数输入某个 k 方向的复电场矢量返回 Stokes 分量和偏振方位角。import numpy as np def far_field_stokes(E_cplx, kvec): # E_cplx: 长度为 3 的复电场矢量 [Ex, Ey, Ez] # kvec: 长度为 3 的实数波矢无需归一化 E np.asarray(E_cplx, dtypecomplex) k np.asarray(kvec, dtypefloat) k_hat k / np.linalg.norm(k) # 构造与传播方向正交的两个基矢量 e1, e2 z_hat np.array([0.0, 0.0, 1.0]) e1 np.cross(k_hat, z_hat) if np.linalg.norm(e1) 1e-12: # 当 k 与 z 轴平行时退化为固定取 x 方向 e1 np.array([1.0, 0.0, 0.0]) e1 e1 / np.linalg.norm(e1) e2 np.cross(k_hat, e1) # 投影到横向平面 E1 np.dot(E, e1) E2 np.dot(E, e2) # Stokes 参数 S0 np.abs(E1)**2 np.abs(E2)**2 S1 np.abs(E1)**2 - np.abs(E2)**2 S2 2.0 * np.real(E1 * np.conj(E2)) S3 -2.0 * np.imag(E1 * np.conj(E2)) # 偏振方位角范围在 0 到 pi 之间 psi 0.5 * np.arctan2(S2, S1) return S0, S1, S2, S3, psi调用这个函数之前要确认 E_cplx 来自同一个时间切片的复场相位关系不能乱。S0 表示总强度S1 和 S2 决定线偏振方向S3 表示圆偏振分量。BIC 点附近 S0 趋近于零此时 psi 会因为数值噪声到处乱跳这不是程序 bug而是物理上该点没有远场辐射、偏振本来就没有定义。所以偏振计算要刻意避开严格 BIC 点取它周围一圈动量点来计算。3.3 从近场到远场傅里叶变换与相位归一化的两个参数远场复电场可以从 FDTD 的近场监测面做空间傅里叶变换得到。近场平面上每个点的复电场 E(x, y) 做一个二维 FFT得到的频域分量就对应不同远场方向的幅值和相位。这一步有两个参数直接决定结果质量近场监测面的宽度必须是整数个晶格周期否则 FFT 会引入 sinc 振铃远场角度谱上出现一堆伪峰时间窗口长度要覆盖模式衰减到足够小否则频率分辨率不够BIC 附近极窄的偏振变化会被抹掉。def near_to_far_spectrum(E_near): # E_near: 二维复数数组形状为 (ny, nx) # 先做空间 FFT再平移到低频居中 E_far np.fft.fftshift(np.fft.fft2(E_near)) # 返回的每个元素对应一个 (kx, ky) 采样点 return E_farFFT 输出的索引坐标与动量 (kx, ky) 的对应关系取决于采样网格间距。实空间采样点间距 dx 决定动量空间总范围采样点数量决定动量分辨率。用这个结果作为far_field_stokes的输入时要注意 E_near 必须包含完整的相位信息不能用强度值替代。取相位时所有监测点要从同一个时间参考点导出通常先让结构响应进入稳态再记录一个时间快照。提示离 BIC 太近时S0 会被数值噪声主导。实际计算中我一般取布里渊区半径的 1%~5% 作为偏移量并在圆上取 8~12 个点这样既能看到完整的偏振旋转又不会踩进零辐射区。4. 空间能带值提取把能带曲面、BIC 位置与偏振分布对应起来4.1 把能带铺成空间曲面空间能带值指的是哪一组数据“空间能带值”这个说法在光子晶体语境里容易产生歧义。它不是指实空间坐标上每个点的能带频率那是声子晶体局域态密度的处理方式在光子晶体 BIC 计算里它通常指动量空间里的能带取值把第一布里渊区当作坐标平面每个点 (kx, ky) 对应某个模式的一个本征频率把这一组频率铺成曲面就是空间能带值。BIC 就藏在这张曲面上位置往往在能带交叉形成的锥形点附近。构建这张曲面时不要只沿 Γ-X-M 路径算一维能带那样会漏掉 BIC 所在的方向。正确做法是在二维动量网格上做扫描kx 和 ky 都取第一布里渊区一半的范围网格数从 21×21 起步。BIC 本身是一个零维奇点网格太粗会直接把它跳过去所以第一轮粗扫只是定位可疑区域第二轮要在可疑区域附近做局部加密扫描。4.2 从这里判断 BIC 位置能带转折点与 Q 值不发散的模式把能带曲面画出来后BIC 候选点有几个可辨识的特征频率等高线围绕某个点出现涡旋状分布能带曲面在该点附近呈现尖锐的漏斗形状沿任意方向穿过该点频率连续但模式的远场辐射突然消失。下面这段代码把从求解器导出的频率表填充成能带曲面再做可视化。import numpy as np import matplotlib.pyplot as plt def build_band_surface(kx_grid, ky_grid, band_index, freq_table): # kx_grid, ky_grid: 由 np.meshgrid 生成的动量坐标网格 # freq_table: 求解器导出的数组形状为 (n_k, n_band) # band_index: 要绘制的能带序号从 0 开始计数 surface np.zeros_like(kx_grid) nky, nkx kx_grid.shape for i in range(nky): for j in range(nkx): k_idx i * nkx j surface[i, j] freq_table[k_idx, band_index] return surface # 示例在 21x21 网格上计算第三条能带 kx np.linspace(-0.5, 0.5, 21) ky np.linspace(-0.5, 0.5, 21) KX, KY np.meshgrid(kx, ky) # freq_table 由能带求解器输出每个 k 点取若干条能带频率 # surface build_band_surface(KX, KY, band_index2, freq_tablefreq_table) plt.contourf(KX, KY, surface, levels40, cmapviridis) plt.colorbar(labelnormalized frequency) plt.xlabel(r$k_x$ (units of $2\pi/a$)) plt.ylabel(r$k_y$ (units of $2\pi/a$))注意band_index的取值要和第二、三章的能带顺序保持一致。TM 模式下第三条能带通常是最容易出 BIC 的位置因为前两条能带往往落在空气光锥之下辐射通道本身就不存在。判断曲面上的漏斗点是不是真 BIC还要回到 2.3 节的 Q 值复核单靠频率曲面只能确定“能带在这里发生了交叉”不能确定“交叉点不辐射”。4.3 热图辅助把偏振方位角叠到能带曲面上空间能带值除了频率还可以把远场偏振方位角作为第二层信息叠加上去。做法是在能带曲面的等高线图上把每个 k 点的偏振方位角画成短线短线方向代表该动量的线偏振方向长度代表 S0 的相对强度。BIC 附近会看到清晰的旋涡状短线分布绕奇点走一圈短线方向旋转 2π 的整数倍。这个旋涡结构直接对应 BIC 的拓扑荷比单纯看能带交叉更可靠。叠加图还能帮你判断 BIC 的对称性类型。如果旋涡旋转方向相反说明拓扑荷符号不同对应的偏振构型也不同。这类信息在做超表面偏振调控时非常关键目标偏振转换器件的功能本质上就是让远场偏振沿特定路径绕 BIC 演化。绘图时要注意角度显示的范围方位角是 0 到 π 的周期量直接用原始值画会看到剧烈跳变建议先对角度做 unwrap 处理再映射到颜色或短线方向。5. 常见问题排查BIC 偏振与能带计算最容易翻车的四个坑5.1 BIC 点上算偏振结果全是噪声现象把 k 点正好设在 BIC 位置调用far_field_stokes后 S1、S2 在每次运行之间完全不一致方位角随机跳动。原因BIC 的远场辐射严格为零求解器算出来的只是浮点噪声。Stokes 参数的 S1、S2 都由远场复电场乘积构成真实信号趋近零时噪声就成了主导项。解决永远不要在严格奇点上算偏振。绕 BIC 取一个半径为 Δk 的小圆Δk 取布里渊区半径的 1% 到 5%圆上相邻点之间夹角均匀。设定几个不同半径各算一圈如果方位角旋转角一致说明结果收敛如果旋转角随半径变化就要加密动量网格可能是附近还有其他奇点干扰。5.2 能带曲面出现多余的折叠分支现象空间能带曲面图上有大量重复的等高线条纹看起来像是多了一整条能带。原因扫描动量网格时没有使用不可约布里渊区而是把整个方形布里渊区扫了一遍。晶格对称性导致某些能带在多个区域内重复出现叠加在一起就成了伪造的多分支结构。解决先确认结构属于哪个点群方形晶格通常取不可约布里渊区的三角区域扫描即可。如果做全布里渊区扫描后处理时按点群对称性把等效点合并或者只在等高线图里保留频率最低的那条分支。这个坑在第四章代码里特别明显我当时第一次画出“八爪鱼”状的曲面排查了半天才发现是扫描范围选错了。5.3 Q 值上不去总是封顶在某个有限值现象FDTD 算出的 Q 值随分辨率从 16 升到 64涨到几千后就不动了再怎么加密网格也上不去。原因结构本身是准 BIC不是严格 BIC或者介质柱的圆形边界被直角网格离散后引入了非物理的对称性破缺。网格一粗圆柱轮廓变成锯齿状旋转对称性被破坏本应禁止的辐射通道被重新打开。解决先检查结构是否满足严格对称条件。圆柱中心要落在网格元胞中心半径在网格上的离散误差用亚像素平滑处理MEEP 里可以开启 subpixel 默认平均。如果已经做了这些Q 值还封顶说明这个几何参数点本身就不是 BIC 条件沿能带方向扫几个相邻参数让 r 或折射率微调再重新看 Q 值趋势。5.4 近场到远场频谱出现非物理的旁瓣现象near_to_far_spectrum得到的远场角度谱里目标方向附近出现一串规则的伪峰间距相等。原因近场监测面宽度不是晶格周期的整数倍。监测面被边界截断后FFT 把边界的跳变卷进频谱产生 sinc 振铃。伪峰间距恰好对应截断窗口的尺寸。解决把监测面宽度精确设置为整数个周期。如果结构本身有缺陷或需要局部激励先在监测面加一个窗函数做平滑再用 FFT。加窗会牺牲一点角分辨率但能换来干净的频谱。防止这类问题的习惯做法是在设计仿真时就记录监测面的起点坐标保证它正好落在周期边界上。提示以上四个坑都会在“先算能带、再算远场”的流程里暴露。每改一个参数先记录网格分辨率和 k 点偏移量不然翻车后根本不知道是几何参数变了还是数值设置变了。6. 两个验证技巧对称性自检与绕 BIC 追踪偏振旋转6.1 用对称性做第一次自检拿到能带曲面和远场偏振结果后先别急着往下做器件设计。跑一个对称性检查把整个结构旋转 90 度重新算一遍能带和 Stoke验证 KX、KY 坐标相应旋转后频率没有变化。如果旋转后的偏振方位角没有跟着旋转说明投影坐标系定义有问题要回 3.2 节检查 e1、e2 的构造——这类问题不是物理错了是坐标系没对齐。这个检查只需要多跑一次能带扫描成本很低却能把一半的坐标系 bug 挡在门外。6.2 绕 BIC 走一圈追踪偏振方向验证拓扑荷绕 BIC 绕一圈看偏振方位角的总旋转量这是 BIC 所有数值结果里最有说服力的一致性验证。拓扑荷由总旋转角除以 2π 得到理论上必须是整数。如果算出 0.5 或 1.7 这种值说明网格、动量偏移或相位归一化至少有一项出了问题。下面这个函数直接用小圆采样计算绕数。def winding_number(k_center, dk, n_points12, get_field): # k_center: BIC 所在 (kx, ky) # dk: 绕圈半径 # get_field: 输入 (kx, ky)返回该方向远场复电场 psi_list [] for idx in range(n_points): angle 2.0 * np.pi * idx / n_points kx k_center[0] dk * np.cos(angle) ky k_center[1] dk * np.sin(angle) E get_field(kx, ky) S0, S1, S2, S3, psi far_field_stokes(E, [kx, ky, 0.0]) psi_list.append(psi) # unwrap 防止方位角在 ±pi/2 处跳变 unwrapped np.unwrap(2.0 * np.array(psi_list)) total (unwrapped[-1] - unwrapped[0]) / 2.0 return int(round(total / (2.0 * np.pi)))这个小函数现在是我所有 BIC 计算的标配。每次换结构参数、换网格密度我都会先用它验证一遍绕数再去做投影器件设计。如果绕数不是整数我会回到第一章的建模流程重新检查不会继续往下算。代价只是多花十分钟但能省下一个晚上重算的时间——这是我在这类计算里最实在的血泪经验希望帮到你。本文还有配套的精品资源点击获取