简介:这是一份基于FDTD(时域有限差分)方法的电磁仿真程序,使用MATLAB实现近场到远场的数据转换,面向电磁场理论学习者、天线与微波工程师,以及需要分析辐射特性的研究人员。程序核心代码为单个m文件,通过空间网格和时间步迭代求解麦克斯韦方程组,并配合PML完美匹配层吸收边界降低反射干扰;随后存储近场数据,利用傅里叶变换将时域响应转换为频域远场方向图,从而支撑方向图绘制、增益评估等应用。压缩包仅包含1个m文件,大小约4KB,结构简洁,便于直接运行和学习。目前已有829人学习。通过研读该程序,可理解FDTD网格设置、激励源布置、边界条件处理及近远场变换的完整流程,对于天线辐射、雷达散射等工程问题具有直接参考价值,适合有一定电磁场基础的读者作为进阶练习。
1. FDTD 近远场变换到底在算什么:从一句让人懵的报错说起
第一次跑 near to far,是 FDTD 近远场变换里最容易让人怀疑人生的节点:仿真区域明明只包住了几个波长的结构,脚本却在报“监视器不在记录面附近”一类错误。于是有人开始改网格、重摆结构、甚至怀疑工程包坏了。其实问题不在“算”,而在“近场记录面”这个前提。FDTD 用局部网格做时域求解,远处电场直接算既不经济也不现实,near to far 变换把闭合记录面上的近场当作等效源,用惠更斯积分一次性推算出任意方向的远场分布。
天线方向图、散射截面、偏振转化效率这些量,全要从这一步拿到。这篇把 EM 仿真里这条线拆开:什么时候该做、最小脚本怎么写、addmode 和 GPU 会影响什么、偏振转化效率怎么抠出来,最后给五条我踩过的坑。适合正在做纳米光子、超表面、天线辐射或 RCS 计算的人,尤其是拿到打包好的工程脚本却跑不出理想远场的那批。
2. 近远场变换的原理与选型:为什么局部网格算不出远方与何时该做投影
做近远场变换之前,先想明白一个问题:FDTD 为什么不能直接“把网格划大一点”把监视器放到远处?因为三维网格数是按体积长大的,从结构表面往外推 10 个波长的距离,网格量就是 1000 倍量级,再加上完美匹配层还要占空间,这个账怎么算都不划算。近远场变换是把“时域求解”和“远处观测”解耦的关键,也是 FDTD 里少数可以“先算完再外推”的环节。
2.1 等效源思想:近场记录面为什么是一切的起点
FDTD 的近远场变换,数学上就是 Stratton-Chu 积分的外推。它把近场记录面上的切向电场和切向磁场当成等效电流和等效磁流,再在均匀空间里用解析的格林函数把这些源传播到任意远场点。这意味着一个硬前提:记录面上的场必须是“唯一且可外推”的,也就是说记录面要包住所有产生散射的金属结构、介质边界和非线性区域,并且记录面本身要落在均匀介质里。
很多工程包把这件事封装成“远场监视器(farfield monitor)”,你把它往结构附近一放,求解器自动记录面上每一点的 E 和 H。但封装不代表不需要懂前提——我见过不止一次把记录面放在基板内部的,外推出来的远场会带上一堆来自基板模式的假信号,方向图怎么看怎么怪。
另一条容易被忽略的是记录面的形状。闭合箱形监视器适合包一个独立散射体;单个平面监视器只适合朝向固定的辐射场景,比如天线朝 +Z 辐射而你只关心上半空间。选错形状,数学上等于等效源缺失,方向图缺瓣是必然结果。判断依据就一句话:你的观察点是否在本仿真网格域内。观察点在域内就直读近场监视器,在域外就必须投影。
2.2 什么时候值得做 near to far,什么时候直接读近场监视器
近远场变换不是万能的。局域电场增强、近场热源分布这类问题,你要的是结构表面附近几十纳米内的强场,直接读近场监视器即可;强行投影到远场反而把亚波长细节抹平了。反过来,只要目标量涉及“远处某个方向的辐射或散射”,就必须做 near to far,因为网格域内没有那个观察点。
| 场景 | 直接读近场监视器 | 做 near to far 投影 |
|---|---|---|
| 局域场增强、SPP 近场分布 | 合适,信息最完整 | 不必要,反而丢失细节 |
| 天线方向图、辐射功率 | 只能看包裹面 | 必须做 |
| 超表面偏振转换效率 | 透射近场可近似 | 推荐做,能看到角度分辨偏振 |
| RCS / 散射截面 | 受边界反射影响 | 必须做 |
偏振转换效率推荐做投影,是因为你要从透射远场里分离共线偏振和交叉偏振的功率。近场监视器给出的局部场强虽然也有偏振信息,但要干净地定义“远场某个方向的偏振分量”,还是球坐标基矢下的投影结果更可靠。尤其是对超表面这类对相位分布敏感的结构,近场一点点相位误差,投影到远场会被放大成旁瓣。
2.3 2D 与 3D 投影的写法差异:farfield2d 与 farfield3d
仿真维度决定投影函数。二维 FDTD 里,结构在 xy 平面、z 方向无限延伸,远场是柱面波,外推用 farfield2d,几何因子是 1/sqrt(r) 的量级;三维里远场是球面波,用 farfield3d,几何因子是 1/r。混用这两者,角度分布的形状会带上一截不自然的尾巴,大角度区域拖得很长,像方向图旁边粘了一块披萨。
更隐蔽的问题在监视器本身。二维仿真的远场监视器在 GUI 里看起来是一条线,三维里才是一个面或箱体。工程包的脚本如果按三维箱体写了,直接套到二维模型上,记录面不闭合,投影出来的方向图会少一大块。所以拿到工程包第一步,先确认仿真维度,再看监视器类型。
最后聊一个常被当作玄学的间距:记录面和完美匹配层的位置。有的工程包为了压缩内存,把记录面贴着 PML 放,PML 的吸波作用会让记录面上的切向场带上衰减伪影。我一般至少让记录面离 PML 内边界有一个网格到半个波长的余量,投影出来的方向图才不带着“吸波”痕迹。材料色散同理,投影积分假设记录面外部是均匀无色散介质,记录面尽量留在空气或纯介质区,别穿过色散材料。
3. 用 near_to_far_EM 工程包跑通最小近远场流程:监视器、脚本与三步操作
3.1 工程包里通常有什么:先别急着点运行
像 near_to_far_EM 这类打包好的近远场工程,里面一般能见到三类文件:仿真结构文件、主脚本(.lsf 或 Python API 脚本)、后处理脚本。结构文件负责几何和材料,主脚本负责加监视器、跑仿真、调投影,后处理负责画方向图、算效率。我的习惯是先打开主脚本扫一遍,确认三件事:结构是否完整、监视器类型是否为 farfield、脚本开头有没有仿真维度开关。
包不是越跑越熟的,很多人拿到包直接 Run,然后对着报错发懵。这个报错十有八九是监视器类型没设对,或者记录面没有完全包住结构。先看脚本,再跑仿真,能省掉至少半小时的翻车时间。另外,把脚本里所有setnamed的监视器名列出来,和 GUI 里的对象树对一遍,名字对不上时投影函数会静默返回空矩阵,比报错更难排查。
3.2 最小脚本:铺记录面、跑仿真、做三维远场投影
把一次三维近远场流程压缩到最小,脚本大致长这样(以常见 FDTD 工具的脚本语言为例):
# 切换到布局模式,清空旧对象 switchtolayout; deleteall; # 新建一个监视器并设为远场监视器 addmonitor; setnamed("monitor", "monitor type", 6); # 6 对应 farfield,以 GUI 下拉为准 setnamed("monitor", "name", "n2f_rec"); # 添加模式扩展光源,校准激励模式 addmode; # 跑 FDTD 求解 run; # 对记录面做三维近远场投影,返回矩阵含远场复电场分量 E = farfield3d("n2f_rec");第一段是布局与清理,避免上次仿真残留的对象干扰投影。第二段创建远场监视器,其中“monitor type=6”这个编号在不同版本里不完全一致,我一般以 GUI 新建监视器时的下拉列表为准,脚本里写编号只是为了可复现。第三段的 addmode 负责把光源设置成目标模式,对波导类结构尤其重要,后文第 4 章会展开。
最后一行farfield3d是核心:它读记录面上的近场,外推出远场球面上每个角度点的电场,返回一个矩阵。这个返回矩阵要特别留意行序,常见排布是若干行分别对应 theta 方向分量、phi 方向分量和半径方向分量,具体以你所用版本的 help 输出为准。我吃过一次亏,以为第 1 行是总场,拿去算效率,结果算出来的是单个偏振分量,整个方向图都偏了。
注意:farfield3d 返回矩阵的行序在各版本间不完全一致,务必先打印 shape 或查看函数帮助确认行定义,再进入后处理。
| 参数 | 取值示例 | 作用 |
|---|---|---|
| monitor type | 6(farfield) | 决定监视器记录近场切向场 |
| addmode 默认模式 | 1 阶 | 参与激励的模式阶数 |
| theta 范围 | 0 到 90 度 | 远场投影的仰角范围 |
| phi 范围 | 0 到 360 度 | 方位角范围 |
| 角度采样 | 181 × 361 | 决定方向图分辨率和内存占用 |
3.3 修改投影角度与距离:从全局方向图到定点场强
投影角度不是越多越好。把 theta 范围设成 0 到 180 度,意味着记录面上每一个点要对半球每个角度做积分,采样点翻倍,时间和内存也跟着涨。如果目标只是看主瓣,0 到 90 度足够;要做全空间方向图再提到 0 到 180 度。投影角范围通常是要在监视器属性里设好的,或者用 farfield3d 的重载参数传入,工程包的参数区一般会空出来让你填。
还有一个常被误解的参数:距离。near to far 外推给出的是远场角分布,不是某个绝对距离上的真实场强。想得到绝对功率,必须拿光源的入射功率做归一化。具体做法是先跑一个无结构参考仿真,用同一个监视器记录入射功率,再把投影结果除以这个基准。工程包的后处理脚本里通常有归一化步骤,但分母是哪个监视器的功率很多人没注意就直接画图,两条曲线差一个量级。
到这一步,你已经能从同一个远场监视器里读出不同角度的复振幅。角度有了、相位有了、偏振分量有了,下一步就是决定要不要开 GPU 加速,以及怎么从这些复振幅里算偏振转化效率。
4. addmode、GPU 与偏振转化效率:近远场脚本里绕不开的三个参数
4.1 FDTD 脚本语言里的 addmode:模式扩展为什么会影响近场记录面
在 FDTD 脚本语言里,addmode是模式扩展相关命令的入口。它可以把某个监视器区域内的场分解到一组本征模式上,也可以用来添加模式光源。近远场流程里用到它,通常是两种目的:一是激励条件要对准目标模式,二是把透射场分解成不同模式的贡献。
模式扩展对近远场的影响往往写在暗处:如果光源是平面波而实际结构支持的是波导模式,入射场会在记录面上激发一堆高阶模式,这些模式的倏逝分量衰减慢,记录面离结构稍近就会被积分进远场,方向图多出旁瓣。用 addmode 把激励校准到目标模式后,近场记录面上的相位分布不再是一堆模式叠加的混沌场,投影结果才干净。
一个和脚本性能相关的细节:addmode 创建的监视器也会参与近远场投影的输入。如果这个辅助监视器的平面和远场监视器的记录面相交,求解器会把两套场都交给投影函数,内存翻倍还是小事,不同监视器的网格插值不一致时,结果会出现细微的栅格噪声。所以脚本里不用的 addmode 监视器,跑投影前先删掉或者 deactivate,这个动作我每次都会做。
4.2 怎么开启 GPU,并确认投影真的跑在 GPU 上
FDTD 怎么开启 GPU,主流工具里通常在求解器设置页有对应开关,脚本里也对应属性,比如把 FDTD 对象的 GPU 开关置为开启。开 GPU 的本质是把 FDTD 的时域迭代挪到显卡上做,网格越大加速比越明显。但三维仿真里,近远场监视器要记录大量切向场数据,显存占用比纯网格计算更高,这一条经常被忽略。
开启之后别急着庆祝。很多人发现开了 GPU,同样的仿真反而更慢了,原因多半是显存不足导致求解器静默回退到 CPU,白跑一遍。怎么确认真的跑在 GPU 上?我的做法是看仿真状态页或者系统任务管理器,GPU 利用率有持续波动才算数。如果 GPU 列表里显示可用但利用率为零,基本就是回退了。
| 检查项 | 预期表现 | 异常时的可能原因 |
|---|---|---|
| GPU 利用率 | 求解迭代期间持续波动 | 显存不足静默回退 |
| 显存占用 | 不超过显卡总显存 | 近场监视器采样过密 |
| 投影阶段行为 | CPU 计算,GPU 空闲正常 | 投影未走 GPU,属正常现象 |
最后补一句:近远场投影本身也就是积分外推那一段,在我的经验里大多在 CPU 上跑,因为它是串行读场再外推,和 FDTD 的迭代并行结构不同。GPU 加速的主要收益在求解阶段,不在投影阶段,这个预期要摆正。否则你会盯着 GPU 利用率发呆,以为投影卡住了。
4.3 偏振转化效率怎么从远场数据算:先定好入射偏振再谈转化
偏振转化效率是超表面和偏振器件里最常被追问的量。它定义为目标偏振分量的功率占入射总功率的比例。远场投影给的是球坐标基矢下的复电场,所以要先明确:你要的是 E_theta 还是 E_phi 分量,交叉偏振还是共线偏振。定义没定清楚,后面所有数字都没有意义。
计算里有三个容易翻车的点。第一,球坐标积分必须带 sin(theta) 权因子,这是球面面积元的一部分,漏掉它效率会偏大,严重时超过 1;第二,归一化的入射功率要和无结构参考仿真对齐,否则效率的绝对值没有意义;第三,远场数据里往往还带着入射参考场的共线分量,超表面场景下要先扣除参考场,再算交叉偏振功率。
import numpy as np # 假设 farfield3d 返回矩阵按 theta 分量、phi 分量、总场排布 E_theta = ... # 远场 theta 方向复电场(需按实际行序提取) E_phi = ... # 远场 phi 方向复电场 theta = ... # 对应仰角网格,弧度 phi = ... # 对应方位角网格,弧度 # 球坐标面积元权因子:这一行决定效率是否虚高 w = np.sin(theta) # 交叉偏振功率与透射总功率 P_cross = np.sum(np.abs(E_phi)**2 * w) P_total = np.sum((np.abs(E_theta)**2 + np.abs(E_phi)**2) * w) # 交叉偏振转化效率,绝对效率需把 P_total 换成入射功率基准 eff = P_cross / P_total print(f"PCE = {eff:.3f}")这段代码里的E_theta、E_phi是从投影矩阵里按行拆出来的复数组,theta、phi要和你调用 farfield3d 时的角度网格一致,并且转成弧度再进sin。w = np.sin(theta)这一行是整段代码里最容易被删但最不该删的一行——把它删掉,效率立刻虚高,我在项目里亲眼见过 1.05 这种“超效率”,查了一晚上才意识到是权因子丢了。
如果你的效率数值反复大于 1,别怀疑能量守恒,先检查两件事:面积元权因子有没有归位,入射功率基准是不是和无结构仿真一致。这两处修完,绝大多数超效率都会被打回原形。另外,想看某个具体角度的偏振状态,就不要积分,直接取那个角度格点的两个偏振分量做比值,这样最直观。
5. 近远场变换的 5 个避坑记录:现象、原因与解决
5.1 记录面没包住散射体,方向图缺瓣
现象:主瓣形状大致对,但后向或侧向出现意料之外的低谷,和文献对不上,第一反应以为是材料参数填错。
原因:远场监视器的记录面是一个开放平面,只包住了结构朝前的一半,反射和侧向散射场从记录面边界漏出去,等效源不再闭合。这是近远场前提里最常见的破坏方式,尤其是用平面监视器代替箱体监视器的时候。
解决:换成闭合箱体监视器,或者把平面监视器的接受角加大,确认记录面在水平方向至少超出结构一到两个波长。改完再跑一次方向图,后向瓣回来了,说明等效源闭合了。以后每次建模型,先在 GUI 里从侧面看一眼监视器边界有没有完全盖住结构,这一步二十秒,能省一次两小时的仿真。
5.2 2D 仿真里混用 farfield3d,边缘出现衰减尾巴
现象:角度分布在大角度区域缓慢衰减,怎么看都不像方向图,倒像一条拖尾,mesh 加密也没用。
原因:二维模型用了三维投影函数,几何因子从 1/r 变成 1/sqrt(r) 的柱面波量级,外推公式对不上。二维模型里的 z 方向没有网格,三维投影在缺失维度上只有一个网格的场,外推自然带夸张的衰减。
解决:确认仿真维度,二维工程一律用 farfield2d。如果确实要三维角度分布,就把模型改成真正的三维结构再跑,别指望二维模型给你一份严谨的三维方向图。这个坑在超表面偏振效率计算里尤其阴险,因为二维模型算出来的趋势可能对,但绝对值完全不可信。
5.3 记录面离结构太近,倏逝波漏进远场
现象:结果对记录面位置异常敏感,移动 10 到 20 纳米,主瓣宽度和旁瓣水平都跟着变,方向图“漂移”。
原因:记录面落在倏逝场区,近场记录里混入未衰减的高阶模式分量。这些分量原则上不属于远场,但积分外推时被一起传播出去了,等效源假设塌了一半。
解决:把记录面往外移到距离结构至少半个到一个波长的均匀介质区,然后做一次位置扫描:从 0.5 波长移到 1 波长,方向图变化小于几个百分点,才说明记录面位置安全。这条是我做纳米天线时的血泪经验,那时为了省内存把记录面贴着结构放,结果每挪一次网格,远场主瓣就换一副面孔。
5.4 GPU 显存不足静默回退,仿真时间翻倍
现象:同一个脚本,开 GPU 前跑两小时,开 GPU 后变成四小时,而且 GPU 利用率几乎为零,任务管理器里一片安静。
原因:显存不够时求解器回退到 CPU 重新规划任务,但监视器配置和网格没变,相当于 CPU 版又跑一遍,还多花了判断的时间。近远场监视器记录的切向场数据量远大于普通监视器,显存压力很容易被低估。
解决:开 GPU 前先清掉不用的监视器,把远场记录面采样降到够用的密度,比如从 361 个角度点降到 181 个,方向图一般看不出差别。如果显存还是不够,开单精度或低内存模式。跑起来后盯一眼 GPU 利用率,确认它在迭代阶段有波动,再离开工位。这个操作我吃过两次亏之后,已经写进了自己的脚本模板开头。
5.5 偏振转化效率算出来大于 1
现象:效率数值超过 100%,第一反应是能量守恒出问题,第二反应是软件有 bug,其实多半是自己算错。
原因:三类高频错误——球坐标面积元漏掉 sin(theta);归一化基准用了入射总功率的一半,比如只取了单偏振分量;透射远场里没扣除共线参考场,交叉偏振功率里混入背景场。
解决:先给积分加上 sin(theta) 权因子;再跑一个无结构参考仿真,用同一监视器、同一投影配置拿到参考场,透射远场减去参考场后再算交叉偏振功率;最后把效率定义写进注释,避免两天后的自己看不懂。这三步做完,效率基本回到 0.8 到 1.0 的合理区间。如果还是大于 1,把投影角度范围缩到透射半球内,排除数值积分散溢的影响。
6. 远场结果怎么验证与进阶:先移动记录面,再谈优化
6.1 三个不花哨但必须做的验证
近远场结果不像近场电场那样能直接“看见”,它是个黑匣子输出,所以我的习惯是每换一个结构,先做三件事再谈优化。第一,移动记录面:把远场监视器往外挪半个波长,方向图主瓣和旁瓣变化不超过几个百分点,才说明等效源前提成立。第二,网格收敛:把网格精度从默认加密一档,远场角度分布变化小于 5%,说明时域求解的场本身够了。第三,和已知解对表:偶极子天线、球散射这类有解析解或公开结果的对象,先跑一遍确认自己的脚本链路,再上真实结构。
这三步不会让结果变漂亮,但能让你在讨论“效率怎么又低了 0.2”的时候,确定问题在结构参数而不是投影配置。把验证写进脚本模板,能省掉大量重复排错时间。至少对我而言,近远场这一环出问题,九成都不是出在最后的投影调用,而是出在记录面位置、网格密度这些“前级”设置上。
6.2 进阶:把远场数据反推回近场
近远场变换是单向的,但掌握了远场的复振幅和相位之后,可以用傅里叶光学把角谱反演回某个焦平面上的场。超表面聚焦器、模式转换器这类任务,我一般先做正向 near to far,再对远场数据做逆传播,得到焦平面光斑,直接看聚焦效率和旁瓣。这样避免了在每一个焦平面位置重新跑一次 FDTD,参数扫描快一个量级。
这个流程要注意角谱传播的空间采样:远场角度网格的采样密度决定了反演焦面的视场范围,theta 采样越密,可恢复的横向波矢范围越大。想分辨小焦斑,就得把角度采样点数适当加高,这是和效率积分相冲突的地方——效率积分不需要太密,反演需要,按目标分开设置即可。
我做过一个超表面偏振转换项目,效率算出来 108%,查了一晚上,最后发现就是球坐标积分里少乘了一个 sin(theta)。那之后我把记录面扫描和网格收敛写成了脚本模板,任何近远场结果先过这两关再谈下一步。近远场这一环,慢一点,先验证再优化,后面的事都好办。希望帮到你。
本文还有配套的精品资源,点击获取