从数学公式到代码实现:用Python仿真大规模MIMO预编码的完整流程
如果你是一位通信算法工程师或者相关领域的研究者,大概率已经对大规模MIMO(Massive MIMO)的理论框架耳熟能详。教科书和论文里充斥着各种优美的矩阵公式,从迫零(ZF)到块对角化(BD),原理推导看起来清晰明了。然而,当我真正尝试将纸面上的 W = H^H (H H^H)^{-1} 转化为可运行、可验证的代码时,才发现中间隔着一道巨大的鸿沟。奇异值分解(SVD)中一个维度的疏忽,功率归一化时一个范数选择的差异,都可能导致仿真结果与理论预期大相径庭,更不用说和MATLAB这类传统仿真工具的结果进行交叉验证了。
这篇文章正是为了弥合这道鸿沟而写。我不会重复教科书上的基础定义,而是假设你已经理解了预编码的核心目标是利用信道状态信息(CSIT)在发送端预处理信号,以消除多用户干扰。我们将直接切入实战,聚焦于如何用Python和NumPy,一步步、稳健地将那些线性代数运算落地为代码。我会分享在实现过程中踩过的坑,比如信道矩阵 H 的维度究竟应该是 用户天线数 × 基站天线数 还是反过来,SVD分解后到底该取 V 的哪些列,以及如何实现工程上至关重要的功率约束。最终,你将获得一套模块化、可复现的代码,不仅能算出预编码矩阵,还能计算和可视化频谱效率等关键性能指标,并与经典结果进行比对。
1. 仿真环境搭建与信道建模
在动手写任何算法之前,一个稳定、可复现的仿真环境是基石。我强烈推荐使用 Jupyter Notebook 或 Jupyter Lab 进行这类探索性编程和算法验证。它的交互式特性允许你逐个单元格执行代码,实时检查中间变量的形状和数值,这对于调试复杂的矩阵运算至关重要。
首先,我们需要导入核心的科学计算库。别小看这个步骤,版本的一致性有时会带来意想不到的结果。
import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg
import warnings
warnings.filterwarnings('ignore') # 暂时忽略一些不影响运行的警告
print(f"NumPy版本: {np.__version__}")
接下来是信道建模。大规模MIMO的核心特征之一是基站天线数 M 远大于单用户天线数 N_k,并且用户数 K 也较多。我们假设一个典型的单小区下行链路场景:
def generate_mu_mimo_channel(M, K, N_k, correlation='uncorrelated', seed=None):
"""
生成多用户MIMO下行链路信道矩阵。
参数:
M: 基站发射天线数
K: 用户数
N_k: 每个用户的接收天线数(假设所有用户相同)
correlation: 信道相关性模型,'uncorrelated' 或 'rayleigh'
seed: 随机种子,用于结果复现
返回:
H_list: 长度为K的列表,每个元素是第k个用户的信道矩阵 H_k,形状为 (N_k, M)
H: 聚合信道矩阵,形状为 (sum(N_k), M),即 (K*N_k, M)
"""
if seed is not None:
np.random.seed(seed)
H_list = []
for k in range(K):
# 每个用户的信道矩阵:瑞利衰落,复高斯随机变量
# 注意维度:H_k 是 N_k x M,表示从M根发射天线到用户k的N_k根接收天线的信道
if correlation == 'uncorrelated':
# 独立同分布瑞利衰落
H_k = (np.random.randn(N_k, M) + 1j * np.random.randn(N_k, M)) / np.sqrt(2)
else:
# 此处可扩展为更复杂的相关信道模型,如Kronecker模型
H_k = (np.random.randn(N_k, M) + 1j * np.random.randn(N_k, M)) / np.sqrt(2)
H_list.append(H_k)
# 垂直堆叠所有用户的信道矩阵,形成聚合信道矩阵 H
H = np.vstack(H_list) # 形状: (K*N_k, M)
return H_list, H
注意:信道矩阵
H_k的维度是(N_k, M)。这是一个关键点,它决定了后续所有矩阵运算的维度一致性。许多初学者容易在这里弄反,导致后续的伪逆或SVD无法计算。
为了验证我们的信道生成是否正确,可以快速检查一下矩阵的秩和条件数:
M = 64 # 基站天线数
K = 8 # 用户数
N_k = 2 # 每个用户天线数
H_list, H = generate_mu_mimo_channel(M, K, N_k, seed=42)
print(f"聚合信道矩阵 H 的形状: {H.shape}")
print(f"矩阵 H 的秩: {np.linalg.matrix_rank(H)}")
print(f"矩阵 H 的条件数: {np.linalg.cond(H):.2e}")
在理想独立衰落下,当 M > K*N_k 时,矩阵 H 通常是列满秩或行满秩的,这取决于天线数量的对比。这个属性直接影响ZF等基于求逆的预编码算法的稳定性。
2. 线性预编码算法的核心实现
理论部分我们快速回顾一下目标:对于聚合信道 H(维度 (K*N_k, M))和待发送的数据符号向量 s(维度 (K*N_k, 1)),我们要找到一个预编码矩阵 W(维度 (M, K*N_k)),使得发送信号 x = W s 满足功率约束 E[||x||^2] = P,并且能有效压制用户间干扰。
2.1 迫零(ZF)预编码:陷阱与实现
ZF预编码的数学公式简洁优美:W_zf = H^H (H H^H)^{-1}。但在代码实现时,有以下几个工程细节必须处理:
- 伪逆的计算:当
H不是方阵时,我们使用Moore-Penrose伪逆。NumPy的np.linalg.pinv函数基于SVD,数值上更稳定,但理解其与直接公式计算的关系很重要。 - 功率归一化:这是保证仿真符合实际功率约束的关键一步,却常常在示例代码中被忽略。归一化必须在预编码矩阵计算完成后进行。
- 维度验证:每一步运算后,都应用
assert语句检查矩阵维度,这是避免隐蔽错误的最佳实践。
下面是一个加入了详细检查和注释的ZF预编码实现:
def zero_forcing_precoding(H, P_tx=1.0, method='pinv'):
"""
计算迫零预编码矩阵。
参数:
H: 聚合信道矩阵,形状 (N_total, M), N_total = sum(N_k)
P_tx: 基站总发射功率
method: 求逆方法,'pinv' 使用伪逆(更稳定),'direct' 使用直接公式计算
返回:
W_zf: 迫零预编码矩阵,形状 (M, N_total)
"""
N_total, M = H.shape
# 确保基站天线数不少于总数据流数,否则信道求逆会严重放大噪声
if M < N_total:
print(f"警告: 基站天线数 M({M}) < 总数据流数 N_total({N_total}),ZF性能将严重恶化。")
if method == 'pinv':
# 使用伪逆:W = H^H * (H H^H)^(-1) 等价于 H 的右伪逆
# numpy.linalg.pinv 计算的是 Moore-Penrose 伪逆,对于行满秩矩阵,pinv(H) = H^H (H H^H)^{-1}
W = np.linalg.pinv(H) # 形状 (M, N_total)
elif method == 'direct':
# 直接公式计算,适用于 H 行满秩的情况
H_H = H.conj().T # 共轭转置
H_HH = H @ H_H # 形状 (N_total, N_total)
# 添加一个极小正则项防止矩阵奇异,提升数值稳定性
epsilon = 1e-10
W = H_H @ np.linalg.inv(H_HH + epsilon * np.eye(N_total))
else:
raise ValueError("method 参数必须是 'pinv' 或 'direct'")
# 功率归一化:确保发送信号满足总功率约束 E[||x||^2] = P_tx
# 假设数据符号 s 是单位功率的,即 E[s^H s] = N_total
# 那么发送功率 = trace(W^H W) * (每个数据流的功率)
# 更常见的工程做法是进行缩放,使得 ||W||_F^2 = P_tx
scale_factor = np.sqrt(P_tx / np.trace(W.conj().T @ W))
W_zf = scale_factor * W
# 维度验证
assert W_zf.shape == (M, N_total), f"预编码矩阵形状错误: {W_zf.shape}"
return W_zf
提示:在实际仿真中,我通常优先使用
method='pinv'。np.linalg.pinv内部使用SVD并自动处理秩亏情况,数值鲁棒性远高于直接求逆。虽然计算量稍大,但对于天线数在百位量级的大规模MIMO仿真,仍在可接受范围内。
让我们测试一下,并观察等效信道 H_eq = H @ W_zf 是否接近单位阵(即实现了“迫零”):
# 生成信道
H_list, H = generate_mu_mimo_channel(M=32, K=4, N_k=2, seed=123)
N_total = H.shape[0]
# 计算ZF预编码矩阵
W_zf = zero_forcing_precoding(H, P_tx=1.0, method='pinv')
# 计算等效信道
H_eq = H @ W_zf # 理想情况下应为近似单位阵
# 检查等效信道的对角优势和非对角元素(干扰)大小
print("等效信道矩阵 H_eq 的形状:", H_eq.shape)
print("H_eq 对角线元素的平均幅度:", np.mean(np.abs(np.diag(H_eq))))
print("H_eq 非对角线元素的平均幅度:", np.mean(np.abs(H_eq - np.diag(np.diag(H_eq)))))
如果实现正确,你会看到对角线元素的幅度接近一个常数(由功率归一化决定),而非对角线元素的幅度远小于对角线元素,这表明用户间干扰被有效压制。
2.2 正则化迫零(RZF)或MMSE预编码
ZF预编码在低信噪比或信道条件数大时,会过度放大噪声。正则化迫零通过引入一个正则化因子 alpha(或 beta)来平衡干扰消除和噪声增强。其公式为 W_rzf = H^H (H H^H + alpha * I)^{-1}。
这里的 alpha 选择是一门艺术,通常与噪声功率 sigma_n^2 和发射功率 P_tx 有关。一个常见的启发式设置是 alpha = N_total * sigma_n^2 / P_tx。
def regularized_ze_precoding(H, P_tx=1.0, snr_db=20):
"""
计算正则化迫零(RZF)预编码矩阵,也称为MMSE预编码。
参数:
H: 聚合信道矩阵
P_tx: 发射总功率
snr_db: 信噪比(dB),用于计算噪声功率,从而确定正则化因子
返回:
W_rzf: RZF预编码矩阵
"""
N_total, M = H.shape
H_H = H.conj().T
# 将SNR转换为线性值,并计算噪声功率sigma_n^2
# 假设接收信号功率归一化为1,则噪声功率 sigma_n^2 = 10^(-snr_db/10)
sigma_n_2 = 10 ** (-snr_db / 10.0)
# 计算正则化因子 alpha
# 一种常见选择:alpha = N_total * sigma_n^2 / P_tx
alpha = N_total * sigma_n_2 / P_tx
# 计算预编码矩阵
H_HH = H @ H_H
# 添加正则化项
G = H_HH + alpha * np.eye(N_total)
W = H_H @ np.linalg.inv(G)
# 功率归一化
scale_factor = np.sqrt(P_tx / np.trace(W.conj().T @ W))
W_rzf = scale_factor * W
return W_rzf
为了直观对比ZF和RZF的性能差异,我们可以计算在不同SNR下的系统和速率(Sum Rate)。这需要模拟发送随机数据符号,并通过带有噪声的信道。
def calculate_sum_rate(H, W, snr_db):
"""
计算给定预编码矩阵下的香农和速率。
参数:
H: 信道矩阵
W: 预编码矩阵
snr_db: 信噪比(dB)
返回:
sum_rate: 和速率(bps/Hz)
"""
N_total, M = H.shape
# 计算等效信道和噪声增强后的等效噪声协方差
H_eq = H @ W # 形状 (N_total, N_total)
# 假设每个数据流独立,功率为1,总发射功率已包含在W的归一化中
# 接收端每个流上的干扰加噪声功率
sigma_n_2 = 10 ** (-snr_db / 10.0)
sum_rate = 0
for k in range(N_total):
# 第k个流的期望信号功率
signal_power = np.abs(H_eq[k, k]) ** 2
# 第k个流受到的来自其他流的干扰功率
interference_power = np.sum(np.abs(H_eq[k, :]) ** 2) - signal_power
# 计算该流的SINR
sinr_k = signal_power / (interference_power + sigma_n_2)
# 累加该流的速率
sum_rate += np.log2(1 + sinr_k)
return sum_rate
2.3 块对角化(BD)预编码:多用户干扰的零空间方法
BD预编码适用于每个用户有多根天线(N_k > 1)的场景,其目标不仅消除用户间干扰(Inter-User Interference, IUI),还要消除用户内各数据流间的干扰。核心思想是为每个用户计算其他所有用户信道张成空间的零空间。
实现BD预编码分为两步:
- 计算干扰消除矩阵:对于用户k,找到其信道
H_k在其他用户联合信道H_tilde_k的零空间中的投影矩阵W_k_a。这通过计算H_tilde_k的SVD并取其右奇异向量矩阵中对应零奇异值的部分(即零空间基)来完成。 - 计算用户内预编码:在消除了用户间干扰的等效信道
H_k_eff = H_k @ W_k_a上,再进行一次单用户MIMO的预编码(如SVD预编码),得到W_k_b。最终预编码矩阵为W_k = W_k_a @ W_k_b。
实现细节颇多,下面是关键部分的代码:
def block_diagonalization_precoding(H_list, P_tx=1.0):
"""
计算块对角化预编码矩阵。
参数:
H_list: 列表,包含K个用户的信道矩阵 H_k,每个形状为 (N_k, M)
P_tx: 总发射功率
返回:
W_bd: 块对角化预编码矩阵,形状 (M, N_total)
"""
K = len(H_list)
M = H_list[0].shape[1] # 基站天线数
N_k_list = [H_k.shape[0] for H_k in H_list]
N_total = sum(N_k_list)
W_list = [] # 存储每个用户的预编码矩阵 W_k
for k in range(K):
# 步骤1: 构造其他所有用户的联合信道矩阵 H_tilde_k
H_tilde_k_list = [H_list[j] for j in range(K) if j != k]
if H_tilde_k_list:
H_tilde_k = np.vstack(H_tilde_k_list) # 形状 (N_total - N_k, M)
else:
# 如果只有一个用户,则不存在用户间干扰,直接进行单用户预编码
H_tilde_k = np.array([]).reshape(0, M)
# 计算 H_tilde_k 的零空间
if H_tilde_k.size > 0:
# 对 H_tilde_k 进行SVD: H_tilde_k = U_t S_t V_t^H
U_t, S_t, Vh_t = np.linalg.svd(H_tilde_k, full_matrices=True)
# V_t 是 Vh_t 的共轭转置
V_t = Vh_t.conj().T
# 零空间由 V_t 中对应奇异值为0的列向量张成
# 由于数值计算,奇异值可能非常小但不精确为0,我们设置一个阈值
rank_t = np.sum(S_t > 1e-10)
if rank_t < V_t.shape[1]:
V_t_null = V_t[:, rank_t:] # 零空间基,形状 (M, M - rank_t)
else:
# 如果没有零空间(理论上不应发生,因为M > N_total - N_k是BD可行的条件)
raise ValueError(f"用户 {k} 的干扰信道矩阵满秩,无法找到零空间。")
else:
# 没有其他用户,零空间是整个 C^M 空间
V_t_null = np.eye(M)
# 干扰消除矩阵 W_k_a 就是零空间基
W_k_a = V_t_null # 形状 (M, M - rank_t)
# 步骤2: 计算用户k在零空间上的等效信道
H_k_eff = H_list[k] @ W_k_a # 形状 (N_k, M - rank_t)
# 对等效信道进行SVD预编码(单用户)
U_k, S_k, Vh_k = np.linalg.svd(H_k_eff, full_matrices=False)
V_k = Vh_k.conj().T
# 取前 N_k 个右奇异向量(对应主要信道方向)
# 注意:这里假设 N_k <= M - rank_t,即等效信道有足够自由度支持所有数据流
W_k_b = V_k[:, :N_k_list[k]] # 形状 (M - rank_t, N_k)
# 用户k的最终预编码矩阵
W_k = W_k_a @ W_k_b # 形状 (M, N_k)
W_list.append(W_k)
# 将所有用户的预编码矩阵水平拼接
W_bd = np.hstack(W_list) # 形状 (M, N_total)
# 功率归一化
scale_factor = np.sqrt(P_tx / np.trace(W_bd.conj().T @ W_bd))
W_bd = scale_factor * W_bd
return W_bd
BD预编码的计算复杂度明显高于ZF,因为它需要对每个用户进行SVD分解。但其优势在于能更好地处理用户内流间干扰,尤其在用户天线数较多时。
3. 性能评估与MATLAB交叉验证
算法实现后,我们必须验证其正确性。一个强有力的方法是与业界公认的仿真工具(如MATLAB)的结果进行交叉验证。虽然我们不能直接运行MATLAB代码,但可以遵循一致的仿真假设,并对比关键指标。
验证策略:
- 固定随机种子:在Python和MATLAB中使用相同的随机种子生成信道矩阵
H,确保输入一致。 - 对比预编码矩阵本身:计算两种语言得到的预编码矩阵
W的Frobenius范数差异:diff = norm(W_python - W_matlab, 'fro') / norm(W_matlab, 'fro')。这个相对误差应该非常小(例如< 1e-10)。 - 对比系统性能指标:计算和速率、误码率(BER)等指标。由于这些指标是标量,更容易比对。
为了模拟这个过程,我们可以用Python生成一个信道,用我们的函数计算预编码矩阵,然后“假设”我们从MATLAB得到了一个结果文件,并进行比对。在实际工作中,你可能需要将Python生成的信道数据保存为 .mat 文件供MATLAB读取,或者反之。
def validate_with_matlab_simulation():
"""
模拟与MATLAB结果的交叉验证流程。
在实际操作中,需要将数据在Python和MATLAB间导入导出。
"""
# 使用固定种子生成可复现的信道
seed = 2024
M, K, N_k = 32, 4, 2
H_list, H = generate_mu_mimo_channel(M, K, N_k, seed=seed)
# Python计算
W_zf_py = zero_forcing_precoding(H, P_tx=1.0, method='pinv')
W_bd_py = block_diagonalization_precoding(H_list, P_tx=1.0)
# 假设我们从MATLAB加载了结果(这里用随机数模拟,实际应从文件加载)
np.random.seed(seed+1) # 使用不同的种子模拟“MATLAB计算”
# 注意:这只是模拟!真实情况下应加载实际MATLAB计算结果
W_zf_ml_simulated = zero_forcing_precoding(H, P_tx=1.0, method='direct') # 模拟MATLAB可能用直接求逆
W_bd_ml_simulated = block_diagonalization_precoding(H_list, P_tx=1.0) # 模拟MATLAB的BD实现
# 计算差异
diff_zf = np.linalg.norm(W_zf_py - W_zf_ml_simulated, 'fro') / np.linalg.norm(W_zf_ml_simulated, 'fro')
diff_bd = np.linalg.norm(W_bd_py - W_bd_ml_simulated, 'fro') / np.linalg.norm(W_bd_ml_simulated, 'fro')
print(f"模拟验证结果(相对Frobenius范数误差):")
print(f" ZF预编码矩阵差异: {diff_zf:.2e}")
print(f" BD预编码矩阵差异: {diff_bd:.2e}")
print("\n注意:以上是模拟流程。真实交叉验证需确保两端算法、归一化方式完全一致。")
# 更实际的验证:比较性能指标,如不同SNR下的和速率曲线
snr_range_db = np.arange(0, 31, 5)
sum_rate_zf_py = []
sum_rate_bd_py = []
for snr_db in snr_range_db:
# 为公平比较,每次重新计算预编码矩阵(因为RZF的alpha与SNR有关)
W_zf = zero_forcing_precoding(H, P_tx=1.0)
W_rzf = regularized_ze_precoding(H, P_tx=1.0, snr_db=snr_db)
W_bd = block_diagonalization_precoding(H_list, P_tx=1.0)
sum_rate_zf_py.append(calculate_sum_rate(H, W_zf, snr_db))
# 使用RZF作为对比
sum_rate_rzf_py.append(calculate_sum_rate(H, W_rzf, snr_db))
sum_rate_bd_py.append(calculate_sum_rate(H, W_bd, snr_db))
# 绘制性能曲线
plt.figure(figsize=(10, 6))
plt.plot(snr_range_db, sum_rate_zf_py, 'o-', label='ZF (Python)')
plt.plot(snr_range_db, sum_rate_rzf_py, 's-', label='RZF/MMSE (Python)')
plt.plot(snr_range_db, sum_rate_bd_py, '^-', label='BD (Python)')
plt.xlabel('SNR (dB)')
plt.ylabel('Sum Rate (bps/Hz)')
plt.title('大规模MIMO下行链路预编码算法性能比较')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.show()
# 运行验证
validate_with_matlab_simulation()
通过绘制和速率随SNR变化的曲线,我们可以直观地看到不同预编码算法的性能趋势:ZF在低SNR时性能较差,RZF在整个SNR范围内更稳健,而BD在用户天线数较多时能提供额外的性能增益。
4. 工程实践:模块化、可视化与性能分析
将代码模块化并封装成类,可以极大提升仿真项目的可维护性和复用性。下面展示一个预编码仿真器的简单类结构,并介绍如何添加关键的可视化功能。
class MassiveMIMOPrecodingSimulator:
"""
大规模MIMO预编码仿真器类。
"""
def __init__(self, M, K, N_k, P_tx=1.0, channel_seed=None):
self.M = M # 基站天线数
self.K = K # 用户数
self.N_k = N_k # 每用户天线数(假设相同)
self.N_total = K * N_k
self.P_tx = P_tx
self.channel_seed = channel_seed
# 生成信道
self.H_list, self.H = generate_mu_mimo_channel(M, K, N_k, seed=channel_seed)
self.W = None # 当前预编码矩阵
self.current_method = None
def compute_precoding(self, method='zf', **kwargs):
"""计算指定方法的预编码矩阵。"""
if method == 'zf':
self.W = zero_forcing_precoding(self.H, self.P_tx, **kwargs)
self.current_method = 'ZF'
elif method == 'rzf' or method == 'mmse':
snr_db = kwargs.get('snr_db', 20)
self.W = regularized_ze_precoding(self.H, self.P_tx, snr_db)
self.current_method = 'RZF/MMSE'
elif method == 'bd':
self.W = block_diagonalization_precoding(self.H_list, self.P_tx)
self.current_method = 'BD'
else:
raise ValueError(f"不支持的预编码方法: {method}")
return self.W
def plot_equivalent_channel(self):
"""绘制等效信道矩阵 H_eq = H @ W 的幅度热图。"""
if self.W is None:
raise ValueError("请先计算预编码矩阵。")
H_eq = self.H @ self.W
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
im0 = axes[0].imshow(np.abs(H_eq), cmap='hot', interpolation='nearest')
axes[0].set_title(f'等效信道幅度 |H_eq| ({self.current_method})')
axes[0].set_xlabel('数据流索引')
axes[0].set_ylabel('接收天线/用户索引')
plt.colorbar(im0, ax=axes[0])
# 突出显示对角线元素(期望信号)
diag_mask = np.eye(H_eq.shape[0], dtype=bool)
off_diag_mask = ~diag_mask
axes[1].hist(np.abs(H_eq[off_diag_mask]).flatten(), bins=50, alpha=0.7, label='非对角 (干扰)')
axes[1].hist(np.abs(H_eq[diag_mask]).flatten(), bins=50, alpha=0.7, label='对角 (信号)')
axes[1].set_xlabel('幅度')
axes[1].set_ylabel('频数')
axes[1].set_title('等效信道元素幅度分布')
axes[1].legend()
axes[1].grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
def evaluate_sum_rate_vs_snr(self, snr_range_db=np.arange(-5, 31, 2)):
"""评估和速率随SNR的变化。"""
sum_rates = []
for snr_db in snr_range_db:
# 对于RZF,需要根据当前SNR重新计算预编码矩阵
if self.current_method == 'RZF/MMSE':
W_temp = regularized_ze_precoding(self.H, self.P_tx, snr_db)
else:
W_temp = self.W # ZF和BD的W与SNR无关(理想功率分配下)
rate = calculate_sum_rate(self.H, W_temp, snr_db)
sum_rates.append(rate)
plt.figure(figsize=(8, 5))
plt.plot(snr_range_db, sum_rates, 'b-o', linewidth=2, markersize=6)
plt.xlabel('SNR (dB)')
plt.ylabel('Sum Rate (bps/Hz)')
plt.title(f'和速率 vs SNR ({self.current_method}预编码, M={self.M}, K={self.K})')
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
return snr_range_db, sum_rates
使用这个类,我们可以方便地进行一系列实验:
# 初始化仿真器
sim = MassiveMIMOPrecodingSimulator(M=64, K=8, N_k=2, channel_seed=42)
# 计算并可视化ZF预编码
sim.compute_precoding('zf')
sim.plot_equivalent_channel()
snr_range, rates_zf = sim.evaluate_sum_rate_vs_snr()
# 计算并可视化BD预编码
sim.compute_precoding('bd')
sim.plot_equivalent_channel()
snr_range, rates_bd = sim.evaluate_sum_rate_vs_snr()
从等效信道热图中,你可以清晰地看到BD预编码产生的块对角化结构——每个用户对应的子块内部有值,而用户间的非对角块元素幅度接近零,这直观地展示了用户间干扰的消除。
在实现和测试过程中,有几个坑值得特别注意。首先是数值稳定性,大规模MIMO中矩阵条件数可能很大,直接求逆 np.linalg.inv 容易产生数值误差,优先使用 np.linalg.pinv 或添加微小正则项。其次是功率归一化的具体形式,不同的文献可能采用不同的归一化准则(如每天线功率约束、总功率约束、每流功率约束),务必明确你的仿真假设并与对比对象保持一致。最后是信道模型的假设,本文使用的是最简单的i.i.d.瑞利衰落,实际中可能需要考虑空间相关性、路径损耗和阴影衰落,这些都会显著影响预编码算法的性能排序。
将这些代码片段整合到你的Jupyter Notebook中,你就拥有了一套从信道生成、算法实现、性能评估到结果可视化的完整大规模MIMO预编码仿真工具链。

509

被折叠的 条评论
为什么被折叠?



