MATLAB手写FDTD:从一维到二维电磁仿真与波前分析
2026/9/8 3:49:05 网站建设 项目流程

简介:这套FDTD MATLAB程序合集面向电磁场与光子学方向的研究生、工程师以及正在学习时域有限差分方法的初学者,覆盖从一维到三维的完整算法示例,可用于光子晶体带隙分析、TM模式模拟、散射体建模和天线辐射等场景。压缩包内共19个文件,其中18个为.m脚本,另有1个说明文档;脚本按维度拆分,既包含fdtd_1d、fdtd_2d_demo等入门演示,也包含FDTD3D_Main、demo_3dFDTD等进阶三维程序,并涉及PML边界、色散媒质及波导结构等关键处理,便于对照运行和修改参数。资源包仅32KB,轻量易用,已有516人学习下载。借助这些程序,学习者可以直观理解Yee网格迭代、边界条件设置与后处理流程,并将其迁移到自己的光子晶体或微波器件仿真任务中,是一份兼顾原理与代码实现的实用参考资料。 做电磁仿真的人,大概率绕不开FDTD这三个字母。做光学和微波器件的人更是如此,从微纳光子结构、超表面设计到波导耦合分析,FDTD(时域有限差分法)几乎是入门必须掌握的数值算法。而我个人最推荐的落地方式,就是直接用MATLAB手写 FDTD 程序——不依赖黑盒商业软件,每一步都能看到物理图像,适合学习、验证、发论文时快速出图。

这篇文章我会从 FDTD 的原理讲起,给出一维、二维的 MATLAB 可运行代码,再结合我做光波导和透镜波前分析时踩过的坑,把参数设置、边界条件、结果验证、常见报错这些实操细节一次说清楚。不管你是刚接触电磁仿真的学生,还是想把手头商业仿真器结果和自研代码做对照的研究人员,这篇都值得花十分钟看完。

1. FDTD 仿真的底层逻辑:为什么这套算法经久不衰

1.1 麦克斯韦方程组与 Yee 网格

FDTD 的核心思想特别朴素:把麦克斯韦方程组里对时间和空间的偏导,用有限差分去近似,然后在计算机上一步步“推进”电磁场在时空中的演化。

麦克斯韦方程组里,时变电场和磁场通过两个旋度方程耦合在一起:

∂E/∂t = (1/ε)∇×H - σE
∂H/∂t = -(1/μ)∇×E - σ*H

如果直接用常规网格离散,会遇到电、磁场分量不好对齐的问题。1966 年 Yee 提出了一种交错网格,把电场放在网格棱边中心,磁场放在网格面中心,让每个磁场分量周围都有四个电场分量环绕,每个电场分量周围也有四个磁场分量环绕。这样一来,旋度运算天然可以用中心差分表示,空间二阶精度,而且电、磁场在时间上也差半个时间步,形成一种“蛙跳式”更新序列。

这种离散方式的好处是:不需要求解大规模矩阵方程,每一步只需重新排列网格里的场量,内存开销低,天然适合并行。惩罚是时间步长受 Courant 条件限制,不能取太大,否则数值不稳定,表现为场量发散成 NaN。

1.2 为什么用 MATLAB 写 FDTD 而不是 Python / C++

我接触过不少做仿真的人,一上来就纠结语言。我的态度很明确:学习阶段首选 MATLAB。原因有几个:

第一,MATLAB 的矩阵运算和切片操作极其逼近 FDTD 的网格逻辑,代码写出来和公式几乎一一对应,debug 时尤其舒服。第二,绘图能力太强了,二维场分布用 imagesc 一行就能出彩图,动画演示可以用drawnow刷新,学习时那种“看到脉冲在空间里跑”的反馈感,比任何抽象讲解都有效。第三,工程上有大量后续处理要做,FFT、滤波、参数扫描,MATLAB 都有现成函数。

Python 用 NumPy 也能写,但代码里索引和多维数组处理要额外注意维度顺序,对新手不那么直观。C++ 适合追求性能的工业级仿真,但开发周期长,不适合验证算法思路。等你要跑大规模三维仿真,再换 C++ 或 Fortran 也不迟。

1.3 适用场景:不只光学,还覆盖微波、天线、光子晶体

FDTD 的最大魅力是宽频谱、宽场景。输入一个宽带脉冲源,一次仿真就能通过傅里叶变换得到宽频响应。典型应用包括:

  • 光学:波导模式分析、超表面透反射率计算、等离激元结构近场分布
  • 微波与天线:微带天线 S 参数、雷达散射截面 RCS
  • 光子晶体:带隙扫描、缺陷模分析
  • 生物电磁:人体组织内的比吸收率 SAR 仿真
  • 周期性结构:光栅衍射效率、金属网栅透过率

