简介:这套MATLAB源码包面向天线阵列设计、雷达与5G通信方向的工程师与研究者,聚焦利用遗传算法优化10元稀疏微带天线阵列的单元间距,从而降低副瓣电平、改善方向性,解决传统密集阵列成本高、体积大的问题。资源将稀疏阵列建模、智能优化算法与方向图可视化整合在同一工作流中,注释清晰,适合具备一定MATLAB基础、希望掌握阵列优化方法或开展相关课题设计的读者。包内共6个文件,核心为4个m脚本,分别承担稀疏阵列建模、遗传算法迭代寻优、方向图绘制与优化前后对比,另有1个fig阵列布局图和1个mat最优单元位置数据,整体仅85KB,轻量易用,便于快速运行与二次开发。已有543人学习下载,在同类型稀疏阵列优化资料中具备参考热度。使用者可先通过fig文件直观查看10元稀疏布局,再运行m脚本完整复现“初始化—迭代优化—方向图对比”流程,也能读取mat文件获得最优间距;若希望迁移到其他设计场景,只需替换约束条件与适应度函数,即可针对不同频段或更大规模阵列开展新一轮低副瓣优化,整体示例对算法工程实现有较强参考价值。
1. 稀疏阵列天线与 GA:为什么要做“减单元”的优化
稀疏阵列天线的核心工程价值,是在口径尺寸不变的前提下把阵元数量压下来:单元少一半,功分网络、T/R 组件、天线罩和整机重量都能跟着瘦身。这个资源做的事,就是用遗传算法去搜一组“开/关”决策,把阵列里真正需要的单元留下,让副瓣电平不至于被牺牲得太难看。它适合两类人:一类是做相控阵、稀疏阵列天线雷达的工程师,需要快速评估某个口径下最少能用到多少个单元;另一类是电磁场与天线方向的研究生,课程设计或论文里的“GA 稀疏阵”章节,几乎都能从这套实现里直接改参数起步。我拆过这份 GA_sparsearray、sparsegenetic 相关的代码,先说结论:GA 本身不是难点,难的是目标函数怎么写、约束怎么加、最后怎么用全波仿真验收。
2. 稀疏阵列综合的数学模型:阵列因子、半个波长网格和编码方式
天线阵列的稀疏优化,说到底是一个离散的 0/1 组合优化问题,但很多人第一次拿到代码就急着跑仿真,结果做出一个单元数特别少、旁瓣却高到离谱的阵列,问题就出在没把“口径、阵元数、副瓣”这三个量之间的关系先理顺。
2.1 为什么要用“口径”而不是“单元数”来衡量
假设你手头的物理口径是 D,用半波长等间距排满,单元数大约是 N = 2D/λ + 1。如果直接减少单元数量,有两种常见做法都会翻车:第一种是把单元间距拉大到 0.8λ 甚至 1λ,阵列口径不变,但高角度会出现栅瓣;第二种是只保留靠中间的几个单元,阵列口径缩小,波束宽度变宽,分辨率直接下降。
稀疏阵列的正确做法是保持候选位置网格不动,比如网格间距仍然取半波长,然后在这个网格上关闭一部分单元,让剩余单元的平均间距大于半波长,但整体口径还撑在那里。GA 要优化的就是“哪些位置保留、哪些位置关闭”。所以在动手写代码之前,先要把口径 D、网格间距 Δ、候选位置数这三个参数写在脚本最前面,后面所有操作都围着它们转。
2.2 阵列因子公式:代码里最先要核对的东西
阵列因子的计算是整个优化的地基。不管包里的脚本叫什么名字,你第一个要检查的函数一定是 AF 计算这部分。对于沿 x 轴排列的线阵,单元位置为 xn,波束扫描角为 θ0,阵列因子可以写为:
AF(θ) = Σ wn · exp(j2πxn(sinθ - sinθ0))
其中 wn 是激励幅度,稀疏优化里通常取 1(等幅激励)或由口径锥削决定。下面这个函数是我在检查和改动这类资源时常用的写法:
function AF = calc_linear_AF(x_lam, theta, scan_deg) % 计算稀疏线阵的阵列因子 % x_lam : 存活单元的位置,单位用波长表示,例如 [-4.5 -3.5 0 2.5 ...] % theta : 观察角,弧度,范围 [-pi/2, pi/2] % scan_deg: 波束扫描角,单位度,0 表示法向 scan = scan_deg * pi / 180; AF = zeros(size(theta)); for n = 1:numel(x_lam) AF = AF + exp(1j * 2 * pi * x_lam(n) * (sin(theta) - sin(scan))); end AF = AF / max(abs(AF)); % 归一化,方便后面转 dB end这段代码的逻辑很直白:对每个存活单元,按它的位置 x_lam 计算相对相位,然后把所有单元矢量叠加;最后除以最大值是为了让后面算副瓣电平时直接以峰值做参考。要注意的是这里假设了等幅激励,如果你要做泰勒锥削或切比雪夫加权,需要在循环体里乘上对应的幅度系数 wn。
这个函数里最容易出错的地方是 theta 的采样密度。如果只取 181 个点,角度步长 1°,峰值副瓣会被低估或漏掉;我一般取 3601 个点,步长约 0.05°,代价只是计算时间多一点,但副瓣峰值的评估可靠很多。另一个容易忽略的点是加 eps 防止取对数时出现 -Inf,算 dB 的时候用 20*log10(abs(AF)+eps) 是最稳妥的。
2.3 遗传算法的编码方式:二进制掩码还是坐标向量
这套资源的名称里带有 sparsegenetic,典型做法是用二进制掩码表达候选位置:把阵列口径按半波长网格切分成 N 个候选位,染色体的每一位是 0 或 1,1 表示该位置放置贴片单元。
二进制掩码的优点是实现简单、搜索空间固定,适合常规的 20~200 个候选位的稀疏阵。它的缺点是位置被网格卡死,表达不了“任意间距”的稀疏布局。连续坐标向量编码则让 GA 直接优化存活单元的位置坐标,理论上能找到更优的解,但搜索空间大得多,交叉变异后容易产生两个单元位置几乎重合的情况,必须加最小间距修复。
| 编码方式 | 实现难度 | 搜索空间 | 最小间距约束 | 工程推荐度 |
|---|---|---|---|---|
| 二进制掩码 | 低 | 离散,2^N | 容易通过修复函数保证 | 入门首选,资源默认 |
| 连续坐标向量 | 高 | 连续,体积大 | 很难在交叉后保证 | 有经验后再尝试 |
如果你拿到的资源以二进制掩码为主,不要急着改成连续坐标。先跑通默认例子,再考虑扩展。很多工程场景里,半个波长网格本身已经足够表达实际布局,因为微带贴片单元本来就要占掉约半个波长的物理面积。
2.4 适应度函数:把副瓣、单元数和最小间距凑成一个数
GA 每一步迭代都在评估“这个个体好不好”,而“好”的标准完全由适应度函数决定。稀疏阵列里我一般把适应度设计成三项的和:峰值副瓣电平、单元数偏差、最小间距惩罚。单纯只优化副瓣,GA 会把单元数一路削减到个位数,因为单元越少、阵列因子越接近全向辐射,副瓣反而好看——这显然不是我们要的结果。
function [score, psll_db, n_active] = fitness_sparse(bitmap, d_min, N_target, aperture_lam) % 输入: % bitmap : 1xN 的 0/1 向量,1 表示保留单元 % d_min : 最小单元间距,单位波长,通常取 0.5 % N_target : 期望的单元数,稀疏优化要逼近这个目标 % aperture_lam : 口径长度,单位波长 % 输出: % score : 适应度值,越小越好 % psll_db : 当前阵列的峰值副瓣电平(dB) % n_active : 实际存活单元数 N = numel(bitmap); pos_lam = linspace(-aperture_lam/2, aperture_lam/2, N); x_lam = pos_lam(bitmap == 1); n_active = numel(x_lam); theta = linspace(-pi/2, pi/2, 3601); AF = calc_linear_AF(x_lam, theta, 0); AF_db = 20 * log10(abs(AF) + eps); % 按口径估计主瓣半宽: 约 lambda/D,再留一点余量 half_width = 1.2 / aperture_lam; main_idx = abs(theta) < half_width; main_idx = main_idx & (AF_db > -40); % 只把最靠近峰值的主瓣区域挖掉 sll_db = AF_db(~main_idx); psll_db = max(sll_db); % 单元数偏差惩罚 n_penalty = abs(n_active - N_target) / N_target * 10; % 最小间距惩罚 d_penalty = 0; x_sort = sort(x_lam); for k = 2:numel(x_sort) if x_sort(k) - x_sort(k-1) < d_min d_penalty = d_penalty + 5; % 每出现一次紧密单元就加 5 dB 风险 end end score = psll_db + n_penalty + d_penalty; end参数设置上,n_penalty 的权重取 10 意味着单元数偏差 10% 会带来约 1 dB 的适应度代价,比较温和;d_penalty 每对紧邻单元加 5 会让 GA 非常抗拒出现间距过近的个体。这部分权重没有绝对标准,我习惯的做法是先跑 10 代看种群变化,如果单元数迅速坍缩到 N_target 以下,就调大 n_penalty;如果副瓣降不下去,就把副瓣项的权重加大,或者把主瓣挖掉的区域再调窄一点。
3. 遗传算法主循环实现:三种算子、一个参数表和一个收敛判定习惯
适应度函数只是评分标准,真正的搜索过程靠主循环驱动。这套资源里主循环脚本承担的就是标准 GA 工作:初始化种群、计算适应度、精英保留、锦标赛选择、单点交叉、位翻转变异,最后再对个体做间距修复。下面把每个环节拆开看。
3.1 解压后先找哪几个关键脚本
压缩包里脚本的命名不一定统一,但按功能划分,你大概率会看到这几类文件:入口主脚本、适应度函数、阵列因子计算函数、图形绘制函数。拿到资源第一步不是直接 F5 运行,而是先把主脚本里的参数区找出来,确认口径、网格数、目标单元数、目标副瓣这几个关键量是不是写在同一个地方。
我一般会按这样的顺序梳理代码:先用 MATLAB 打开入口脚本,在编辑器的“转到文件”窗口里看函数列表;找到入口脚本后,把所有可调参数用 %% 分节标记出来;最后把适应度函数单独打开,对着 2.4 节的公式核对一遍。如果发现适应度里完全没有单元数惩罚,说明这个脚本只适合做纯副瓣优化,你需要自己补上约束。
3.2 主循环代码骨架:从选择到修复
下面这段主循环是这类资源最常见的结构,你拿到的源码可能封装得更复杂,但核心逻辑不会超出这个框架:
%% 遗传算法主循环 rng(20250317); % 固定随机种子,结果可复现 pop_size = 100; % 种群规模 max_gen = 200; % 最大迭代代数 pc = 0.85; % 交叉概率 pm = 0.10; % 变异概率,按位翻转 elite_num = 2; % 精英保留个体数 N = 29; % 候选位置个数,由口径和网格决定 aperture_lam = 7; % 口径 7 个波长 d_min = 0.5; % 最小单元间距,半波长 N_target = 12; % 目标单元数 pop = randi([0 1], pop_size, N); % 随机初始化种群 best_history = zeros(max_gen, 1); for gen = 1:max_gen fits = zeros(pop_size, 1); for k = 1:pop_size fits(k) = fitness_sparse(pop(k,:), d_min, N_target, aperture_lam); end % 精英保留 [~, idx] = sort(fits); elite = pop(idx(1:elite_num), :); % 锦标赛选择 sel = zeros(pop_size - elite_num, N); for k = 1:size(sel, 1) i1 = randi(pop_size); i2 = randi(pop_size); if fits(i2) < fits(i1) i1 = i2; end sel(k, :) = pop(i1, :); end % 单点交叉 for k = 1:2:size(sel, 1)-1 if rand < pc cut = randi(N-1); tmp = sel(k, cut+1:end); sel(k, cut+1:end) = sel(k+1, cut+1:end); sel(k+1, cut+1:end) = tmp; end end % 位翻转变异 mask = rand(size(sel)) < pm; sel(mask) = 1 - sel(mask); % 间距修复,见第 5 章 sel = repair_spacing(sel, d_min, N_target); % 合并精英和子代 pop = [elite; sel]; best_history(gen) = min(fits); fprintf('gen %3d, best = %.2f dB\n', gen, best_history(gen)); end逻辑说明:每一代先算适应度,用排序找出最好的 2 个个体直接复制到下一代,保证最优解不会丢失;然后通过锦标赛选择从当前种群中挑出父代,锦标赛的做法是随机抽两个个体、取适应度较好的那个,这一点代码里就是简单的 if 比较。单点交叉在随机位置切割染色体并交换尾部,变异则按 pm 概率把 0 变 1 或把 1 变 0。
需要特别注意的是变异算子的写法。很多新手写成sel(rand(size(sel)) < pm) = 1 - sel(rand(size(sel)) < pm);,但前后两次 rand 调用产生的掩码不一致,实际翻转的位置和判定位置不是同一批。建议先生成 mask,再统一赋值,就像上面代码那样。
3.3 参数表:种群规模、交叉概率、变异概率怎么调
这类资源里默认参数通常能跑通,但不代表适合你的口径和频段,参数需要按问题规模重新设置。我给出一组参考值,实际使用时先跑默认算例,再按调试方向微调。
| 参数 | 参考默认值 | 适用场景与调整建议 |
|---|---|---|
| 种群规模 | 100~200 | 候选位 N 越大种群越大,N=60 以上建议取 200 |
| 最大代数 | 200~300 | 300 代后无明显下降就固定下来 |
| 交叉概率 pc | 0.8~0.9 | 太小搜索慢,太大破坏优秀染色体 |
| 变异概率 pm | 0.05~0.15 | 二进制位图用 0.1 附近,太大退化成随机搜索 |
| 精英数 | 2~4 | 一般 2 个够用,多了容易早熟 |
| 目标 SLL | -25 dB | 微带阵全波仿真的副瓣余量至少留 2 dB |
对微带天线阵列,我给目标副瓣的建议是别定到 -30 dB 以下。微带基板、馈电网络和互耦都会让实测副瓣比阵列因子算出来的高 2~3 dB,GA 优化阶段定 -25 dB,全波仿真验证时落在 -22 dB 左右,已经算不错的工程结果。
3.4 收敛判定:别被“历史最优”骗了
很多脚本在循环里只保留 best_history 并在最后画一条下降曲线,你看着曲线平了就觉得优化完成。我踩过的坑是:历史最优可能停留在第 30 代,但种群中位数还在慢慢下降,说明搜索还没完全收敛;也可能是精英个体早就把副瓣压住了,但单元数约束并没有真正满足,曲线平坦是因为适应度被间距惩罚项卡死。
判断收敛我一般看两个指标:一是 best_history 连续 30 代变化小于 0.2 dB;二是种群中位数适应度和最优值之间的差距缩小到 1 dB 以内。如果中位数始终比最优值高很多,说明种群多样性不足,早熟了,需要调大变异概率或增大初始种群。建议每次跑完都把 best_history 用 save 命令存下来,后面调参数才有对比依据。
4. 从稀疏布局落到微带阵列:微带贴片映射、最小间距与扫描面
GA 算出来的是一串 0/1 决策或一组坐标,这离真正的微带贴片阵列还有一段距离。这段距离往往是工程交付时最容易出问题的地方,你需要把抽象的位置坐标映射成具体的贴片布局,并检查是否满足物理可实现性和扫描性能。
4.1 为什么偏偏是微带贴片单元
稀疏阵列的阵列因子计算默认单元是全向点源。实际上微带贴片单元的方向图是有方向的,E 面方向和 H 面方向的波束宽度不同,单元因子和阵列因子相乘后,总方向图的副瓣水平会和纯阵列因子结果有偏差。尤其是大角度副瓣,贴片单元的单元因子在低角度有明显衰减,实际方向图的边射副瓣通常比阵列因子算出来的更低,而扫描到 40°~60° 时会引入新的问题。
因此在用这套资源做微带阵列时,我建议把适应度评估保留为点源阵列因子,但最后验收阶段一定要把单元方向图乘进去。具体做法是对每个存活单元,在计算 AF 之后乘上贴片单元的单元因子,比如常见的微带贴片 E 面方向图近似可以写成 cosθ 形式,H 面近似为 1。这一步不必改动 GA 主循环,只需要在验证脚本里多乘一项。
4.2 从半波长网格到几何坐标:位图怎么变成 layout
二进制掩码对应的位置坐标计算其实很简单。假设口径长度为 aperture_lam,候选位置数 N,那么各候选点的坐标就是 linspace(-aperture_lam/2, aperture_lam/2, N)。把位图里为 1 的位置挑出来,就得到存活单元的坐标序列。
%% 位图转坐标,并导出给全波仿真软件 N = 31; % 候选位置数 aperture_lam = 7.5; % 口径,单位波长 bitmap = [1 0 1 1 0 0 1 0 1 0 1 1 ...]; % 示例位图,实际来自 GA 结果 pos_lam = linspace(-aperture_lam/2, aperture_lam/2, N); x_lam = pos_lam(bitmap == 1); % 存活单元坐标,波长单位 % 换算成物理坐标,例如 f = 5 GHz, lambda = 60 mm lambda = 60; % 单位 mm x_mm = x_lam * lambda; y_mm = zeros(size(x_mm), 1); % 线阵场景,y 坐标为 0 writematrix([x_mm(:), y_mm(:)], 'sparse_layout.csv'); fprintf('存活单元数: %d\n', numel(x_mm));这段代码输出一个两列的 CSV 文件,第一列是 x 坐标,第二列是 y 坐标。导入 HFSS 或 CST 时,可以直接按这个坐标放置贴片单元,不需要手动输入。参数说明:lambda 的单位要和你的工作频率对应,5 GHz 对应 60 mm;如果做平面稀疏阵,把 y_mm 换成另一个 linspace 网格即可。
这里还要注意一个细节:贴片单元本身有物理尺寸,通常大约 0.4λ 到 0.5λ。如果 GA 结果里两个存活单元中心距只有 0.5λ,贴片边缘几乎要贴在一起,耦合会非常严重。所以我实际做工程时会保守地取 d_min = 0.55λ,给贴片边缘留一点间隙。
4.3 扫描角与栅瓣:稀疏阵列的“隐形债”
稀疏阵列把单元间距拉大之后,表面上看副瓣曲线还不错,但一旦波束扫描到一定角度,栅瓣就会冒出来。栅瓣出现的条件,粗略判断是最大单元间距 d_max 超过 λ/(1 + sinθ_max)。也就是说,如果阵列里有些位置间距达到 1λ,扫描到 30° 时栅瓣就会进入可视区域。
如果用这套资源做稀疏阵列天线雷达这类扫描相控阵,我强烈建议在适应度评估里加一个“最大扫描角检查”:把 calc_linear_AF 的 scan_deg 参数改成你的最大扫描角,比如 45°,然后取这个扫描角下的峰值副瓣作为适应度的一部分。代价是每一代的评估时间翻倍,但对雷达工程来说这个代价是必要的,否则交付之后发现 30° 扫描旁瓣飙升,返工成本远大于优化时间。
一个折中做法是先用 0° 扫描优化得到一组位置,再单独写一个脚本一次性评估 0°、15°、30°、45° 四个扫描角下的 SLL。如果某个角度下 SLL 超标,就把该角度的 SLL 加入适应度再跑一轮。两个阶段叠加,通常 2~3 轮就能收敛到一个兼顾法向和扫描性能的稀疏布局。
5. 稀疏阵列优化避坑:三个常见翻车点
GA 稀疏阵列看着简单,实际调起来翻车点不少。这里写的三个坑,每个都是我实际跑这类资源时遇到过的,按“现象 → 原因 → 解决”的方式记录下来,方便你排查时对照。
5.1 翻车现场:适应度把单元数“优化”没了
现象:跑完 200 代,最优个体的存活单元只剩下两三个,副瓣曲线很漂亮,但增益和波束宽度完全不可用。
原因:适应度函数里副瓣项占比太高,或者压根没有单元数惩罚。GA 是纯数值优化,它只认 score 数字大小,不认“工程合理性”。单元越少,阵列因子越接近各向同性,主瓣越宽,副瓣电平越低,score 自然越来越小,GA 就把单元数一路削减。
解决:把单元数偏差作为强约束写进适应度,N_target 附近才给低分,偏差每多一个或少一个单元都增加惩罚。权重不能太小,我一般按每个单元偏差等效 1~2 dB 副瓣代价来设。跑完一轮之后把 n_active 打出来看,如果偏离 N_target 超过 20%,直接调大头两项的权重。
5.2 翻车现场:交叉变异之后,最小间距约束全乱
现象:GA 结果里有两个靠得很近的单元,间距只有 0.1λ,优化过程中适应度只下降了很少,但最后画出的方向图出现很深的波谷或不规则起伏。
原因:交叉和变异算子作用在二进制位图上,只改变 0/1 状态,不感知物理间距。两个相邻候选位都是 1,中心距 0.5λ,这是正常网格;但如果变异把中间若干位全变成 1,再经过交叉,就可能出现连续多个 1 集中在一小段区域,等效间距小于 d_min。
解决:每一代变异之后强制调用间距修复函数。修复逻辑不复杂:从左到右扫描存活单元,如果两个单元间距小于 d_min,就把后一个单元关闭;同时统计当前单元数和 N_target 的差距,如果少了就在空缺位置随机补开。要注意的是修复顺序必须在交叉变异之后、合并精英之前,否则精英个体也可能被破坏掉。
5.3 翻车现场:同一份代码,每次跑出来的结果都不一样
现象:同一套参数连续跑三次,三次得到的最优副瓣电平差 2 dB,甚至最优单元位置都不一样,很难判断哪个结果是可信的。
原因:GA 是随机算法,初始种群、交叉点、变异位置都依赖随机数发生器。如果主脚本里没有固定 seed,每次运行都会从完全不同的起点开始搜索,最终落在不同解上。
解决:在脚本最开头固定随机种子,也就是采用 rng(20250317) 这样带日期编号的写法。固定之后,同版本代码在同版本 MATLAB 里跑出的结果应该完全一致。更进一步,我习惯用 parfor 跑 10 个不同 seed,取副瓣电平的中位数作为该参数组的最终评估值,这样能看出优化的稳定性和参数鲁棒性。
6. 拿到结果后的验证习惯:阵列因子复算、全波对拍与固定脚本
GA 输出一条漂亮的归一化方向图,不代表这个稀疏微带阵列真的能用。验收阶段我固定走三遍流程,每一步都能筛掉一批“纸面最优”的结果。
第一步,用独立脚本重新算一遍阵列因子。不要直接调用优化包里 calc_linear_AF 的同名函数,我通常新建一个文件,用完全不同的写法把阵列因子重新实现一次,把 GA 输出的坐标放进去,对比峰值副瓣。如果两边的副瓣差超过 0.1 dB,说明原脚本里存在归一化或主瓣挖空逻辑的问题,需要先修正再继续。
%% 验证脚本:直接按定义重算阵列因子,不调用包内函数 x_lam = [-3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5]; % 示例坐标 theta = linspace(-pi/2, pi/2, 7201); AF = zeros(size(theta)); for n = 1:numel(x_lam) AF = AF + exp(1j*2*pi*x_lam(n)*sin(theta)); end AF_db = 20*log10(abs(AF)/max(abs(AF))+eps); % 主瓣之外区域的峰值即为副瓣 [~, peak] = max(AF_db); % 以峰值最近的零点为边界,简单起见用 exclude_deg 指定挖掉的角度范围 exclude = find(abs(theta) < 1.2/numel(x_lam)*2); sll = max(AF_db(setdiff(1:numel(theta), exclude))); fprintf('验证 SLL = %.2f dB\n', sll);第二步,检查最小间距和边缘单元的物理位置。把坐标乘以波长,换算成毫米,再画到 CAD 图层里看。这一步能发现那些数值上成立、实际上贴片已经重叠或落到基板边缘之外的布局。
第三步,全波仿真对拍。我在 HFSS 里通常的做法是:每个存活单元设置为一个 50 欧姆集总端口激励,关闭的单元位置不画贴片,只保留接地板和介质基板。仿真得到的 E 面和 H 面方向图会和阵列因子差 1~3 dB,来源主要是贴片单元方向图和互耦。如果全波仿真的副瓣比 GA 目标高 3 dB 以上,说明单元间耦合太强,优先做法是把 d_min 调大到 0.6λ 重新优化,而不是盲目加惩罚权重。
从那以后我每次跑完稀疏阵列优化,都强制走一遍这三步:数组因子独立复算、坐标间距检查、全波扫描角对拍。尤其是微带阵列,GA 出来再漂亮的曲线,没有经过全波验证我都不敢交付。希望这个习惯对你也有帮助。
本文还有配套的精品资源,点击获取