原理与Python实现)
简介本资源是一份面向光子学初学者与电磁仿真实践者的MATLAB计算工具聚焦一维光子晶体能带结构的数值求解解决光学器件设计中带隙预测与色散分析的核心问题。程序基于平面波展开法PWM将周期性介电结构中的电磁场展开为傅里叶级数通过求解本征方程获得布里渊区内的频率-波矢关系直观呈现光子带隙位置与宽度适用于光滤波器、反射镜及谐振腔等原型设计验证。压缩包仅含1个核心文件——onedimen_OpCrystal_BandStr_PWM.m为完整可运行的MATLAB脚本涵盖晶格参数设置、介电常数建模、矩阵构造、本征值求解及能带图自动绘制功能包体仅2KB轻量易部署。目前已有297人学习下载读者可直接复用该脚本调整周期长度、材料折射率等参数快速生成不同结构的一维光子晶体能带图并结合代码注释深入理解平面波法的物理建模逻辑与数值实现细节。1. 一维光子晶体能带计算为什么用平面波展开法PWM而不是直接解麦克斯韦方程你手头有一段周期性介质结构——比如Si/空气交替堆叠的多层膜想算它在特定波长范围内允许哪些频率的光传播、哪些被完全禁止。这不是光学薄膜设计那种“算反射率”的问题而是要画出色散关系 ω(k)也就是光子能带结构Photonic Band Structure, BandStr。很多人第一反应是上COMSOL或Lumerical点几下仿真就出图。但实际一跑就会发现网格太密、内存爆掉、收敛慢、结果抖动大——尤其当介电常数对比度高比如Si/空气、周期数多、或想扫大k区间时商业软件反而成了黑匣子。这时候“onedimen_OpCrystal_BandStr_PWM”这个标题指向的是一套不依赖网格剖分、纯解析数值求解、可复现、可调试、内存开销可控的底层方案用平面波展开法Plane Wave Expansion Method, PWM求解一维光子晶体onedimen OpCrystal的本征频率问题。它本质是把电磁场按周期结构的倒格矢展开把麦克斯韦方程组转化为一个广义本征值问题Ax λBx再用标准线性代数库求解。适合做参数扫描、能带拓扑分析、缺陷态定位也常作为教学和算法验证的基准。如果你正在写毕业论文、调通自研光子器件仿真模块、或需要嵌入到更大规模的逆向设计流程中这套方法不是“备选”而是必须掌握的底层能力。2. 平面波展开法PWM的物理建模与矩阵构建逻辑2.1 为什么一维光子晶体能简化为标量亥姆霍兹方程一维光子晶体沿z方向周期排列如ε(zΛ)ε(z)其介电常数ε(z)是z的周期函数。考虑TE偏振电场E沿x方向磁场H在y-z平面麦克斯韦方程组可退化为标量波动方程$$ \frac{d^2 E_x(z)}{dz^2} k_0^2 \varepsilon(z) E_x(z) 0 $$其中 $k_0 \omega/c$ 是真空波数。注意这里没有近似是严格从麦克斯韦方程导出的TE模控制方程。而TM模H沿x方向则需解$$ \frac{d}{dz}\left( \frac{1}{\varepsilon(z)} \frac{d H_x(z)}{dz} \right) k_0^2 H_x(z) 0 $$二者形式不同不能混用。标题中“onedimen_OpCrystal_BandStr_PWM”默认指TE模更常用、数学更简洁后续所有代码与推导均基于第一个方程。这是整个PWM建模的起点——若你误用TM模公式去解TE结构结果会系统性偏移且无法与文献对标。2.2 平面波展开把未知场和已知介电函数都写成傅里叶级数设周期为Λ定义倒格矢 $G_m \frac{2\pi m}{\Lambda}$m为整数。将介电常数ε(z)和电场Eₓ(z)分别展开$$ \varepsilon(z) \sum_{m-M}^{M} \varepsilon_m e^{i G_m z}, \quad E_x(z) \sum_{n-N}^{N} c_n e^{i (k G_n) z} $$其中k是约化波矢布里渊区内的k∈[−π/Λ, π/Λ]cₙ是待求展开系数。关键点在于ε(z)的傅里叶系数εₘ可解析计算对分段常数结构如Si/空气层有闭式解而Eₓ(z)的展开截断数N决定了精度——N越大能带越精细但矩阵维度(2N1)²增长极快。将上述两式代入标量方程利用正交性得到本征值方程$$ \sum_{n} \left[ -(k G_m)^2 \delta_{mn} k_0^2 \varepsilon_{m-n} \right] c_n 0 $$即矩阵形式$\mathbf{A}(k) \mathbf{c} 0$其中矩阵元为$$ A_{mn}(k) - (k G_m)^2 \delta_{mn} k_0^2 \varepsilon_{m-n} $$这是一个非对称、稠密、实系数矩阵因εₘ为实其零空间对应非零解cₙ存在的条件即det[A(k)] 0。实际求解时我们固定k求k₀²即ω²使矩阵奇异——这等价于求解广义本征值问题$$ \mathbf{K} \mathbf{c} \omega^2 \mathbf{M} \mathbf{c} $$其中Kₘₙ (k Gₘ)² δₘₙMₘₙ εₘ₋ₙ。这才是标准数值求解器如scipy.linalg.eig能直接处理的形式。提示很多初学者卡在“为什么是广义本征值问题”。记住原始方程中k₀²乘在ε上而ε本身是系数矩阵的一部分所以不能写成标准Axλx。必须把k₀²单独提出来其余全归入矩阵M。2.3 介电常数傅里叶系数εₘ的解析计算以双层结构为例假设一维晶胞由两层组成厚度d₁的材料1介电常数ε₁厚度d₂的材料2ε₂总周期Λ d₁ d₂。则ε(z)在一个晶胞内为$$ \varepsilon(z) \begin{cases} \varepsilon_1, 0 \le z d_1 \ \varepsilon_2, d_1 \le z \Lambda \end{cases} $$其傅里叶系数为$$ \varepsilon_m \frac{1}{\Lambda} \int_0^\Lambda \varepsilon(z) e^{-i G_m z} dz \frac{1}{\Lambda} \left[ \varepsilon_1 \int_0^{d_1} e^{-i G_m z} dz \varepsilon_2 \int_{d_1}^{\Lambda} e^{-i G_m z} dz \right] $$对m 0ε₀ (ε₁d₁ ε₂d₂)/Λ即体积平均介电常数对m ≠ 0$$ \varepsilon_m \frac{1}{\Lambda} \left[ \varepsilon_1 \frac{1 - e^{-i G_m d_1}}{i G_m} \varepsilon_2 \frac{e^{-i G_m d_1} - e^{-i G_m \Lambda}}{i G_m} \right] \frac{1}{\Lambda} \cdot \frac{e^{-i G_m d_1/2}}{i G_m} \left[ \varepsilon_1 (e^{i G_m d_1/2} - e^{-i G_m d_1/2}) \varepsilon_2 (e^{i G_m d_2/2} - e^{-i G_m d_2/2}) \right] $$进一步化简得$$ \varepsilon_m \frac{2}{\Lambda G_m} e^{-i G_m d_1/2} \left[ \varepsilon_1 \sin\left(\frac{G_m d_1}{2}\right) \varepsilon_2 \sin\left(\frac{G_m d_2}{2}\right) \right] $$该式可直接编码无需数值积分无精度损失且速度极快。这是PWM优于FDTD/FEM的核心优势之一高频分量衰减快前几十个εₘ就足够收敛。3. Python实现从介电函数到能带图的完整可运行脚本3.1 核心函数计算εₘ并构建k点对应的本征值矩阵import numpy as np from scipy.linalg import eig import matplotlib.pyplot as plt def eps_fourier_coefficients(d1, d2, eps1, eps2, M): 计算一维双层光子晶体介电常数的傅里叶系数 ε_m, m ∈ [-M, M] 返回: array of length (2*M1), index 0 corresponds to m0 Lambda d1 d2 G lambda m: 2 * np.pi * m / Lambda eps_m np.zeros(2*M1, dtypecomplex) # m 0 eps_m[M] (eps1 * d1 eps2 * d2) / Lambda # m ! 0 for m in range(-M, M1): if m 0: continue gm G(m) # 使用解析公式避免数值积分误差 term1 eps1 * np.sin(gm * d1 / 2) term2 eps2 * np.sin(gm * d2 / 2) phase np.exp(-1j * gm * d1 / 2) eps_m[M m] (2 / (Lambda * gm)) * phase * (term1 term2) return eps_m def build_pwm_matrix(k, eps_m, N, c3e8): 构建平面波展开法本征值矩阵: K c omega^2 M c K: 对角阵K_mm (k G_m)^2 M: 稠密阵M_mn eps_{m-n} 返回: K, M 两个 (2N1)x(2N1) 矩阵 G lambda n: 2 * np.pi * n / (d1 d2) # 倒格矢 size 2*N 1 K np.zeros((size, size), dtypefloat) M np.zeros((size, size), dtypecomplex) # 构建K矩阵对角 for idx, m in enumerate(range(-N, N1)): km k G(m) K[idx, idx] km ** 2 # 构建M矩阵M[m,n] eps_{m-n} # 注意eps_m数组索引为 [Mm]m∈[-M,M]此处m-n可能超出[-M,M]需截断 for i, m in enumerate(range(-N, N1)): for j, n in enumerate(range(-N, N1)): diff m - n if abs(diff) len(eps_m)//2: # 确保diff在eps_m有效范围内 M[i, j] eps_m[len(eps_m)//2 diff] else: M[i, j] 0.0 # 高频截断 return K, M逻辑说明与参数说明eps_fourier_coefficients函数严格按前述解析公式计算εₘ输入d₁/d₂为物理厚度单位米eps₁/eps₂为相对介电常数无量纲。M是傅里叶截断阶数建议M ≥ 2×N否则M矩阵会欠采样。build_pwm_matrix中k是约化波矢单位rad/m范围应覆盖第一布里渊区[−π/Λ, π/Λ]。N是平面波展开阶数决定能带分辨率N5只能看到前2~3条带N15才能分辨高阶带隙但内存占用∝N²N20时矩阵已是41×41N30→61×61需权衡。c3e8是光速用于后续将ω转为波长λ2πc/ω。此参数可外置方便适配不同单位制如设c1则ω单位为rad·Λ⁻¹。3.2 主循环扫k点、解本征值、提取能带# 参数设置Si/空气周期Λ0.5 μm d1 0.25e-6 # Si层厚度250 nm d2 0.25e-6 # 空气层厚度250 nm eps1 12.0 # Si在红外波段的ε无量纲 eps2 1.0 # 空气ε M 30 # ε_m截断阶数必须≥N N 15 # 平面波展开阶数 k_points np.linspace(-np.pi/(d1d2), np.pi/(d1d2), 101) # 101个k点 c 3e8 # 预计算所有ε_m eps_m eps_fourier_coefficients(d1, d2, eps1, eps2, M) # 存储能带数据bands[i][j] 第j条带在第i个k点的ω值 bands [] for k in k_points: K, M build_pwm_matrix(k, eps_m, N, c) # 求解广义本征值问题 K c ω² M c # 注意scipy.linalg.eig 默认求解 A x λ B x即 K c λ M c → λ ω² w2, _ eig(K, M) # 取实部虚部应≈0否则说明数值不稳定 w2_real np.real(w2) # 过滤负值数值误差导致和过大值高频噪声 w2_valid w2_real[(w2_real 0) (w2_real 1e17)] # 排序并取前10个最小正根对应最低10条能带 w2_sorted np.sort(w2_valid)[:10] omega np.sqrt(w2_sorted) bands.append(omega) # 转为波长μm便于绘图 lambda_bands [[2*np.pi*c / w for w in omegas] for omegas in bands] # 绘图 plt.figure(figsize(10, 6)) for i in range(len(lambda_bands[0])): plt.plot(k_points, [lb[i] for lb in lambda_bands], b-, linewidth1.2) plt.xlabel(k (rad/m)) plt.ylabel(Wavelength (μm)) plt.title(Photonic Band Structure of 1D Si/Air Crystal (TE mode)) plt.grid(True, alpha0.3) plt.show()关键执行细节k_points必须均匀覆盖第一布里渊区且端点严格为±π/Λ。若用np.linspace(-np.pi/Lambda, np.pi/Lambda, 101)则首尾点正好是布里渊区边界能准确捕捉带边折叠。eig(K, M)返回的本征值λω²必须取实部。理想情况下虚部应1e−12若出现较大虚部如1e−6说明矩阵病态需检查εₘ计算或增大M。w2_valid过滤逻辑至关重要负ω²是无效解对应衰减模过大ω²是高频截断噪声对应|kGₘ|过大的平面波分量。经验法则是保留ω² 10×(π/Λ)²即波长 0.1Λ。最终绘图纵轴用波长μm而非频率THz因为光子晶体文献普遍用λ描述带隙位置且人眼对λ尺度更敏感。4. 避坑指南PWM计算中5个真实踩过的坑与血泪修复方案4.1 现象能带图在布里渊区边界k±π/Λ处出现尖锐发散或断裂原因k点未精确落在边界或Gₘ计算时用了错误周期Λ。例如误将d₁d₂当作Λ但实际结构周期含界面效应或单位混淆nm vs m。解决强制k_points首尾为±np.pi/(d1d2)并在打印时验证k_points[0]和k_points[-1]是否严格等于该值浮点误差1e−15。同时检查Gₘ定义中Λ是否与d₁d₂完全一致——哪怕差1nm在高频下也会导致Gₘ偏移引发相位错误。4.2 现象低频能带长波长平直如镜但高频带短波长剧烈抖动、甚至出现虚线原因平面波截断数N不足无法分辨高频振荡场。N10时最多能可靠描述波长2Λ的模式而短波长模式需要更高阶Gₘ参与。解决对目标波长λ_min要求N ≥ Λ/λ_min × 5经验系数。例如Λ500 nm想算到λ1 μm即k_min2π/λ6.28e6 rad/m则N至少取15若要算到λ0.5 μmN需≥30。宁可N过大内存慢不可N过小结果假。4.3 现象同一k点解出的ω²出现大量重复值如10个几乎相同的本征值原因M矩阵秩亏缺rank-deficient通常因εₘ计算中m−n超出预设范围填零后导致M奇异。常见于N M即平面波阶数超过介电函数傅里叶分辨率。解决永远保证M ≥ 2×N。M是εₘ的截断N是场的截断前者必须包络后者。若M30N最大设15若强行N20则需同步提升M至40以上。可在build_pwm_matrix中加断言assert M 2*N, M must be 2*N。4.4 现象能带整体上移或下移与文献结果偏差10%原因单位制混乱。最常见的是d₁,d₂用了nm但Gₘ计算时没换算成米导致Gₘ放大1e9倍K矩阵爆炸。或者c3e8用了m/s但d用nm造成量纲错乱。解决所有长度单位统一为米。定义d₁0.25e−6而非250Λ5e−7。在注释中明确标注“All lengths in meters”。可加校验计算Λ后打印print(fPeriod Λ {Lambda:.2e} m)确认数量级正确。4.5 现象TE模能带与TM模能带形状相似但带隙位置完全错位原因误将TM模方程套用于TE模计算或反之。标题明确是TE模但代码中用了TM的M矩阵构造逻辑即1/ε参与。解决严格对照本节2.1的方程。TE模Mₘₙ εₘ₋ₙTM模Mₘₙ (1/ε)ₘ₋ₙ。二者傅里叶系数计算完全不同——TM需先算1/ε(z)再展开而1/ε(z)在界面处不连续其傅里叶收敛更慢。除非明确需求TM否则坚持用TE模公式并在函数名中标明_TE后缀。5. 进阶技巧如何用PWM快速定位缺陷态与验证带隙鲁棒性5.1 在完美晶格中引入单层缺陷修改ε(z)的傅里叶系数完美一维晶体的ε(z)是双层周期函数。若在第p个晶胞中将某一层厚度或介电常数微调如Si层掺杂导致ε₁→ε₁δ则ε(z)不再是严格周期但可近似为“弱扰动”。此时缺陷态会出现在带隙中其频率可通过微扰理论预估或直接重构ε(z)并重算。更实用的做法是保持周期性但在一个晶胞内构造三层结构如Si/缺陷层/空气使其仍满足ε(zΛ)ε(z)但晶胞内含缺陷。例如原晶胞Si(250nm)/Air(250nm)新晶胞Si(200nm)/Si₀.₉Ge₀.₁(100nm)/Air(200nm)。此时Λ不变但ε(z)变为三段函数需重写eps_fourier_coefficients函数增加d₃、eps₃参数并分三段积分。def eps_fourier_coefficients_3layer(d1, d2, d3, eps1, eps2, eps3, M): 三段晶胞的ε_m计算支持缺陷层建模 Lambda d1 d2 d3 G lambda m: 2 * np.pi * m / Lambda eps_m np.zeros(2*M1, dtypecomplex) eps_m[M] (eps1*d1 eps2*d2 eps3*d3) / Lambda # m0 for m in range(-M, M1): if m 0: continue gm G(m) # 三段积分0~d1, d1~d1d2, d1d2~Lambda term1 eps1 * (np.exp(-1j*gm*d1) - 1) / (-1j*gm) if gm ! 0 else eps1*d1 term2 eps2 * (np.exp(-1j*gm*(d1d2)) - np.exp(-1j*gm*d1)) / (-1j*gm) term3 eps3 * (np.exp(-1j*gm*Lambda) - np.exp(-1j*gm*(d1d2))) / (-1j*gm) eps_m[Mm] (term1 term2 term3) / Lambda return eps_m使用场景当你需要快速评估“在Si/空气光子晶体中插入100nm Ge掺杂层能否在1.55μm处产生局域缺陷态”时只需调用此函数设eps310.5Ge掺杂Si的εd2100e−9其余不变重跑能带——缺陷态会以孤立点形式出现在带隙中其Q值品质因子可通过邻近k点能带宽度粗略估计。5.2 带隙鲁棒性分析参数扫描与自动带隙提取单纯看能带图不够工程上更关心“这个带隙有多宽对制造误差是否敏感”。为此需对关键参数如d₁、ε₁做±5%扫描并量化带隙中心频率偏移Δω和带宽变化ΔΔω。以下函数自动提取第i个带隙i从0开始的上下边频率def extract_bandgap(bands, i0): 从bands列表每个元素是该k点的ω数组中提取第i个带隙 返回: (omega_lower, omega_upper, width) # bands是list of arrays每个array是该k点的ω值已排序 # 找所有k点中第i条带和第i1条带的差值 gaps [] for omegas in bands: if len(omegas) i1: gap omegas[i1] - omegas[i] gaps.append(gap) if not gaps: return None, None, 0.0 # 带隙中心 min(上带) - max(下带) lower_band [omegas[i] for omegas in bands if len(omegas) i] upper_band [omegas[i1] for omegas in bands if len(omegas) i1] omega_lower max(lower_band) omega_upper min(upper_band) width omega_upper - omega_lower return omega_lower, omega_upper, width # 示例扫描d1从0.2375e-6到0.2625e-6±5% d1_base 0.25e-6 d1_range np.linspace(d1_base*0.95, d1_base*1.05, 21) gaps_data [] for d1 in d1_range: d2 0.25e-6 # 固定d2 eps_m eps_fourier_coefficients(d1, d2, eps1, eps2, M30) # ... 重跑能带计算省略中间循环... gap extract_bandgap(bands, i0) # 第一个带隙 gaps_data.append(gap) # 绘制带隙宽度随d1的变化 widths [g[2] for g in gaps_data] plt.plot(d1_range*1e6, widths, ro-, labelBandgap width) plt.xlabel(Si layer thickness d1 (nm)) plt.ylabel(Bandgap width (rad/s)) plt.legend() plt.grid(True)参数说明与技巧extract_bandgap中omega_lower max(lower_band)确保取“最窄处”的下边omega_upper min(upper_band)取“最窄处”的上边这才是物理带隙宽度。若用平均值会高估鲁棒性。扫描步长建议21点10个步长足够捕捉非线性响应。若发现宽度在某点突变说明存在临界点需加密扫描。此方法可无缝接入自动化流程将d1_range换成eps1_range即可评估材料色散误差影响换成Λ_range可评估光刻套刻误差。我坚持用这套PWM流程跑了三年光子晶体项目——从硕士课题的Si基波导带隙设计到量产光芯片的工艺容差仿真再到给合作方写SDK时的底层能带引擎。最大的教训是别信“一键生成”的GUI工具它们隐藏了ε(z)建模的每一个假设而自己写的PWM脚本哪怕只有50行也能让你在客户问“为什么带隙偏移了20nm”时立刻定位到是氧化层厚度公差超限而不是甩锅给“仿真不准”。希望帮到你。本文还有配套的精品资源点击获取