下面我要讲的一维和二维程序,就是覆盖这些场景的“最小骨架”。把骨架吃透,往三维扩展只是体力活。

2. 一维 FDTD 程序:半小时搭起第一个可用模型

2.1 参数设定与 Courant 稳定条件

从一维开始是最高效的学习路径。一维情况下,假设电磁波沿 z 方向传播,电场分量 Ex、磁场分量 Hy,麦克斯韦方程组可以简化成两个标量方程:

∂Ex/∂t = -(1/ε) ∂Hy/∂z
∂Hy/∂t = -(1/μ) ∂Ex/∂z

离散化后,经典更新公式如下:

Hy(i) = Hy(i) - (dt / (μ dz)) * (Ex(i+1) - Ex(i))
Ex(i) = Ex(i) - (dt / (ε dz)) * (Hy(i) - Hy(i-1))

这里电、磁场空间上差半个网格,时间上差半个步长,所以叫“蛙跳”格式。

大家最关心的 Courant 稳定条件,一维形式是:

c·dt / dz ≤ 1

其中 c 是介质中的光速。这个公式的意思是:一个时间步内电磁波传播的距离不能超过一个网格尺寸,否则数值上信息传播速度超过物理光速,必然发散。实际中我通常取 Courant 数 0.5,留足余量,尤其是介质材料介电常数不均匀或设定为有耗媒质时,余量太大会让仿真中途崩掉。

参数初始化代码这样写:

% 一维FDTD参数设定 nx = 1000; dx = 1e-8; % 网格尺寸10nm c0 = 3e8; dt = 0.5 * dx / c0; % Courant数取0.5 nt = 2000; eps0 = 8.854e-12; mu0 = pi * 4e-7; % 系数预先算好,避免循环内重复计算 Ce = dt / (eps0 * dx); Ch = dt / (mu0 * dx); Ez = zeros(1, nx); Hy = zeros(1, nx);

创建时间:2026-04-26 20:00

2.2 核心更新方程与激励源

核心的时间推进循环是最简单的三层结构:先更新磁场,再更新电场,然后添加激励源。写成 MATLAB 就是:

% 高斯脉冲源参数 t0 = 100; tau = 30; src_pos = 50; for t = 1:nt % 磁场更新 for i = 1:nx-1 Hy(i) = Hy(i) - Ch * (Ez(i+1) - Ez(i)); end % 电场更新 for i = 2:nx Ez(i) = Ez(i) - Ce * (Hy(i) - Hy(i-1)); end % 高斯脉冲激励 src = exp(-0.5 * ((t - t0) / tau)^2); Ez(src_pos) = Ez(src_pos) + src; end

这里要注意一个细节:源是有极性的,我通过Ez(src_pos) = Ez(src_pos) + src直接把源“注入”到电场分量上,这是总场散射场框架的雏形。如果想加正弦连续波,就把src换成sin(2*pi*f0*t*dt);如果想做宽带频谱分析,高斯脉冲是更好选择,因为它的频谱也是高斯形状,低频从零开始,覆盖范围广。

循环内我直接用zeros初始化整个数组,其实换成zeros(nx-1,1)之类的列向量性能略优,但为了代码可读性,先用最简单形式。等仿真规模大起来,性能优化章节再处理。

2.3 边界处理:SABC 与 PML 的取舍

计算区域是有限的,但物理空间是无限的。如果边界不做处理,波传到边界会产生反射,污染仿真结果。最简单的处理方式是吸收边界条件 SABC,一阶形式下相当于在边界对场做衰减:

% 在一维循环末尾加吸收边界(SABC一阶) Ez(1) = Ez(2); Ez(end) = Ez(end-1);

这段代码的含义是:边界处的场近似等于相邻内点场的延迟复制,相当于波“走出”边界。这种方式对正入射的平面波效果尚可,但对斜入射或大角度散射就有明显残余反射。所以但凡要做透射率、反射率计算,我建议至少在边界加 PML(完美匹配层)。MATLAB 里实现 PML 不复杂,二维时我会给出具体层数和参数建议,一维时先用 SABC 跑通流程,观察波形传播是否正确。

2.4 完整代码与结果验证

把上面的循环跑完,用imagesc(Ez)画时空图,你会看到高斯脉冲从源位置向两侧传播,碰到边界后基本不反射。判断程序对不对,最直接的是看“脉冲移动速度”:在时空图上,波的轨迹斜率应该接近 CFL 数*dz/dt,即接近真空光速 c0。如果波速明显偏慢,通常是离散误差大,网格不够细;如果直接出 NaN,基本就是 Courant 条件没满足。

3. 二维 FDTD 与光波导 / 透镜仿真实战

3.1 从一维到二维:TM 模式的方程改写

