ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

全双工与大型天线阵列:自干扰消除与干扰协调仿真

全双工与大型天线阵列:自干扰消除与干扰协调仿真 简介面向计算机、电子信息工程与数学等专业学生这份Matlab代码包聚焦全双工干扰协调网与大型天线阵列Massive MIMO的仿真实现特别适合课程设计、期末大作业与毕业设计场景。代码采用参数化编程所有关键参数均可灵活调整注释详细清晰并附有可直接运行的案例数据便于快速验证不同配置下的系统性能。包体共37个文件以21个.m脚本为主体涵盖主程序、遗传算法、梯度下降、贪心算法、图着色等核心算法模块另有6个.fig图形文件用于结果可视化4个.eps矢量图、3个.vsd结构图以及说明文档、许可证等辅助材料压缩包整体仅422KB轻量易用。内容覆盖全双工自干扰消除、干扰协调网络、大型天线阵列波束成形等关键技术的MATLAB实现思路已有49人学习下载。通过运行代码包读者可直观理解全双工与大规模MIMO的协同优化机制并基于现有框架修改参数、扩展算法开展自己的仿真实验。1. 全双工干扰协调网与大型天线阵列.zip先别急着解压想清楚它要解决什么全双工、干扰协调、大型天线阵列这三个词和 zip 后缀捆在一起通常意味着你拿到的是一个无线物理层仿真工程包用大型天线阵列的空间自由度去啃同频同时收发带来的自干扰再用干扰协调网去处理多小区之间和上下行交叉的干扰。它解决的核心矛盾是——全双工理论上能把频谱效率翻倍但工程上被自己的发射信号和邻区干扰压得抬不起头。适合做 5G-A/6G 预研、通信算法验证以及想评估同一座基站上收发同时开火到底值不值得投入的从业者。最反直觉的一点是全双工系统最难处理的往往不是远端干扰而是自己发射的信号直接灌进自己的接收机这个自己打自己的问题贯穿整个方案设计。2. 先把自干扰和空间自由度算明白为什么全双工必须用大型天线阵列硬啃2.1 自干扰有多大从发射功率到接收机底噪之间的百 dB 差距做全双工仿真的第一步不是打开代码而是先算一笔链路账。以 3.5 GHz 的微站为例发射功率取 23 dBm约 200 mW接收底噪按 -174 dBm/Hz 噪声谱密度加 20 MHz 带宽算是 -174 10log10(2e7) ≈ -101 dBm再加上 5 dB 噪声系数接收机底噪大约是 -96 dBm。发射天线和接收天线如果在同一块板子上相距 1 米自由空间损耗约 43 dB自干扰信号到达接收端还有约 -20 dBm。也就是说自干扰比底噪高出 76 dB比远端期望信号通常 -80 dBm 量级高出 60 dB。指望靠一种手段把这 60~80 dB 压掉不现实工程上必须分层天线域隔离先吃掉 20~40 dB模拟域做副本对消再吃掉 25~35 dB数字域基于估计出的残差信道做自适应抵消再压 30~40 dB。三层加起来才能逼近 110 dB 的目标这也是全双工系统能在工程上成立的底线。消除层级作用范围典型抑制量依赖条件天线域隔离空间隔离、极化隔离、屏蔽板20~40 dB天线布局与阵列结构不依赖信道估计模拟域消除射频链路重建耦合信号副本25~35 dB耦合路径的粗时延与相位估计数字域消除模拟抵消后的残差信道30~40 dB高精度信道估计受导频信噪比约束这三层里第一层也就是天线域隔离直接决定了后续两层压力。而大型天线阵列在这里的角色不只是隔离它可以通过调整各阵元的相位和幅度把发射波束的自干扰分量在接收方向上压出零点。这种做法不需要额外屏蔽结构只需要空间自由度这正是标题里把全双工和大型天线阵列放在一起的根本原因。2.2 大型天线阵列的自由度把干扰压进零陷而不是挡住为什么偏偏是大型天线阵列而不是多加两块屏蔽板因为屏蔽板解决的是宽带耦合解决不了方向性问题。大规模 MIMO 的每个阵元都能独立调整相位和增益N 根天线组成的天线阵列在空间上能形成 N-1 个独立零陷方向。对同时同频全双工来说这 N-1 个零陷里要分出几个给自干扰主径剩下给邻区干扰用户再剩下的才用于增强期望用户信号。设计波束权值时最常见的两个选择是迫零ZF和最小均方误差MMSE。迫零把干扰方向完全压零数学简单但会在低信噪比时放大噪声MMSE 在干扰抑制和噪声放大之间取了一个平衡点依赖信噪比参数实现上就是协方差矩阵里加一项单位阵。全双工仿真里我一般默认用 MMSE因为自干扰链路的信道估计本身有误差把干扰压到零不一定比留一点残余更优。这里有一个必须记住的不等式N ≥ K_user K_self margin。N 是基站天线数K_user 是同时服务的用户数K_self 是要压掉的自干扰主径数margin 一般留 4~8 根天线的余量用于抵抗信道估计误差和互耦效应。64 天线服务 8 个用户、压 2~4 条自干扰主径绰绰有余32 天线就会边缘发紧。很多翻车案例根子都在这个不等式没算清楚阵列自由度被分光波束权值无论如何优化都救不回来。2.3 干扰协调网从各扫门前雪到协同波束单基站的全双工已经把复杂度拉满了一旦进入组网问题会再上一个台阶。半双工系统里干扰主要来自邻区同频信号全双工系统里还多了上行用户干扰下行用户、下行基站干扰上行基站这两类交叉链路干扰。这些干扰不会因为你做了自干扰消除就自动消失只会在干扰拓扑里新增多条边。干扰协调网要解决的正是把这些边用协作的方式压下去。常见的做法是划分协调簇。簇内基站共享用户信道状态信息联合计算波束权值簇间通过功率控制和资源错开兜底。集中式协调把所有信道信息汇总到一个中心节点性能上限高但回传开销和实时性压力大分布式协调在本地算波束靠迭代交换干扰信息逼近联合解收敛速度是主要风险。全双工干扰协调网一般走折中路线自干扰消除留在本地做簇级用户干扰协调走联合设计把必须共享的信息控制在很小的维度上。3. 把仿真工程跑成真结果解压目录、最小复现链路与必调参数3.1 解压后先看什么工程目录与数据流拿到 zip 包我一般不会先跑主程序而是把解压后的工程收拢成下面这种布局再对着数据流理一遍。目录结构是否清晰直接决定你改参数时会不会迷路project_root/ ├── config/ │ ├── default.yaml # 全局参数天线数、发射功率、协调簇大小 │ └── scenario_ula64.json # 场景脚本用户角度、自干扰主径角度 ├── src/ │ ├── main.py # 仿真入口按阶段调用各模块 │ ├── channel/ │ │ ├── ula.py # 均匀线阵导向矢量生成 │ │ └── coupling.py # 自干扰耦合信道生成 │ ├── beamforming/ │ │ ├── zf.py │ │ └── mmse.py # 波束权值设计 │ ├── self_interference/ │ │ └── cancellation.py # 模拟/数字消除的残余建模 │ └── evaluate/ │ └── metrics.py # SINR、频谱效率统计 └── data/ └── rates.csv # 每次仿真的输出结果数据流整体上是单向的config 里的参数先被 main.py 读入生成信道和自干扰耦合矩阵然后交给波束权值模块计算自干扰模块根据消除目标对残余建模最后 evaluate 模块统计每用户 SINR 和频谱效率。最容易出错的地方是 channel 和 cancellation 的接口——自干扰消除目标是 dB 值但波束权值计算需要的是信道矩阵很多工程包这两个模块各自为政结果就是消除目标设了 100 dB实际仿真链路里只作用在了一个常数衰减上。3.2 最小可复现核心链路从 ULA 阵列到权值计算的 Python 版本zip 包里很多仿真是用 MATLAB 写的但核心链路用 Python 复现一遍并不难。我习惯先写一个最小闭环确认算法逻辑成立再搬回完整工程。下面这段代码完整描述了生成均匀线阵导向矢量、构造用户信道和自干扰耦合信道、设计带自干扰零陷约束的 MMSE 波束、计算残余自干扰下的每用户 SINR。import numpy as np def ula_steering(theta_deg, N, d_lambda0.5): 均匀线阵导向矢量theta_deg 为来波方向角度 idx np.arange(N) return np.exp(1j * 2 * np.pi * d_lambda * idx * np.sin(np.deg2rad(theta_deg))) def build_channel(N, K_user, K_self, angles_deg): 构造下行用户信道以及自干扰耦合信道。 H_user 形状为 N x K_userH_self 形状为 R x NR 取自干扰主径数 H_user np.column_stack([ula_steering(a, N) for a in angles_deg[:K_user]]) # 自干扰耦合信道从发射阵列到接收耦合端口的等效信道 H_self np.column_stack([ula_steering(a, N) for a in angles_deg[K_user:K_user K_self]]) return H_user, H_self.conj().T def design_zero_forcing(H_user, H_selfNone, methodmmse, snr_db20): 设计发射波束权值要求自干扰方向上尽量压零。 先对 H_self 做 SVD 取零空间投影再在投影域做 ZF/MMSE N H_user.shape[0] if H_self is not None and H_self.shape[0] N: # SVD 取右奇异向量的后 N-R 列作为自干扰零空间 _U, _S, Vh np.linalg.svd(H_self, full_matricesTrue) null_dim N - H_self.shape[0] V_null Vh[null_dim:, :].conj().T # 零空间投影矩阵 H_work V_null.conj().T H_user # 投影后的等效用户信道 if method mmse: # MMSE 的正则项snr_db 越小正则项越大越偏重抗噪声 reg 10 ** (-snr_db / 10) W_proj H_work np.linalg.inv(H_work.conj().T H_work reg * np.eye(H_work.shape[1])) else: W_proj H_work np.linalg.inv(H_work.conj().T H_work) # 纯 ZF W V_null W_proj else: reg 10 ** (-snr_db / 10) W H_user np.linalg.inv(H_user.conj().T H_user reg * np.eye(H_user.shape[1])) # 每条流功率归一化保证发射总功率等于用户数 W W / np.linalg.norm(W, axis0, keepdimsTrue) return W def evaluate_sinr(H_user, W, residual_db30, noise_power1e-6): 计算全双工残余自干扰下的每用户 SINR。 residual_db 表示模拟域数字域对原始耦合的抑制量 res_scale 10 ** (-residual_db / 20) K H_user.shape[1] sinr_list [] for k in range(K): signal np.abs(H_user[:, k].conj() W[:, k]) ** 2 inter sum( np.abs(H_user[:, k].conj() W[:, j]) ** 2 for j in range(K) if j ! k ) # 残余自干扰简化为与总发射功率成正比的白噪声项 self_inter res_scale ** 2 * np.sum(np.abs(W) ** 2) sinr_list.append(signal / (inter self_inter noise_power)) return np.array(sinr_list) if __name__ __main__: N 64 # 天线数 K_user, K_self 8, 2 # 用户数、自干扰主径数 # 前 8 个是用户方向后 2 个是自干扰耦合主径方向 angles [-60, -30, 0, 25, 45, 61, -10, 12, 5, -40] H_user, H_self build_channel(N, K_user, K_self, angles) W design_zero_forcing(H_user, H_self, methodmmse, snr_db20) sinr evaluate_sinr(H_user, W, residual_db30, noise_power1e-6) rate np.log2(1 sinr) print(每用户 SINR(dB):, np.round(10 * np.log10(sinr), 2)) print(频谱效率总和(bps/Hz):, round(np.sum(rate), 2))代码逻辑分四步。第一步ula_steering生成导向矢量所有信道和波束计算都建立在它之上第二步build_channel把角度列表拆成用户信道和自干扰信道注意自干扰信道做完共轭转置后形状是 R x N这决定了后续零空间投影的方向第三步design_zero_forcing是最核心的先在自干扰信道的零空间里做投影再对投影后的等效用户信道做 MMSE这样得到的波束既照顾用户方向又把自干扰主径压向零陷第四步算 SINR 时残余自干扰用一个与发射总功率成正比的噪声项近似residual_db越大这个项越小。参数上最值得玩味的是residual_db。它写的是 30意味着原始自干扰耦合经过模拟加数字消除后只剩 1/1000 的功率。如果你把这个值改成 60SINR 通常会立刻改善但这是假象——高消除目标需要高精度信道估计这个最小例子里没有建模信道估计误差所以不能只看最终曲线还要在完整工程里把导频信噪比和消除残差绑在一起。3.3 必调参数清单每个改动会拖出什么问题参数默认值调大的影响调小的风险N 天线数64自由度增多能压更多干扰径仿真耗时上升低于 K_userK_self 时波束直接失效K_user 用户数8频谱效率总量上升单用户 SINR 下降浪费可用自由度K_self 自干扰主径数2建模更精确占自由度更多漏掉强耦合径时零陷错位residual_db 消除目标30~40残余干扰变小但要求信道估计更准太小则全双工增益被残余淹没snr_db 正则项20波束偏重抗噪声干扰抑制变弱波束偏重压干扰低信噪比时噪声放大表格里的五个参数互相牵制最典型的误用是只看单点结果不做敏感性扫描。比如把 K_self 从 2 改成 4N 不变自由度就少了两维用户间的干扰抑制必然变差。仿真时我建议先保持 N64 和 K_user8 不动单独扫描 residual_db 从 20 到 50画出频谱效率变化曲线就能直观看到消除目标与性能之间不是单调关系。这个曲线也能帮你判断当前信道估计精度下全双工到底还能不能带来净增益。提示config/default.yaml里的参数尽量保持和代码里常量一致很多翻车现场是配置文件改了但代码里写死了默认值两套参数互相覆盖。4. 从单小区到干扰协调网联合波束、协调集与收敛控制4.1 协调簇怎么划让强干扰链路闭合再谈联合计算从单小区仿真往干扰协调网扩展最容易犯的错是一上来就按地理位置把相邻基站圈进协调集。干扰协调的目的不是地理上相邻的站一起干活而是互相产生强干扰的链路都在同一个协调集内。我一般先做一次干扰拓扑图统计每条跨小区链路的接收功率凡是高于目标用户底噪 10 dB 以上的干扰源必须被划进同一个协调簇否则这个簇的联合波束算得再好也会被簇外面一条没闭合的强干扰链路打穿。导频复用的设计也在这个阶段定。簇内用户用正交导频簇间可以复用。全双工系统里还要额外注意上行和下行导频之间的串扰——上行探测导频可能被下行发射波束干扰导致信道估计结果里混入发射信号的影子。做法是给上下行导频分配不同的正交序列集合或者加扰偏置让交叉导频污染变成噪声项而不是相关项。4.2 两阶段联合波束设计先本地消自干扰再做簇级协作联合波束设计如果一步到位把所有基站信道矩阵都堆进一个大矩阵做全局迫零性能上限最高但回传开销和计算量都不可控。工程上我更推崇两阶段做法。阶段一每个基站做本地自干扰零陷这一步只用自己的 H_self输出一个本地投影矩阵和一个净化后的等效用户信道。阶段二各基站把等效用户信道发给协调簇的中心节点中心节点只需要在等效信道上做联合 MMSE算出的权值再乘回本地的零空间投影。这样做的好处是自干扰消除的细节不需要上报跨站共享的只是 K_target x K_interferer 维度的等效信道信息量小了一个数量级。两阶段方案不是全局最优但因为自干扰和用户间干扰在空间维度上往往是近似正交的性能损失通常可以控制在 0.5 dB 以内换来的是实现简单、回传压力小。如果你观察到的性能损失超过 1 dB先怀疑本地零空间投影和簇级权值之间有没有做功率再归一化这个坑我踩过不止一次。4.3 收敛判据盯总频谱效率更要盯用户级 SINR分布式干扰协调要靠迭代收敛到稳定点判断收敛的指标往往不是总频谱效率而是用户级 SINR 的抖动幅度。总频谱效率可能只变了 0.01 bps/Hz但某个边缘用户的 SINR 在 ±3 dB 之间来回跳这时如果判收敛下一步整条链路都会带病运行。我常用的收敛判据是双条件相邻两次迭代的频谱效率变化小于 0.01 bps/Hz并且所有用户 SINR 变化小于 0.5 dB。代码里对应这样一段检查delta_rate abs(rate_cur - rate_last) delta_sinr_max np.max(np.abs(sinr_db_cur - sinr_db_last)) if delta_rate 0.01 and delta_sinr_max 0.5: print(迭代收敛当前总频谱效率, round(rate_cur, 3)) else: print(未收敛max SINR 抖动, round(delta_sinr_max, 2), dB)只判断delta_rate会漏掉平均收敛、个别用户抖动的假收敛这个教训是在一个 57 小区的场景里踩出来的。另外迭代次数上限也要设一般设 50 次超过就说明协调参数不合理常见原因是功率初始化太大导致振荡或者簇内有用户处于深衰落却仍被分配了大量功率。5. 全双工干扰协调仿真避坑五个最容易让整套结果作废的细节下面的坑我基本都踩过有的一眼就能看到曲线反直觉有的直接带偏了整个方案方向。按现象、原因、解决的顺序写方便你直接对照排查。5.1 自干扰消除目标设得越高性能反而越差现象residual_db从 30 改到 60频谱效率非但没涨反而掉了好几个点。原因高消除目标依赖高精度信道估计仿真里如果没有建模导频噪声和估计误差消除就是假成功一旦你在级联链路里加上导频估计误差零陷方向就会偏残余自干扰反而被放大。解决把信道估计精度和自干扰消除量耦合建模先跑一遍不同导频信噪比下的可达消除曲线找到消除目标的上限。一般导频信噪比每提高 10 dB可支撑的消除目标可以提高约 10 dB两者近似线性关系。5.2 波束权值把干扰用户点亮了现象某个用户 SINR 特别差其他用户都正常波束图一看主瓣却指到了干扰用户方向。原因导频污染。簇间复用导频时干扰用户的导频序列和目标用户相关信道估计矩阵里混入了邻区导频分量MMSE 权值会把邻区干扰当成期望信号来增强。解决簇内导频正交是底线簇间导频加扰偏置全双工场景里再检查一下上行探测导频和下行发射导频是否落在同一资源位置。5.3 别把串口全双工的思路带进无线全双工现象从嵌入式转过来的工程师看到全双工第一反应是两片 485 芯片组成 422 全双工通信那种收发各走各的物理通道于是仿真里给自干扰加一个固定衰减就完事。原因无线全双工没有另一根线自己的发射天线和接收天线通过空间电磁耦合、机箱互耦和 PCB 走线串扰是频率选择性的多径信道不是一个常数损耗。解决把自干扰建造成一条包含 2~4 条主径的向量信道保留幅度、相位和频选特性波束零陷才有真正的作用对象。只加固定衰减的仿真本质上等于一直相信天线隔离是完美的这和在真实硬件上开环跑没什么区别。5.4 频谱效率算成负值现象SINR 算出来有 10 dBlog2(1 SINR) 却是负数。原因功率归一化翻车。常见的是把发射总功率除以天线数而不是把每一条用户流的功率归一化到 1导致有效发射 SNR 比显示值低十几个 dBSINR 直接跌到 1 以下。解决做完权值设计后检查np.sum(np.abs(W)**2, axis0)是否等于用户数 K再用W / np.linalg.norm(W, axis0, keepdimsTrue)逐流归一化。这个检查写进评估函数里每次仿真结束跑一遍能省下大量排错时间。5.5 单小区转多小区时边缘用户直接吊线现象单小区仿真频谱效率很漂亮扩展到 7 小区或 19 小区场景边缘用户 SINR 直接掉到负数。原因协调集没有闭合。小区内部的干扰被波束压掉了但跨小区最强的几条干扰链路不在协调集范围内联合权值根本管不到它们。解决先画干扰拓扑图把接收功率高于底噪 10 dB 的跨小区干扰链路全部标出来再以这些链路为主体检索协调簇是否闭合。只要有一条强链路落在簇外边缘用户的曲线就一定会在扩展场景里掉下来这不是调参能解决的得回去改簇划分。6. 验证与扩展波束图、参数扫描和半双工基线对比验证这套方案我通常用三种手段叠加缺一不可。第一种是波束图可视化直接看零陷是不是真的落在了自干扰主径方向。用前面最小复现的代码补一段绘图就能看到import matplotlib.pyplot as plt grid np.linspace(-90, 90, 361) resp [np.abs(ula_steering(deg, N) W[:, 0]) for deg in grid] plt.plot(grid, 20 * np.log10(np.maximum(resp, 1e-12))) plt.axvline(angles[0], colorr, ls--, labeluser 0) plt.axvline(angles[8], colorg, ls--, labelself path 1) plt.xlabel(angle (deg)) plt.ylabel(gain (dB)) plt.legend() plt.grid(True) plt.show()看到用户方向有主瓣、自干扰方向有深零陷才说明权值设计方向是对的。第二种是做天线数扫描从 16、32、64 到 128 逐个跑频谱效率增益会在某个天线数之后开始饱和这时候新增的自由度对系统性能的贡献已经很小曲线拐点就是你值得投入的阵列规模上限。第三种是跟半双工基线对比同样天线数、同样功率把全双工时隙减半作为基线计算全双工净增益 全双工总速率 - 半双工基线速率/ 半双工基线速率。如果这个净增益小于 10%那说明自干扰残余和交叉链路干扰已经把全双工的理论红利吃光了。我现在的习惯是先把半双工基线跑出来再开全双工最后回头看波束图不然增益来源根本说不清楚。这套方案适合先在小规模仿真里验证自干扰零陷和协调集闭合再往硬件在环推。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进