TF/SF源这套东西,我在前两篇里已经分别把边界注入和吸收层单独验证过。到了这一篇,终于要把它们放进同一个二维网格里,目标变成一件很常见但也很容易翻车的事:用2D-FDTD算TM波照射无限长圆柱时的散射场,然后把RCS提取出来,和解析解做对比。一开始我以为这只是把两段调好的代码拼起来,等真正跑起来才发现,问题全藏在“组合状态”里——单独测没问题,一叠加就冒出一堆说不清来路的反射和漏能。
这篇就把我第三次实现时总结的场区布局、迭代顺序、TF/SF边界修正、UPML分裂场离散、圆柱建模和RCS外推完整写出来。所有内容都按TM波极化(电场只有z分量,磁场有x和y分量)展开,TE波的分量切分不一样,不能直接套。适合正在啃FDTD散射验证、手头有前两篇基础但还没把整套流程跑通的读者。
1. 组合前先理清场区布局:TF/SF和UPML到底谁在外面
1.1 计算域从内到外可以切成四块
当TF/SF源和UPML同时存在时,一个矩形计算域内部的信息流是这样的:最中心是总场区,圆柱目标放在这个区域里,这里的场数值是入射场加散射场;总场区外沿是一个闭合的TF/SF边界,入射平面波从这条边界注入;边界外面是散射场区,理论上只存在散射场;最外层是UPML吸收区,把散射场区漏出来的波都吃干净。
这个四层布局里有一个我很早就踩过的坑:TF/SF边界离UPML内边界至少要留10个网格以上。如果贴得太近,TF/SF连接条件的修正和UPML的衰减在空间上重叠,角上会出现一种很隐蔽的伪反射——远看是均匀的,放大看是一圈亮暗纹。另外圆柱也不要贴着TF/SF边界放,圆柱近场的强分布会反过来污染边界上的入射场修正,通常留5个网格以上的距离。
1.2 一次完整迭代里的更新顺序
FDTD本质是电场和磁场交替更新。加入TF/SF和UPML之后,我把一次时间步的循环固定成这个节奏:
- 更新UPML区内所有磁场分量(H_x和H_y,用分裂场形式,后面细说);
- 更新内部区域(总场区加散射场区)的H_x和H_y;
- 对TF/SF边界上的H_x、H_y做入射场修正;
- 更新UPML区内电场E_z的分裂分量;
- 更新内部区域的E_z;
- 对TF/SF边界上的E_z做入射场修正;
- 如果圆柱是PEC,把圆柱内部所有E_z强制置零。
这里最核心的一条经验:H场和E场各自先全部更新完,再做TF/SF修正。千万不要在每更新一个点时顺手修正,那样会漏掉相邻节点刚被更新后的依赖关系,后期出现随机的直流偏移。更不要先用完旧场值再修一半,否则每条边上的等效磁流都不连续。
1.3 分区更新和全空间更新的取舍
我第一次实现时把内部区和UPML区写成两套更新公式。调试时发现PML内侧出现一条平行于边界的等效反射面,无论怎么调σ参数都消不掉。后来排查到真正原因:内部区和UPML区在边界上使用了不同的磁场差分模板,虽然理论上在σ=0时等价,但实际编程里一个用旧值、一个用新值,边界处的场就不连续了。
所以我后来改成全空间统一用分裂场形式更新,内部区域σ_x=σ_y=0,自然退化成标准FDTD公式,这样整片网格只有一套代码,没有接口差异。代价是多存两个电场数组,而对200×200量级的网格来说,这点内存完全不用纠结。
2. TF/SF连接条件的实际实现:四条边加四个角,一个都不能漏
2.1 边界修正的本质就一句话
标准2D-TM更新公式我前两篇已经写过,这里不重复推。实际写TF/SF修正时,很多人容易被四边四角的具体公式绕晕,我的办法是只记一句判断逻辑:
“如果待更新节点在散射场区,而它的差分模板里另一个节点在总场区,为了让差分值保持纯散射场,就在更新式中加入对应的入射场分量;反过来,待更新节点在总场区而模板另一边在散射场区,就减去入射场分量。”
这句话能覆盖所有边和角的修正。实际代码里只需要判断“模板的两个节点分别落在哪个区”,再根据入射场的x方向和y方向分量做加减。比背四条边的公式更稳,因为你自己的网格编号规则可能和别人不一样。
2.2 左、右、上、下边各自修的磁场分量
正入射(沿+x方向传播)时,入射平面波的磁场可以写成:
H_x^inc = -(k_y / (ω μ0)) * E_z^inc
H_y^inc = (k_x / (ω μ0)) * E_z^inc
其中k_x = k0 cosθ,k_y = k0 sinθ,θ是入射方向角。正入射θ=0时,k_y=0,H_x^inc=0,全程只需要关心H_y,调试量一下子少了一半。
在左边界和右边界的直边段,跨边界的主要是H_y的差分模板,这两条边上要修正H_y;上边界和下边界跨边界的则是H_x,修正H_x。E_z在直边段也有修正,因为它的差分模板会跨到散射场区的H节点,但直边段只涉及一个方向,比角点简单。
2.3 角点漏能是最隐蔽的问题
角点是重灾区。角上的E_z节点同时连着两条边的修正路径,H_x和H_y两个方向的入射场分量都要补进去。我见过的大多数“散射场区莫名出现驻波纹路”案例,最后都能定位到四个角点少补了一个方向。
这种问题的检测方式很有意思:直接看总场图往往发现不了,因为漏能幅度只有入射场的几个百分点。但如果把散射场区单独显示,你会看到从角上发出两条很淡的斜向波前,和入射方向不一致。用RCS验证时,它通常会让你在某些特定角度上偏差1~2 dB。
2.4 入射场统一接口
为了减少符号错误,我把入射场写成一个统一函数传入三个量:E_z^inc(x, y, t)、H_x^inc(x, y, t)、H_y^inc(x, y, t),内部统一用θ控制方向。TF/SF修正和后面的远场外推都调用同一个函数,避免两处各写一份相位定义。我早期在外推代码里自己重写了一遍入射场表达式,方向一改成斜入射就对不上,这个亏吃过一次。
3. UPML在2D-TM里的分裂场离散:为什么只有E_z要拆成两个
3.1 坐标伸缩下的三个场方程
UPML的核心是复坐标伸缩。对2D-TM,频域Maxwell方程变成这样:
jωε E_z = (1/s_x) ∂H_y/∂x - (1/s_y) ∂H_x/∂y
jωμ H_x = -(1/s_y) ∂E_z/∂y
jωμ H_y = (1/s_x) ∂E_z/∂x
其中s_x = κ_x + σ_x/(jωε0),s_y写法相同。注意H_x只被s_y拉伸,H_y只被s_x拉伸,而E_z同时被两个方向的s影响。所以H_x、H_y不需要分裂,E_z必须分裂成两个子分量:
E_z = E_zx + E_zy
满足:
jωε s_x E_zx = ∂H_y/∂x
jωε s_y E_zy = -∂H_x/∂y
转换到时域,并做离散化,就得到我实际用的更新式:
∂E_zx/∂t = (1/(ε κ_x)) ∂H_y/∂x - (σ_x/(ε0 κ_x)) E_zx
∂E_zy/∂t = -(1/(ε κ_y)) ∂H_x/∂y - (σ_y/(ε0 κ_y)) E_zy
磁场分量这边的系数同样分方向:
∂H_x/∂t = -(1/(μ κ_y)) ∂E_z/∂y - (σ_y/(ε0 κ_y)) H_x
∂H_y/∂t = (1/(μ κ_x)) ∂E_z/∂x - (σ_x/(ε0 κ_x)) H_y
离散时衰减项我习惯做隐式处理。比如E_zx的衰减项,不要在右端直接减去σ*E,而是算完差分后除以一个系数。写成代码就是:
E_zx_new = (E_zx_old + dt / (eps * kappa_x) * D_Hy) / (1 + dt * sigma_x / (eps0 * kappa_x))
这样在PML最外层σ值很大的时候,时间步长稍微取大一点也不会发散。如果全用显式,第一版代码很可能在几十步之后就看到NaN。
3.2 σ_x、σ_y和κ_x、κ_y的方向含义
σ_x只在左右两侧的PML层非零,σ_y只在上下两侧的PML层非零,四个角区两者都非零。实现时我用四个二维数组分别存sigma_x、kappa_x、sigma_y、kappa_y,初始化时根据网格到内边界的距离填充渐变值。
之所以建议用二维数组而不是一维数组,是因为角区的渐变方向有两个,一维数组容易写成“距离内边界的最近距离”,这在角区会出错。二维数组最直观,调试时可以逐格打印某个切片看渐变曲线是否符合预期。
3.3 厚度、幂次和最大电导率的选择
UPML厚度d通常取10到15个网格。渐变多项式阶数n取2到4,我个人习惯用n=3。最大电导率用经验公式:
σ_max = -(n+1) ε0 c ln(R0) / (2d)
R0是目标反射系数,我一般设1e-4,除非对吸收指标有特别要求才设到1e-6。每个网格的σ按距离内边界的比例取σ(ρ) = σ_max (ρ/d)^n。
有一点必须强调:σ_max不是越大越好。你实际跑出来会发现,σ_max超过最优值后,离散误差导致的反射反而增大。边界层和内部交界处存在一个折中,经验公式给出来的量级基本就是可用范围。如果还要兼顾低频和掠射角吸收,再加κ渐变:
κ(ρ) = 1 + (κ_max - 1) * (ρ/d)^n
κ_max我一般取5到10。加κ之后,更新式里多出κ的除法项,别漏了。
3.4 内存储存建议
内存允许时,全空间按分裂场格式更新是最省事的方案:每个网格保存E_z总场、E_zx、E_zy三份电场,外加sigma_x、kappa_x、sigma_y、kappa_y四个数组。这样可以彻底消灭“PML内部接口”问题。等你确认结果正确、需要撑大网格时,再把内部区域改回普通FDTD更新,省掉E_zx和E_zy两个数组。
4. 圆柱建模与入射波设置:PEC掩膜、CW和宽带脉冲怎么选
4.1 PEC圆柱和介质圆柱的差异
PEC圆柱最简单。给定圆心和半径R,做一张二维布尔掩膜,凡是到圆心的距离小于等于R的网格点,在每轮迭代最后把E_z强制置0。TM波的电场只有z分量,PEC表面切向电场为零,正好就是E_z=0,所以置零操作在物理上是对位的。
介质圆柱则不同,需要给ε_r(i,j)和σ(i,j)两个参数矩阵。FDTD在介电常数突变面附近,直接用硬赋值会有误差,习惯做法是对界面网格取体积平均。不过如果半径足够大、网格足够细,这个误差会很小。介质圆柱的E_z在边界是连续的,绝对不能像PEC那样置零。
4.2 连续正弦波(CW)是最稳的验证路径
我强烈建议第一次跑通用CW,不要直接上宽带脉冲。CW源就是持续注入单频正弦平面波,目标明确:跑若干周期后,观察记录环上某个探针点的散射场幅度是否进入稳态。如果连续10步的幅度变化小于0.1%,就可以保存数据做RCS了。
需要的总时间步数估算很粗:至少让波穿过计算域对角线对应的传播时间,然后留出3到5倍的余量,同时至少要覆盖5到10个入射波周期。多跑几千步没什么成本,但少跑几百步导致稳态没建立,外推出来的RCS就会带上未收敛的振荡误差。
4.3 宽带高斯脉冲的注意点
如果确认CW已经调通,再考虑换高斯脉冲做宽带计算。脉冲时间波形选高斯或带载波的带通脉冲,频谱宽度大致是Δf约等于1/τ。你需要保证关注频段的最高频率满足波长分辨率要求,也就是λ/Δ至少20。另一边,脉冲必然包含低频成分,而低频对应的波长很长,PML厚度相对波长就变薄了,吸收效果会明显下降。
所以宽带方案对UPML的考验比CW大得多。如果宽带算出的低频段RCS异常,不要先怀疑外推算法,先检查UPML低频反射。我在实际调试时,先CW调好单频,再换脉冲。直接上宽带,遇到问题很难定位到底出在源、边界还是外推。
4.4 网格密度对解析对比的影响
网格密度直接影响RCS验证的误差。λ/Δ=20时,主瓣和大部分旁瓣能对上,但深零点可能偏差1到2 dB;λ/Δ=40时,零点位置明显更准。定量对比至少取λ/Δ=30以上。圆柱半径也不要太小,半径至少覆盖20个网格,否则阶梯近似带来的形状误差会超过你要验证的数值误差。
5. 近远场外推与2D RCS提取:记录环放在基场区之外
5.1 记录环必须在散射场区
远场外推要用的等效原理,前提是记录环上的场是纯散射场。所以记录环一定要放在TF/SF边界和UPML之间,绝不能放进总场区。如果环放进总场区,里面混着入射波,外推出来的“散射场”会被入射场主导,RCS结果会大得离谱。这是很多RCS偏大问题的直接原因。
5.2 记录环上的数据和相位累加
取一个闭合方形环,逆时针记录每个采样点的E_z^s和切向磁场。等效电流源用J_s = n×H_s,等效磁流源用M_s = -n×E_s,n是记录环外法向。远场某个方向(ρ, φ)上的散射电场,近似为所有采样点贡献的相干叠加,每项带一个相位因子exp(jk(x' cosφ + y' sinφ)),外面乘上二维柱面波因子exp(-jkρ)/√ρ。
外推公式里的ωμ权重和常数因子要照标准形式写,不要自己凭感觉组合。如果相位和幅度系数错了,整体方向图形状可能还像,但绝对值会偏一个常数,和解析解对照时一眼就能看出来。
5.3 2D RCS的定义和单位
二维散射宽度定义为:
σ_2D(φ) = lim_{ρ→∞} 2πρ |E_z^s(ρ,φ)|^2 / |E_z^i|^2
注意这里是2πρ而不是3D的4πr²,因为二维柱面波的幅度按1/√ρ衰减,不是按1/r。单位是米,对比时通常画成10log10(σ_2D/λ)的dB值。如果入射场幅度归一化成1,分母可以简化成1,但代码里最好保留入射幅度变量,方便以后换源强度。
5.4 和Mie级数的对照方法
PEC圆柱TM_z散射有严格的级数解,用Bessel函数展开就能算。对比时不要只看前向,前向散射由衍射主瓣主导,对网格细节不敏感,对上了也不能证明整套流程没问题。我的习惯是至少对比0度、90度、180度三个方向,三个都落在可接受误差内,才认为外推和源注入都对了。
6. 我反复翻车的几个细节,以及最终的调试顺序
6.1 永远先做自由空间PML测试
不要一上来就放圆柱。第一步,关掉TF/SF,或者把TF/SF边界放到远离PML的位置,在自由空间里注入一个脉冲,看计算域内总场能量能不能在几十个时间步后衰减到可以忽略。如果PML反射明显,问题大概率在σ_max或厚度上,和后续的TF/SF、圆柱都无关。
第二步,单独开TF/SF,不加UPML或UPML推很远,验证总场区内能看到干净的平面波,散射场区基本是零。第三步,加上圆柱前先跑一次“空圆柱”——也就是纯自由空间,看看散射场区残留幅度是否接近数值噪声。如果这一步还有明显残留,问题一定出在TF/SF角点或PML接口,而不是圆柱建模。
6.2 角点漏能的确认方式
我遇到过一次典型情况:散射场区四个角附近出现持续的、与入射波同频率的驻波纹路。排查到最后,发现是角上E_z修正时两个方向的入射磁场只补了一个方向。补全之后,纹路当场消失。这种问题在整体场图里很难发现,因为幅度只有入射场的百分之几,但外推RCS后会造成某些角度的系统性偏差。
6.3 入射场函数统一入口
把E_z^inc、H_x^inc、H_y^inc统一写进一个函数,TF/SF修正和远场外推都引用同一个相位定义。我早期在TF/SF边界用θ定义入射方向,在外推代码里又用k_x、k_y单独写了一份,方向一改就符号对不上。统一之后,调斜入射几乎没有再为相位问题折腾过。
6.4 时间步长和内部参数的保守取值
Courant数在二维条件下取上限的0.6到0.9是稳的,我习惯取0.5这样的保守值。UPML分裂场不改变标准Courant稳定性条件,但衰减项的隐式处理很重要。参数都调好之后,我建议把sigma_x、kappa_x这一组值的分布打印出来看一眼,确认渐变曲线是对的,这种调试成本比反复跑整场低很多。
做到这一步,整套2D-FDTD圆柱散射流程基本就通了。这个系列到这里,TF/SF源、TM波、UPML和近远场外推已经串成一条完整可复现的链路。后面如果再往下走,我会去试介质圆柱的多频点验证和斜入射宽带脉冲,但不管怎么扩展,先用CW加PEC加Mie级数这套工作流跑通,始终是最快的验证路径。