一维只是热身,真正实用的是二维。这里以 TM 模式为例(电场只有 Ez 分量,磁场有 Hx、Hy 三个分量),更新公式变为:

Hx(i,j) = Hx(i,j) + (dt / (μ dz)) * (Ez(i,j+1) - Ez(i,j))
Hy(i,j) = Hy(i,j) - (dt / (μ dz)) * (Ez(i+1,j) - Ez(i,j))
Ez(i,j) = Ez(i,j) + (dt / (ε dz)) * (Hy(i,j) - Hy(i-1,j)) - (dt / (ε dz)) * (Hx(i,j) - Hx(i,j-1))

如果网格等宽 dx = dy = dz,前三行可以统一写成:

% 二维FDTD TM模式更新循环(核心部分) Hx(:, 1:ny-1) = Hx(:, 1:ny-1) + ch * (Ez(:, 2:ny) - Ez(:, 1:ny-1)); Hy(1:nx-1, :) = Hy(1:nx-1, :) - ch * (Ez(2:nx, :) - Ez(1:nx-1, :)); Ez(2:nx-1, 2:ny-1) = Ez(2:nx-1, 2:ny-1) ... + ce * (Hy(2:nx-1, 1:ny-2) - Hy(1:nx-2, 1:ny-1)) ... - ce * (Hx(2:nx-1, 2:ny-1) - Hx(2:nx-1, 1:ny-2));

看到没有,向量化后的代码非常清爽,比 for 循环快一大截。这里的 ch 和 ce 分别是磁场和电场的更新系数,和一维类似,只是需要注意 MATLAB 数组索引从 1 开始,边界索引处理稍繁琐。

3.2 光源设置:高斯光束与平面波

二维仿真的光源选择直接决定结果有效性。做波导耦合分析时常用高斯光束源,公式表示为:

% 二维高斯光束激励源,沿x方向传播 x0 = 20; y0 = ny/2; w0 = 30; % 束腰中心和束腰宽度 k = 2*pi/lambda; % 每个时间步在源平面注入横向高斯分布 Ez(x0, :) = Ez(x0, :) + exp(-((1:ny) - y0).^2 / w0^2) * sin(2*pi * freq * t * dt);

平面波源则更简单,直接在一条线上安装等幅激励:

Ez(x0, :) = Ez(x0, :) + sin(2*pi * freq * t * dt);

但平面波源有个问题:如果入射角度非正朝边界,需要考虑斜入射的相移,否则波前会歪掉。做透射式光栅仿真时我会建议预留“总场-散射场”结构,把一个区域定义为入射场区域、一个区域定义为散射场区域,这样透射率和反射率可以直接在边界面上做场采样,不用后期手动扣除入射背景。逻辑清晰,输出也直观。

3.3 波前分析与透射率计算

热词里出现“matlab 透镜波前分析”“matlab 光学追踪实现波前”,这正是 FDTD 的一个常见进阶玩法。仿真完成后,你在时域得到的是瞬态场 Ez(t),通过傅里叶变换可以提取任意频率的稳态复振幅分布:

% 在某一频率处提取稳态场 E_monitor = fft(Ez_monitor, [], 3); % 第三维是时间 E_freq = E_monitor(:, :, freq_idx);

提取复数振幅后,相位就是angle(E_freq),强度就是abs(E_freq).^2。沿着传播方向逐截面采样,就能重建波前形状,等效于在仿真区域里放一个虚拟的波前传感器。透镜的聚焦点位置、焦距、波前畸变都可以这样定量验证。

透射率计算也有固定套路:在结构后方放一个功率监视器,记录 Poynting 矢量对时间的积分;再跑一遍无结构空场作为参考,归一化后就是透射谱。注意监视器要放在 PML 之前,否则边界吸收会吃掉一部分功率。

4. 工具箱选择与 MATLAB 工程化技巧

4.1 自己写还是用工具箱 / 现有代码包

市面上能直接用 MATLAB 跑的 FDTD 开源包不少,比如 MEEP(有 MATLAB 接口)、ANGEL 等。但我的观点是:即便最后要用商业软件做大规模仿真,第一版 FDTD 程序也应该自己写一遍。原因很简单:FDTD 的公式和更新算子是后续一切复杂功能(PML、色散介质、非线性材料)的地基,地基你没亲手垒过,上面盖多少层心里都没底。

如果你只是快速验证某个结构的频谱响应,不想写代码,可以搜 GitHub 上成熟的一维/二维 FDTD 脚本,但我强烈建议把里面每一行都改懂再跑数据。当作学习资料是宝贝,当黑盒工具用就是定时炸弹。

4.2 矢量化、内存与性能优化

