拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MIMO线性预编码算法对比:ZF/BD/SLNR仿真实现与避坑指南

简介面向多输入多输出MIMO下行链路中的线性预编码算法比较场景这份MATLAB源码包系统实现了奇异值分解SVD、块对角化BD、迫零ZF、匹配滤波MF、信漏噪比SLNR、最小均方误差MMSE等多种算法的性能对比适合通信工程专业学生、算法研究人员以及预编码技术初学者进行仿真验证与算法理解。资源包共34个文件其中14个M脚本负责主程序、各算法函数、调制映射与注水功率分配20个FIG图形文件直观呈现和速率、误码率等性能随信噪比或天线数变化的对比曲线整个压缩包仅207KB轻量紧凑、便于下载。目前已有228人学习验证了该资源在预编码仿真中的参考价值。通过运行主脚本可快速复现4用户2天线MIMO系统下的和速率与误码率性能并支持动态调整信噪比、发射天线数等参数观察不同算法的对比结果代码结构清晰、模块化程度较高便于移植到自有仿真框架也可为理解线性预编码原理和论文图表绘制提供有力支撑。1. 六种线性预编码放在同一套源码里对比这套代码在解决什么、适合谁做 MIMO 下行链路仿真时几乎每个人都会撞上这样一份清单SVD、BD、ZF、MF、SLNR、MMSE六种预编码方案摆在面前文献里每篇论文都说自己的算法最优。可真把代码跑起来就会发现排序经常变——低信噪比下匹配滤波占优高信噪比下迫零和块对角化才显本事天线数不够时 SLNR 又成了那个怎么都能用的方案。这套线性预编码性能对比源码就是解决这个问题的把六种算法放进同一信道模型、同一功率归一化规则、同一 BER 统计框架里公平比一轮。适合正在做 MIMO 预编码课程设计、物理层算法验证或者基站预编码选型的从业者。看完你能得到一张可以解释为什么性能排序会变的曲线而不是又多一份堆公式的理论笔记。2. 预编码的数学模型选型为什么 ZF 会放大噪声SVD 只在单用户最优2.1 系统模型单用户和多用户必须先分清预编码公式本身不复杂但六种算法适用的场景差异极大选型前第一件事是问一句你的系统是单用户 MIMO 还是多用户 MIMO先写系统模型。发射端有 Nt 根天线服务 K 个用户第 k 个用户有 Nr,k 根天线。发送符号向量 x_k 经过预编码矩阵 F_k形状 Nt×NsNs 是发送流数后第 k 个用户的接收信号可以写成y_k H_k F_k x_k Σ_{j≠k} H_k F_j x_j n_k前面两项里第一项是有用信号第二项是其他用户信号泄漏到本用户的干扰叫用户间干扰IUIInter-User Interferencen_k 是高斯白噪声方差为 σ²。单用户 MIMO 只有 H、F、x、y没有第二项问题简化为怎么把一个 Nt×Nr 的信道拆成多个并行子信道。多用户 MIMO 则多了一个维度除了要照顾每个用户自己的多流复用还得处理用户之间的干扰。六种算法在这个坐标系里的位置完全不同——MF、ZF、MMSE 单用户和多用户都能用但多用户下必须用整体信道矩阵求逆SVD 是单用户的理论最优直接搬到多用户会因干扰崩溃BD 和 SLNR 则是典型的多用户方案。选型第一步不是比性能是比场景否则后面全是白做。2.2 单用户家族SVD 最优MF/ZF/MMSE 的边界在哪单用户 MIMO 里信道矩阵 H 的 SVD 分解 H UΣV^H 给出了最漂亮的答案发射端用 V 做预编码取前 Ns 列接收端乘 U^H等效信道变成对角矩阵 Σ一组并行子信道互不干扰。这就是 SVD 预编码容量上是最优的没有争议。但工程上 SVD 需要接收端把完整的信道状态信息反馈给发射端且每次信道变化都要重新分解代价不小。于是就有了更廉价的线性方案MF匹配滤波也叫 MRT直接用信道的共轭转置做预编码 F H^H。它的物理含义是把信号能量集中到目标信道方向上完全不考虑干扰。接收端信噪比低时MF 性能反而好因为它不放大噪声但信噪比一高干扰变得无法忽略曲线会明显掉头。ZF 迫零预编码取 F H^H (H H^H)^{-1}目标是让等效信道 HF 变成单位阵彻底消除干扰。代价是当 H 接近奇异时预编码矩阵的范数会变得很大把噪声成倍放大——这就是常说的ZF 放大噪声。低信噪比下 ZF 往往比 MF 还差高信噪比下才追回来。MMSE 是折中F H^H (H H^H αI)^{-1}其中 α 与噪声方差和发送功率的比值相关。多出来的 αI 起到了正则化作用防止求逆时放大噪声。当信噪比趋近无穷MMSE 趋近 ZF信噪比趋近 0MMSE 趋近 MF。工程上 MMSE 是默认选择原因就是它在各种信噪比下都不会太差。2.3 多用户家族BD 的零空间与 SLNR 的泄漏多用户 MIMO 预编码要在每一根发射天线上同时向多个用户发信号干扰管理成了主角。BD块对角化的核心思想是把其他所有用户的信道构成一个干扰信道矩阵 H̃_k [H_1^T, ..., H_{k-1}^T, H_{k1}^T, ..., H_K^T]^T然后找到这个矩阵的零空间让预编码矩阵 F_k 完全落在零空间里。这样 H_j F_k ≈ 0j ≠ k用户间干扰从物理上被消除。F_k 的求解分两级先对 H̃_k 做 SVD取右奇异矩阵中零空间对应的列再在该零空间内对 H_k 做一次 SVD挑出最优的 Ns 个方向。BD 性能好且能支持多流但有一个硬约束所有用户接收天线总数必须小于发射天线数否则零空间不存在公式直接崩掉。SLNR信漏噪比换了个思路不追求完全消除干扰最大化有用信号功率 /泄漏给其他用户的功率 噪声功率。对第 k 个用户SLNR 的优化目标是max f^H H_k^H H_k f / f^H (σ²I Σ_{j≠k} H_j^H H_j) f这是一个广义瑞利商问题最优解是广义特征分解中最大广义特征值对应的特征向量。SLNR 的好处是不要求零空间存在天线数紧张时照样能用同时对信道误差的鲁棒性比 BD 好。代价是闭式解要解一个 Nt×Nt 的广义特征问题数值稳定性需要小心处理。2.4 选型坐标一张表看六种算法的适用边界整理成一张选型表方便照着参数选方案算法干扰处理核心思路最佳适用位置主要代价MF不处理共轭转置能量集中低信噪比、单用户基线高信噪比受干扰限制ZF完全消除信道求逆天线富余、高信噪比病态信道放大噪声MMSE折中抑制正则化求逆工程默认、未知信道需要估计噪声方差SVD不处理单用户信道对角化单用户最优、多流多用户场景需配合 BDBD完全消除零空间投影多用户、天线充足总接收天线必须小于 NtSLNR按泄漏最小化广义特征分解天线紧张、信道相关广义特征求解复杂度高这张表是后面所有仿真判断的依据。比如你发现 ZF 的 BER 曲线比 MF 还高先别怀疑代码先看信噪比区间再比如你发现 BD 报矩阵维度错误先数一数总接收天线是不是超过 Nt 了。3. 搭建可复现的对比仿真信道模型、参数表与最小 Python 框架3.1 参数怎么定天线数决定你一半的结论仿真参数不是随手填的天线数直接决定哪些算法能跑、哪些算法不能跑。本文采用的是一套多用户配置Nt 8 发射天线K 4 个用户每用户 Nr 2 根接收天线Ns 2 条数据流。这样每用户 2 流、4 个用户共 8 条流总接收天线数等于发射天线数BD 和 ZF 的维度约束恰好满足。为什么强调这一点因为如果总接收天线数大于 NtZF 的 (H H^H) 矩阵就会奇异BD 也找不到零空间仿真直接报错。反过来如果天线数太富余所有算法性能都会趋同看不出区别。这套参数是论文和工程里最常见的一组对比结果最有代表性。其余参数按常见做法设置参数取值说明调制方式QPSK每符号 2 bitBER 统计方便SNR 范围0~20 dB步进 2 dB覆盖典型工作区信道模型i.i.d. Rayleigh每用户独立生成复高斯矩阵每用户发射功率归一化为 1逐用户归一化保证公平比较蒙特卡洛帧数视误码率自动增加每个 SNR 点至少统计 50 个错误比特3.2 信道模型与 SNR 定义瑞利衰落不是随便生成个复数i.i.d. Rayleigh 信道的生成方法是 H (randn j·randn) / √2这样每个元素的方差是 1。但要提醒一句生成完信道后最好做一次功率归一化把信道矩阵的期望功率归一否则 SNR 的定义会和实际不符。SNR 的定义直接影响噪声方差总发射功率和有用信号功率都归一化为 1 后噪声方差 σ² 10^{-SNR/10}。这里有个容易绕晕的地方每个用户发射功率都是 1四个用户叠加后总发射功率其实变大了。本文刻意采用逐用户归一化目的是聚焦算法对干扰的抑制能力而不是让功率分配差异干扰结论。实际系统里如果需要公平功率分配还要额外加注水算法或等功率分配那是另一个话题。3.3 最小 Python 框架一套代码跑遍所有系统配置接下来是一个可直接运行的仿真骨架。先把系统参数、QPSK 调制解调和 BER 统计的主循环搭起来后面每一章实现的预编码函数都可以直接塞进这个循环import numpy as np Nt, K, Nr, Ns 8, 4, 2, 2 snr_db_range np.arange(0, 22, 2) # QPSK 调制把 bit 映射成能量为 1 的复数符号 def bits_to_qpsk(bits): bits bits.reshape(-1, 2) real (bits[:, 0] * 2 - 1) / np.sqrt(2) imag (bits[:, 1] * 2 - 1) / np.sqrt(2) return (real 1j * imag).astype(complex) # QPSK 硬判决解调 def qpsk_to_bits(syms): hard_real (syms.real 0).astype(int) hard_imag (syms.imag 0).astype(int) return np.stack([hard_real, hard_imag], axis1).reshape(-1) # 生成一组多用户信道每个用户一个 H_k形状为 (Nr, Nt) def generate_channels(): H_list [] for _ in range(K): H (np.random.randn(Nr, Nt) 1j * np.random.randn(Nr, Nt)) / np.sqrt(2) H_list.append(H) return H_list # 蒙特卡洛主循环precoder_func 接收 H_list 和 sigma2返回 F_list def run_ber_simulation(precoder_func): ber_per_snr [] for snr_db in snr_db_range: sigma2 10 ** (-snr_db / 10) total_errors, total_bits 0, 0 while total_errors 50 and total_bits 5e6: H_list generate_channels() F_list precoder_func(H_list, sigma2) for k in range(K): bits np.random.randint(0, 2, Ns * 2) x bits_to_qpsk(bits).reshape(Ns, 1) Fk F_list[k] # 有用信号 其他用户干扰 噪声 y H_list[k] Fk x sigma2 ** 0.5 * ( np.random.randn(Nr, 1) 1j * np.random.randn(Nr, 1) ) / np.sqrt(2) for j in range(K): if j ! k: xj bits_to_qpsk(np.random.randint(0, 2, Ns * 2)).reshape(Ns, 1) y H_list[k] F_list[j] xj Heff H_list[k] Fk # 接收端 MMSE 检测 W np.linalg.solve(Heff.conj().T Heff sigma2 * np.eye(Ns), Heff.conj().T) est W y rx_bits qpsk_to_bits(est) total_errors np.sum(rx_bits ! bits) total_bits len(bits) ber_per_snr.append(total_errors / total_bits) return ber_per_snr这段代码的逻辑说明如下bits_to_qpsk 把二进制比特映射为 ±1/√2 ± j/√2保证每个符号的平均能量为 1run_ber_simulation 是蒙特卡洛主循环外层遍历 SNR内层不断产生信道、发送数据、计算预编码、叠加噪声和干扰最后统计误比特率。循环终止条件用的是至少统计 50 个错误比特而不是固定帧数。为什么这么做因为高信噪比下误码率很低如果固定帧数BER 曲线后半段会抖动得很厉害按错误比特数控制可以让每个 SNR 点的统计精度基本一致。上限 5×10^6 bit 是为了防止极低误码率时仿真时间过长。3.4 预编码矩阵的功率归一化这个坑不踩后面全是幻觉预编码函数返回的 F_k 不能直接用必须做功率归一化。原因在于 MF 和 ZF 的矩阵范数差异可以差出几个数量级如果不归一化MF 的曲线会因为发送功率小得可怜而看起来差到离谱ZF 又会因为范数太大而表现得异常优秀。这不是算法本身的差异是功率没对齐。本文统一用逐用户 Frobenius 归一化在每个预编码函数返回前加一行Fk Fk / np.linalg.norm(Fk, fro)这样一来每个用户的发射功率恒为 1各 SNR 点的噪声方差 σ² 可以直接用 10^{-SNR/10}。需要说明的是逐用户归一化的代价是总发射功率会随用户数增长但既然我们的目标是算法干扰抑制能力的对比这是最公平的基准。如果将来要模拟真实基站功率受限场景就要改成整体归一化并配合用户间的功率分配策略那属于系统级仿真的范畴了。4. 写对比脚本六种预编码的实现要点、关键函数与参数校准4.1 MF/ZF/MMSE 实现整体信道矩阵与逐用户归一化的次序MF、ZF、MMSE 在多用户场景下的标准做法是把所有用户的信道堆叠成一个总矩阵 H_all形状为 (K·Nr) × Nt然后统一求预编码矩阵 F_all再按用户拆开、逐用户归一化。def mf_precoder(H_list, sigma2): H_all np.vstack(H_list) # (K*Nr, Nt) F_all H_all.conj().T # (Nt, K*Nr) return split_and_normalize(F_all, Ns) def zf_precoder(H_list, sigma2): H_all np.vstack(H_list) # 用 solve 代替 inv数值稳定性更好 F_all H_all.conj().T np.linalg.solve(H_all H_all.conj().T, np.eye(H_all.shape[0])) return split_and_normalize(F_all, Ns) def mmse_precoder(H_list, sigma2): H_all np.vstack(H_list) alpha sigma2 # 每用户功率归一为 1故 alpha sigma2 F_all H_all.conj().T np.linalg.solve( H_all H_all.conj().T alpha * np.eye(H_all.shape[0]), np.eye(H_all.shape[0]) ) return split_and_normalize(F_all, Ns) def split_and_normalize(F_all, Ns): F_list [] for k in range(K): Fk F_all[:, k * Nr:(k 1) * Nr][:, :Ns] F_list.append(Fk / np.linalg.norm(Fk, fro)) return F_list三个函数的区别只在一行ZF 用的是 H_all H_all^H 求逆MMSE 在求逆前加了 αIMF 干脆不求逆。这段代码里有两个参数需要注意。第一个是 α 的取值。由于前面做了逐用户功率归一化每个用户的发射功率是 1所以 α σ²。如果改成整体功率归一化α 必须跟着发射功率缩放否则 MMSE 会退化成 ZF。第二个是切分方式第 k 个用户占据 F_all 的第 k·Nr 到 (k1)·Nr 列若每用户只发 Ns 条流就取前 Ns 列。这里最常犯的错是把 Ns 和 Nr 弄混导致矩阵维度对不上或者在 BER 统计时直接报广播错误。4.2 SVD 预编码只在单用户才成立的最优SVD 预编码在单用户 MIMO 里的实现非常简洁发射端取 V 的前 Ns 列接收端乘 U^H 即可把信道拆成并行子信道。在多用户场景里经典做法是把 SVD 作为 BD 的内部步骤而不是独立算法。所以这一小节先给单用户的完整实现def svd_single_user_precoder(H, sigma2, Ns2): U, S, Vh np.linalg.svd(H, full_matricesFalse) F Vh[:Ns, :].T # V 的前 Ns 列 return F / np.linalg.norm(F, fro)代码本身没什么玄学但有一个认知要掰正SVD 在多用户场景下如果直接对每个用户分别做 SVD完全不处理 IUI用户间干扰会成为主要矛盾BER 曲线会表现出明显的错误平台。文献里常看到的SVD 性能最优几乎都限定在单用户或者配合 BD 使用的场景单独拿 SVD 去做多用户对比属于刻舟求剑。4.3 BD 的实现两级 SVD 与零空间维度判断BD 是六个算法里实现最容易出 bug 的一个。核心逻辑是对每个用户构造干扰信道矩阵 H̃_k 并做 SVD取零空间基再在零空间内对 H_k 做第二次 SVD。def bd_precoder(H_list, sigma2): F_list [] for k in range(K): # 第一步堆叠其他所有用户的信道 H_others np.vstack([H_list[j] for j in range(K) if j ! k]) _, _, Vh_others np.linalg.svd(H_others, full_matricesTrue) null_dim Nt - H_others.shape[0] # 零空间维数 Nt - (K-1)*Nr V_null Vh_others[-null_dim:, :].T # 零空间基形状 (Nt, null_dim) # 第二步在零空间内对目标用户做第二次 SVD H_eff H_list[k] V_null _, _, Vh_eff np.linalg.svd(H_eff, full_matricesFalse) Fk V_null Vh_eff[:Ns, :].T F_list.append(Fk / np.linalg.norm(Fk, fro)) return F_list逻辑说明第一步 Vh_others 的形状是 (Nt, Nt)由 SVD 的性质最后 null_dim 行对应 H_others 的零空间方向经过转置后成为零空间基第二步把 H_k 投影到零空间再取前 Ns 个右奇异向量作为最终的预编码方向。一个关键判断null_dim Nt - (K-1)·Nr。在本文参数下是 8 - 6 2恰好等于每用户流数 Ns因此每个用户的预编码矩阵是方阵。如果 zero dimension 小于 NsBD 就无解后面 5.2 会详细展开这种情况的处理。另外注意全 SVD 和瘦 SVD 的区别这里必须用 full_matricesTrue 才能拿到完整的零空间用默认的瘦 SVD 会直接丢维度报错。4.4 SLNR 的实现广义特征分解的取法SLNR 的实现核心是求解广义特征值问题。每个用户的优化目标涉及两个 Nt×Nt 的 Hermitian 矩阵A_k H_k^H H_k 对应有用信号B_k σ²I Σ_{j≠k} H_j^H H_j 对应泄漏加噪声。最优预编码是最大广义特征值对应的特征向量。from scipy.linalg import eigh def slnr_precoder(H_list, sigma2): F_list [] for k in range(K): A_k H_list[k].conj().T H_list[k] # 有用信号相关矩阵 B_k sigma2 * np.eye(Nt) # 噪声项保证 B 正定 for j in range(K): if j ! k: B_k H_list[j].conj().T H_list[j] # 泄漏项 # 广义特征分解特征值升序排列 _, eigvecs eigh(A_k, B_k) Fk eigvecs[:, -Ns:] # 取最大的 Ns 个特征向量 F_list.append(Fk / np.linalg.norm(Fk, fro)) return F_listscipy.linalg.eigh 的第二个位置参数传入 B_k返回的特征值默认按升序排列最大特征值在最后所以取 -Ns 到最后列。这里有两个容易翻车的点一是 B_k 必须正定好在 σ²I 作为正则项保证了这一点二是如果误取前 Ns 列最小特征值SLNR 的曲线会比 MF 还难看这种错误从数值上完全不会报错只能靠对比曲线位置发现。如果环境里没有 scipy可以用 numpy 替代对 B_k 做 Cholesky 分解 B L L^H通过变换把广义特征问题转为普通特征问题但数值稳定性稍差仍建议优先 eigh。4.5 串起来跑完整轮五个算法全部接入主循环把上面三个函数和 SVD 单用户实现都接入 run_ber_simulation就能得到完整的对比图。实际跑的时候我建议每换一个算法都单独确认一次曲线形状MF 的 BER 在低 SNR 应该和中高 SNR 的 ZF 有交叉MMSE 的曲线应该全程贴着 ZF 或更优BD 和 SLNR 在高 SNR 下应该明显优于 MF。如果这些常识位置对不上优先检查归一化和接收端检测器而不是先怀疑理论。5. 预编码对比的五个常见翻车点现象、根因与修复5.1 功率归一化不统一MMSE 在低 SNR好得离谱现象MMSE 的 BER 在低信噪比下显著优于 SVD甚至逼近无干扰理论上限怎么看都不合理。原因预编码矩阵没有做逐用户功率归一化或者归一化方式不一致。MF 的 F 范数天然比其他算法小ZF 的范数又偏大如果直接用未归一化的矩阵仿真实际上每用户发射功率相差十倍以上BER 曲线反映的是功率差异而不是算法差异。解决统一在预编码函数末尾执行 Fk Fk / np.linalg.norm(Fk, fro)。同时检查主循环里的 x 符号能量是否为 1QPSK 映射必须用 ±1/√2 而不是 ±1否则 SNR 的定义整体偏移 3 dB。这一步做完各算法的相对位置才会回到正常范围。5.2 天线维度违反约束BD 和 ZF 直接矩阵奇异现象把用户数从 4 改成 5或者每用户天线数从 2 改成 3BD 报 svd 维度错误ZF 报 LinAlgError而 SLNR 和 MF 还能正常出数。原因BD 的零空间维度 Nt - (K-1)·NrZF 要求总接收天线数 ≤ Nt。当用户天线总数超过发射天线时H̃_k 的零空间不存在数学上无解。代码不会提示约束不满足只会表现为矩阵奇异或维度对不上。解决在仿真脚本入口加一个断言。Nt ≥ K·Nr 是 ZF 和 BD 的必要条件如果 Nt 不够要么减少用户数或单用户天线数要么改用 SLNR。SLNR 不要求零空间天线紧张时它是少数还能工作的线性方案这也是它的一大实用价值。5.3 接收端检测器和预编码混为一谈曲线高得没有意义现象同一套预编码代码有些人跑出来的 BER 比论文里的还好仔细看是接收端用了所谓的理想判决或者把整条链路当成单用户等效来检测。原因接收端后处理方式没有统一。预编码性能对比应该固定接收机否则 ZF 预编码配合 ZF 检测、MMSE 预编码配合 MMSE 检测会让结果变成预编码检测器的联合性能谁占便宜完全说不清。解决统一用 MMSE 检测器公式是 W (H_eff^H H_eff σ²I)^{-1} H_eff^H即前文主循环里的写法。这样对 ZF 和 BD 这类已经消除干扰的方案检测器退化为匹配滤波对 MF 和 SLNR 这类残留干扰的方案检测器还能起到一定补偿作用。需要对比接收机影响时再单独把检测器作为变量。5.4 蒙特卡洛统计帧数太少BER 曲线抖到没法判断现象低 SNR 区域 BER 两条曲线纠缠在一起高 SNR 区域曲线抖得像是噪声信号同一个点每次跑结果都不一样。原因BER 统计是靠随机事件撑起来的。高 SNR 下误码事件很稀疏如果固定只跑 500 帧可能一个错误比特都没有BER 记成 0曲线直接断掉低 SNR 下如果只统计了几百个比特误码率的方差非常大。解决按每个 SNR 点至少统计 50 个错误比特作为循环退出条件同时设定一个最大比特数上限防止算法陷入死循环。实际仿真时还可以给高 SNR 点增加帧数比如从 0 dB 到 20 dB 每个点单独设置统计量这是最稳的提速手段。5.5 SLNR 广义特征分解取错方向性能比 MF 还差现象SLNR 的 BER 曲线在高 SNR 下不仅没有收敛反而比 MF 差且差得很稳定——不是随机抖动是稳定的差。原因scipy.linalg.eigh 返回的特征值默认升序排列第一次写 SLNR 的人极容易自然取前 Ns 列也就是最小特征值对应的特征向量。这些方向完全不匹配信号子空间预编码等于把能量发到了错误方向。解决取特征向量矩阵的倒数 Ns 列即 eigvecs[:, -Ns:]。如果还是不放心可以打印广义特征值看一眼最大特征值应该显著大于其他特征值并且随 SNR 升高而增大。这一步属于典型的代码不报错、曲线让你怀疑人生的坑排查时先怀疑方向再怀疑公式。6. 换一种信道再跑一遍相关性信道下性能排序为什么会变前面所有仿真用的都是 i.i.d. Rayleigh 信道这个模型假设各天线之间的信道完全不相关。但真实基站天线间距有限发射端往往存在相关性信道不再满足 i.i.d. 假设。要验证预编码方案的鲁棒性常见的做法是用 Kronecker 模型产生发射端相关信道H H_iid · R_t^{1/2}其中 H_iid 是原来的复高斯矩阵R_t 是发射端相关矩阵。指数相关模型生成 R_t 的方式很直观R_t[i,j] ρ^{|i-j|}ρ 是相邻天线间的相关系数取 0.6 左右就能明显改变结论。对应代码只有几行def generate_correlated_channel(Nr, Nt, rho0.6): R_t np.array([[rho ** abs(i - j) for j in range(Nt)] for i in range(Nt)]) L np.linalg.cholesky(R_t) H_iid (np.random.randn(Nr, Nt) 1j * np.random.randn(Nr, Nt)) / np.sqrt(2) H H_iid L.T # 归一化使发射端相关矩阵的迹为 Nt信道功率不随 rho 改变 return H / np.sqrt(np.trace(R_t) / Nt)把 generate_channels 换成这个函数其他代码一行都不用改。跑完后你会发现性能排序发生了三个值得注意的变化第一ZF 的高 SNR 优势被明显压缩因为相关信道导致 H_all H_all^H 的条件数变大求逆放大了噪声第二BD 的零空间虽然还存在但有效子信道的增益变小BER 曲线整体上移第三MMSE 和 SLNR 的相对位置提前了α 和泄漏项的正则化作用在这种病态信道下反而成了优势。我自己的一个习惯是做任何预编码对比前先写一个只含 MF 和 ZF 的基线脚本分别跑一遍 i.i.d. 信道和相关性信道确认两条曲线的相对位置符合理论预期——MF 低 SNR 好、ZF 高 SNR 好、相关信道下差距缩小——再往上加 BD 和 SLNR。这个习惯帮我挡掉过多次实现写错导致的翻车比任何公式验证都直接。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门