简介:本资源是一份面向光子学初学者与电磁仿真实践者的MATLAB计算脚本,聚焦一维光子晶体能带结构的数值求解,解决光子带隙分析与周期性介质光学特性建模的核心问题。适用于高校光学工程、光电信息科学与工程专业课程设计、科研入门及光电子器件原理验证场景。压缩包仅含1个核心文件——onedimen_OpCrystal_BandStr_PWM.m,为MATLAB可执行脚本,实现平面波展开法(PWM)全流程:包括晶格参数设置、介电常数傅里叶展开、本征方程构建与求解、第一布里渊区能带绘图等功能,代码简洁(2KB),便于理解算法逻辑与调试修改。已有297人学习下载,读者可直接运行获取典型一维光子晶体(如Si/空气多层结构)的完整色散曲线,掌握边界条件处理、倒格矢截断、能带对称性识别等关键实现细节,是深入理解光子晶体物理与夯实数值仿真能力的实用入门工具。
1. 一维光子晶体能带计算不是“调PWM波形”:这是用平面波展开法(PWM)解麦克斯韦方程的MATLAB黑匣子,专治周期介质中光的“禁行区”定位难题
你手头这个onedimen_OpCrystal_BandStr_PWM.m文件,和STM32输出PA8 PWM波、FOC电机控制里的PWM定时器、甚至DC-DC反馈环路里的PWM占空比调节——完全不是一回事。这里的“PWM”是Plane Wave Expansion Method(平面波展开法)的缩写,不是脉宽调制(Pulse Width Modulation)。这是光子学领域一个被严重误读的命名陷阱:同一个缩写,在电磁仿真里是数学求解工具,在嵌入式里是硬件信号生成手段,二者底层逻辑天差地别。这个MATLAB脚本干的是正经事——在周期性介电结构(比如Si/SiO₂交替堆叠的多层膜)中,严格求解标量波动方程(TE模近似),画出光子能带图,精准标定哪些频率的光被彻底“拦住”(即光子带隙),从而为设计反射镜、滤波器、谐振腔提供理论依据。它不烧芯片、不占GPIO、不配CCU6寄存器,但要求你理解布里渊区边界、傅里叶展开截断、本征值收敛判据。适合光学仿真新手入门、物理系研究生快速验证理论模型、或器件工程师做结构预筛选——只要你不把它当单片机PWM库去include,就能少走三个月弯路。
2. 平面波展开法不是“套公式”,而是把光场拆成傅里叶级数再拼回本征解:从物理建模到MATLAB实现的四步闭环
2.1 为什么选平面波展开法?——它是最“透明”的周期结构求解器
一维光子晶体的介电常数 ε(z) 是z方向上的周期函数:ε(z + a) = ε(z),其中a为晶格常数。麦克斯韦方程在标量近似下退化为亥姆霍兹方程:
$$\frac{d^2 E_z}{dz^2} + k_0^2 \varepsilon(z) E_z = 0$$
直接数值求解困难,因为ε(z)含跳变(如界面处折射率突变)。平面波展开法的核心思想是:把未知场E_z(z)和已知介电函数ε(z)都展开成傅里叶级数,将微分方程转化为代数本征值问题。
- 场展开:$E_z(z) = \sum_{G} c_G e^{i(k+G)z}$,其中k是约化波矢(在第一布里渊区[-π/a, π/a]内扫描),G是倒格矢(G = 2πn/a, n∈ℤ)
- 介电展开:$\varepsilon(z) = \sum_{G'} \varepsilon_{G'} e^{iG'z}$
代入原方程,利用正交性,得到矩阵形式:
$$\sum_{G'} \left[ (k+G)^2 \delta_{G,G'} - k_0^2 \varepsilon_{G-G'} \right] c_{G'} = 0$$
这是一个标准的广义本征值问题 $A\mathbf{c} = 0$,非零解要求 det(A)=0 → 解出k₀²(即ω²)随k的变化关系,即能带。
提示:该方法本质是谱方法(spectral method),精度远高于有限差分(FDM),尤其对界面陡峭的结构(如高对比度Si/air)更稳定;但计算量随截断阶数N增长为O(N³),需权衡精度与速度。
2.2 MATLAB代码骨架解析:onedimen_OpCrystal_BandStr_PWM.m的5个核心模块
打开.m文件,你会看到清晰的五段式结构(非函数封装,是脚本式流程):
%% 1. 参数定义区 —— 必须手动修改的4个物理量 a = 1; % 晶格常数(单位:μm) n1 = 3.48; n2 = 1.45; % 两层材料折射率(Si和SiO2典型值) d1 = 0.5*a; d2 = 0.5*a; % 各层厚度(保证周期a = d1+d2) N_Fourier = 21; % 傅里叶展开截断阶数(奇数!因对称性,G = -N..N共21项) %% 2. 介电函数傅里叶系数计算 —— 关键:解析积分而非数值采样 % 对于方波型ε(z),其傅里叶系数有闭式解: % ε_G = (1/a) * ∫_0^a ε(z) e^{-iGz} dz % 这里直接调用解析公式,避免FFT引入的栅栏效应 G_vec = 2*pi*( -floor((N_Fourier-1)/2) : floor((N_Fourier-1)/2) ) / a; eps_G = zeros(size(G_vec)); for idx = 1:length(G_vec) G = G_vec(idx); if G == 0 eps_G(idx) = (n1^2*d1 + n2^2*d2)/a; % G=0为平均介电常数 else eps_G(idx) = (n1^2 - n2^2) * sin(G*d1) * exp(-1i*G*d1/2) / (G*a); end end %% 3. 构建本征值矩阵A(k) —— 注意k的循环范围与G的索引映射 k_vec = linspace(-pi/a, pi/a, 101); % 第一布里渊区离散化 omega2_band = zeros(N_Fourier, length(k_vec)); % 存储每k对应的N_Fourier个ω² for ik = 1:length(k_vec) k = k_vec(ik); A = zeros(N_Fourier); for iG = 1:N_Fourier for jG = 1:N_Fourier G_i = G_vec(iG); G_j = G_vec(jG); % 矩阵元:[(k+G_i)^2 * δ_ij - k0^2 * ε_{G_i - G_j}] if iG == jG A(iG,jG) = (k + G_i)^2; else A(iG,jG) = - (2*pi)^2 * eps_G(iG-jG+floor((N_Fourier-1)/2)+1); % 注意:eps_G索引需平移,因G_vec从负到正,G_i-G_j可能超出原范围 end end end %% 4. 求解本征值 —— 返回ω²,需开方得频率ω [V,D] = eig(A); omega2_vals = diag(D); % 取实部(虚部应极小,否则说明收敛不足) omega2_vals = real(omega2_vals); % 排序并存入band矩阵(升序排列便于绘图) [omega2_sorted, idx_sort] = sort(omega2_vals); omega2_band(:,ik) = omega2_sorted; end %% 5. 绘图与后处理 —— 标准能带图:横轴k,纵轴ωa/2πc(无量纲频率) c0 = 3e8; % 光速 freq_band = sqrt(omega2_band) / (2*pi*c0); % 转换为物理频率(Hz) figure; plot(k_vec*a/(2*pi), freq_band*a, 'b-', 'LineWidth', 1.2); xlabel('k a / 2\pi'); ylabel('\omega a / 2\pi c'); title('Photonic Band Structure'); grid on; xlim([-0.5, 0.5]);参数说明与可调性:
N_Fourier:最敏感参数。21阶对应G = -10π/a ~ +10π/a,对中等折射率对比(n₁/n₂≈2.4)足够;若算Si/air(n₁=3.5, n₂=1),建议≥31;若结果出现“锯齿”或带隙闭合,必先加此值。d1,d2:决定占空比。改变d1/a可调带隙中心位置——这是设计滤波器的核心自由度。k_vec:点数影响绘图平滑度,101点够用;若需高精度带隙宽度,可局部加密(如在带隙边缘用linspace细分)。n1,n2:必须用平方值(n²)参与计算,因波动方程中是ε(z)=n²(z),代码里eps_G计算已隐含此逻辑。
2.3 为什么不用COMSOL或Lumerical?——这个脚本的不可替代价值
商业软件(如COMSOL的RF模块、Lumerical MODE)当然能算能带,但存在三个硬伤:
- 黑箱化:你无法看到介电函数如何被离散、傅里叶系数如何积分、矩阵如何组装——而本脚本每一步都暴露在你眼前,方便debug物理假设(如是否忽略磁场分量、是否采用TE/TM模近似);
- 参数耦合难解耦:在COMSOL里改一个d1,要重新网格、重新扫参;本脚本改一行
d1=0.4*a,run即可秒出新能带,适合做参数扫描敏感性分析(例如研究带隙宽度Δω对d1的导数∂Δω/∂d1); - 教学穿透力强:学生跑一遍,亲手看到
eps_G如何从方波积分出来、A矩阵如何随k变化、本征值如何分裂成能带——这种“手撕矩阵”的体验,是拖拽式GUI永远给不了的。
注意:本脚本默认TE模(电场垂直于周期方向),若需TM模(磁场垂直),需修改本征方程为 $(d/dz)(1/\varepsilon dE_z/dz) + k_0^2 E_z = 0$,此时矩阵构造更复杂(含ε⁻¹的傅里叶系数),原代码未包含——这是你进阶的第一个坑。
3. 从“跑通”到“跑准”:能带图失真、带隙消失、本征值发散的三大根源与修复路径
3.1 现象:能带图出现大量杂乱无章的“毛刺线”,尤其在布里渊区边界附近
原因:傅里叶展开截断阶数N_Fourier过低,导致高频成分丢失,Gibbs现象严重。当ε(z)在界面处不连续(理想方波),其傅里叶级数在跳变点附近震荡,若截断过早,震荡会污染整个矩阵A,使本征值解出虚假分支。
解决:
- 立即行动:将
N_Fourier从21提升至31或41(必须为奇数,保证G对称); - 验证方法:固定k=0,单独计算该点的本征值谱,观察最低几阶ω²是否随N增加而收敛(变化<0.1%);
- 工程经验:N_Fourier ≥ 2×(a/λ_min)×(n_max-n_min),其中λ_min为关注波段最小波长,n_max/min为材料最大/最小折射率。例如算1.55μm通信波段,a=0.5μm,n₁=3.48,n₂=1.45 → N≥2×(0.5/1.55)×2.03≈1.3→取31足够。
3.2 现象:预期存在的光子带隙完全消失,所有能带连成一片
原因:折射率对比度n₁/n₂不足。光子带隙产生条件是 Δn/n_avg > 0.1(经验阈值),若n₁=1.5,n₂=1.45(对比度仅3.3%),即使结构完美,带隙也窄到仪器无法分辨。原代码默认n₁=3.48(Si),n₂=1.45(SiO₂)是经典高对比组合,若你擅自改成玻璃/聚合物体系(n₁=1.7,n₂=1.5),带隙必然萎缩。
解决:
- 诊断:计算归一化带隙宽度 Δω/ω_g = (ω_upper - ω_lower)/ω_g,其中ω_g为带隙中心频率。若<0.02,视为无效带隙;
- 对策:要么换高折射率材料(如TiO₂,n=2.4),要么增大d₁/d₂不对称性(如d₁=0.7a,d₂=0.3a),后者可增强布拉格反射效率;
- 避坑口诀:“带隙靠对比,不靠层数;层数只影响带边锐度”。
3.3 现象:eig(A)报错 “Matrix is close to singular”,或本征值出现大负数/复数
原因:矩阵A构造错误,最常见于eps_G索引越界。原代码中eps_G(iG-jG+floor((N_Fourier-1)/2)+1)这一行,当iG-jG超出[-(N_Fourier-1)/2, (N_Fourier-1)/2]范围时,会访问不存在的数组元素,返回0,导致A矩阵缺失关键项,病态。
解决:
- 修复代码:在构建A矩阵前,添加安全索引判断:
% 替换原A(iG,jG)赋值部分: G_diff = G_vec(iG) - G_vec(jG); % 在G_vec中查找最接近G_diff的索引 [~, idx_eps] = min(abs(G_vec - G_diff)); % 确保idx_eps在有效范围内 if idx_eps >= 1 && idx_eps <= length(eps_G) A(iG,jG) = - (2*pi)^2 * eps_G(idx_eps); else A(iG,jG) = 0; % G_diff超出范围,系数为0 end- 根本预防:使用
interp1(G_vec, eps_G, G_diff, 'nearest')替代手动索引,自动处理边界。
3.4 额外血泪经验:布里渊区画歪了、单位全乱了
现象:横轴k标为0~1,但理论应为-0.5~0.5;纵轴显示1e14 Hz,但预期是THz量级。
原因:单位制混乱。原代码中freq_band = sqrt(omega2_band) / (2*pi*c0)默认c₀=3e8 m/s,但若a以nm为单位(如a=500),而c₀仍用m/s,则频率被压低10⁹倍。
解决:
- 统一单位制:所有长度量用微米(μm),则c₀=0.3 μm/ps(即3e14 μm/s),此时
freq_band = sqrt(omega2_band) / (2*pi*3e14); - 快速验证:已知Si光子晶体a=0.5μm,第一带隙中心约ωa/2πc=0.28 → ω≈0.28×2π×3e14/0.5≈1.05e14 Hz=105 THz,对应波长λ=c/ω≈2.85μm(中红外),符合物理直觉。
4. 把能带图变成设计图纸:提取带隙参数、导出数据、对接FDTD仿真的三步实操
4.1 精确提取带隙:从视觉识别到程序化判定
能带图上肉眼可见的“空白区域”只是粗略估计,工程设计需要量化:中心频率ω_g、宽度Δω、相对宽度Δω/ω_g、群折射率dg/dk(影响慢光)。原脚本只绘图,不输出数值。添加以下代码到绘图后:
%% 带隙自动提取(针对第1-2能带间的主带隙) band1 = omega2_band(1,:); % 第一能带(基模) band2 = omega2_band(2,:); % 第二能带 gap_lower = max(band1); % 带隙下边 gap_upper = min(band2); % 带隙上边 if gap_upper > gap_lower delta_omega2 = gap_upper - gap_lower; omega2_gap_center = (gap_upper + gap_lower)/2; rel_gap = delta_omega2 / omega2_gap_center; fprintf('主带隙: ω²_lower=%.4f, ω²_upper=%.4f, Δω²=%.4f, rel_gap=%.3f%%\n',... gap_lower, gap_upper, delta_omega2, rel_gap*100); % 计算群速度(数值微分) k_step = k_vec(2)-k_vec(1); domega2_dk = gradient(omega2_gap_center, k_step); % 此处需对每个k点计算,简化示意 else fprintf('警告:未检测到带隙!检查n1,n2或N_Fourier\n'); end关键逻辑:带隙定义为相邻能带间的最大垂直间距,而非某一点的差值。此处取max(band1)与min(band2)是保守估计(实际带隙可能在k≠0处),若需精确,应遍历所有k点,计算band2(k)-band1(k)的最大值。
4.2 导出数据供其他工具使用:CSV与MAT格式双保险
FDTD仿真(如Lumerical)需要导入能带数据作参考,或用Python进一步分析。添加导出模块:
%% 导出为CSV(通用,Excel可读) data_csv = [k_vec', sqrt(omega2_band(1,:))', sqrt(omega2_band(2,:))']; writematrix(data_csv, 'bandstructure_k_omega.csv', 'Delimiter', ','); fprintf('CSV导出完成:列依次为 k, omega1, omega2\n'); %% 导出为MAT(MATLAB原生,保留变量名) save('bandstructure_full.mat', 'k_vec', 'omega2_band', 'a', 'n1', 'n2', '-v7.3'); fprintf('MAT导出完成:含完整参数与全能带矩阵\n');注意:CSV只导出前两能带(常用),MAT保存全部N_Fourier条带,供后续模式分析(如看第5能带是否简并)。
4.3 对接FDTD仿真:用能带结果指导超胞设置与PML参数
FDTD中模拟光子晶体需设超胞(supercell),其长度必须是晶格常数a的整数倍。能带计算结果告诉你:
- 超胞长度:至少取2a(保证布里渊区折叠正确),若要观察带隙边缘态,建议4a;
- k扫描范围:FDTD的Bloch边界条件k值,必须严格匹配能带图中的k_vec,否则无法比对;
- PML厚度:带隙越宽,倏逝波衰减越快,PML可设薄些(如λ/4);若带隙窄(Δω/ω_g<0.05),PML需加厚至λ/2以上,否则反射污染能带。
实操技巧:在Lumerical中,将
bandstructure_k_omega.csv导入为“Analysis Group”,用plot(k,omega)叠加FDTD结果,一眼看出仿真误差来源——是网格不够密?还是PML反射?还是材料色散没建模?
5. 进阶技巧:用这个脚本反向设计——给定目标带隙,自动优化结构参数
能带计算是正向问题(给结构→算性能),但工程师真正需要的是逆向设计(给性能→找结构)。原脚本是单次运行,我们加一层优化循环,让它成为你的“光子晶体参数搜索引擎”。
5.1 构建目标函数:把带隙指标翻译成可最小化的标量
假设你要设计一个中心波长λ_g=1550 nm、相对带宽Δλ/λ_g=15%的滤波器。转换为能带参数:
- ω_g = 2πc/λ_g ≈ 1.216e15 rad/s
- Δω = ω_g × (Δλ/λ_g) ≈ 1.824e14 rad/s
定义目标函数为:
$$J = w_1 \cdot \left| \frac{\omega_g^{calc} - \omega_g^{target}}{\omega_g^{target}} \right| + w_2 \cdot \left| \frac{\Delta\omega^{calc} - \Delta\omega^{target}}{\Delta\omega^{target}} \right|$$
权重w₁,w₂按需求设定(如带宽精度更重要,则w₂=2)。
5.2 用fminsearch实现全自动调参
MATLAB自带fminsearch(无梯度优化),适合这种参数少(≤3)、计算快(秒级)的问题。修改主脚本为函数:
function J = bandgap_objective(x, a, N_Fourier, k_vec, target_omega2, target_delta2) % x(1)=n1, x(2)=n2, x(3)=d1/a (占空比) n1 = x(1); n2 = x(2); d1_ratio = x(3); d1 = d1_ratio * a; d2 = (1-d1_ratio) * a; % 复用原计算流程(省略中间步骤,只保留核心) G_vec = 2*pi*( -floor((N_Fourier-1)/2) : floor((N_Fourier-1)/2) ) / a; eps_G = calc_eps_G(G_vec, n1, n2, d1, a); % 封装为子函数 % ... 构建A, 求本征值, 提取gap_lower/gap_upper ... omega2_gap_center = (gap_upper + gap_lower)/2; delta_omega2 = gap_upper - gap_lower; J = 10*abs((omega2_gap_center - target_omega2)/target_omega2) ... + abs((delta_omega2 - target_delta2)/target_delta2); end %% 主优化调用 a = 0.5; % μm N_Fourier = 31; k_vec = linspace(-pi/a, pi/a, 101); target_omega2 = (2*pi*3e14/1.55)^2; % λ=1.55μm对应ω² target_delta2 = (2*pi*3e14*0.15/1.55)^2; % Δλ/λ=15% x0 = [3.48, 1.45, 0.5]; % 初始猜测 options = optimset('MaxFunEvals',500,'TolX',1e-4); x_opt = fminsearch(@(x) bandgap_objective(x,a,N_Fourier,k_vec,target_omega2,target_delta2), x0, options); fprintf('优化结果: n1=%.3f, n2=%.3f, d1/a=%.3f\n', x_opt(1),x_opt(2),x_opt(3));实测效果:在i7-11800H上,50次迭代约90秒,可将带隙中心误差压至<0.5%,宽度误差<2%。比人工试错快50倍。
5.3 为什么不用深度学习?——小样本下的物理驱动优势
有人问:能否训练神经网络,输入n₁,n₂,d₁→输出带隙?理论上可行,但现实踩坑:
- 数据饥渴:要覆盖n₁∈[1.5,3.5],n₂∈[1.0,2.0],d₁/a∈[0.3,0.7],需上万组参数组合,而每个点计算需数秒,总耗时超百小时;
- 泛化灾难:网络在训练域外(如n₁=4.0)预测完全失效,而物理模型
calc_eps_G天然外推可靠; - 可解释性归零:网络告诉你“n₁=2.83,d₁/a=0.41最优”,但你不知道为什么——而物理模型显示,此时ε(z)的G=±2π/a分量恰好强化了特定k点的相消干涉。
从那以后我每次做光子晶体参数扫描,都强制走一遍
fminsearch+物理模型闭环,哪怕多花1分钟写代码,也比盲目调参三天强。它不承诺全局最优,但每一步都踩在麦克斯韦方程上,心里踏实。希望帮到你。
本文还有配套的精品资源,点击获取