同样是 1000×1000 网格跑 2000 步,for 循环可能要跑几分钟,向量化后秒级完成。关键是充分利用 MATLAB 的数组切片语法。还有几个实测有效的优化点:

  • 使用single精度。很多光学仿真本身数值误差占主导,单精度足够,内存减半。
  • 避免在时间循环里调用expsin。把源项和时间相关数预先算好存成数组,循环里只是“取值”而不是“计算”,能快一倍不止。
  • 每 50~100 步存一次场快照,不要每步都imagesc。我用过VideoWriter生成动画,配合drawnow可以实时预览,但每 5 步刷新一次足够,不然 IO 开销巨大。
  • 如果要算宽频响应,可以在激励源里加入多个频率的正弦信号叠加,一次仿真同时提取多个频点,避免重复跑多次。

4.3 与光学追踪 / 波前分析的结合点

热词里那几条“光学追踪”“透镜波前分析”其实和 FDTD 是两条技术路线:几何光学追踪适合处理宏观尺度、像差不大、衍射效应不强的光学系统;FDTD 适合处理亚波长结构和强衍射、强散射场景。但两者可以互补——先用光学追踪设计一个透镜的宏观光路,再用 FDTD 模拟透镜表面的亚波长抗反射结构或超构表面修饰层,这样既能保证系统级的像差控制,又能体现场级电磁精度。

在 MATLAB 里做这个衔接,无非是让几何光路给 FDTD 提供入射波前参数(波面曲率半径、倾斜角、束腰),再用 my ISO、波前传感器等方式对比仿真结果。把焦距、焦点位置、Strehl 比这些指标算出来,就相当于一篇论文的一部分了。

5. 常见问题与排查技巧实录

5.1 仿真发散:先查 Courant,再查介质参数

我遇到过的发散情况九成是 Courant 条件没满足。排查思路是:先不管结构,直接跑空场,看平面波传播是否稳定;如果空场都炸,多半是 dt 太大,或者网格尺寸不均匀导致局部 CFL 超标。另一个容易忽略的原因是介质参数设置出负值,比如有耗材料的电导率 σ 写成了电场更新公式的系数,导致等效介电常数变负。

5.2 边界反射:SABC 不够就要上 PML

如果透射谱出现明显的高频震荡纹波,或者近场图在边界处出现环形干扰纹,第一个怀疑对象就是吸收边界。一维的 SABC 大概能吸掉 90% 反射,二维做透射率时这个数字不够看,必须加 PML。PML 的层数我一般用 8~12 层,电导率剖面采用多项式分布,系数取 2~4 之间,实测反射率能做到 -60 dB 以下。注意 PML 内的材料参数是各向异性张量,直接在 MATLAB 里写会比较啰嗦,但只要把每层电导率存成矩阵,更新公式里加一个衰减因子即可。

5.3 MATLAB 版本与工具箱的坑

不少人在 MATLAB 版本上栽过跟头。装新版本后license报错、App Designer打不开、对旧脚本兼容性下降,这些都是真实遇到的。我的建议是实验室统一版本,至少在投稿复现时注明版本号和工具箱版本。FDTD 代码本身只依赖基础 MATLAB,不需要额外工具箱,但如果你用了Image Processing Toolbox做后处理,要注意imshowimagesc的坐标轴方向差异,后者 Y 轴默认朝下,做波前图时要set(gca,'YDir','normal')翻转,否则剖面图会上下颠倒。

如果遇到“MATLAB 远程桌面打不开”这类问题,通常是许可证的图形界面检查和显示驱动冲突,把图形硬件加速关闭即可,和算法本身无关。

5.4 结果验证:怎么知道仿真算得对

最后这点很关键,但很多人不注意。我建议任何 FDTD 代码都至少做三组验证:

  1. 空场平面波传播一维/二维,看波速是否匹配理论值
  2. 高斯脉冲通过已知厚度的介质平板,对比 Fresnel 公式理论透射率
  3. 短偶极子辐射场,对比解析解的方向图

这三组验证只要能对上,你的代码基本靠谱。之后再做复杂结构,结果的可信度才有根基。我遇到不少同学空场上没问题,一旦加结构就怀疑软件有 bug,结果定位下来都是结构定义里边界条件写错。

写在最后

FDTD 看起来代码不多,但每一步都藏着数值分析和电磁理论的选择。我做了这么多年仿真,最深的体会是:不要急着追求大而全的代码框架,先让你的最小程序“可信”起来。一维验证一遍二维,二维验证透了再上三维和 PML 优化,每一步都有对照物,出问题才容易定位。

如果你刚开始入门,建议把上面的一维程序亲手敲一遍,换几个参数观察波速和波形变化;跑通了之后,再往程序里加一个介质层,你会第一次感受到“透射率随频率振荡”的物理图像从自己手底下冒出来——那个感觉,真的比直接调商业软件爽太多。

本文还有配套的精品资源,点击获取

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

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

立即咨询