NOMA-OFDMA仿真:功率分配与SIC接收机实现及BER曲线分析
发布时间:2026/9/26 7:45:47来源:尧图网络
简介这份资源是面向无线通信学习者与科研人员的NOMA与OFDMA对比仿真工具包聚焦两种多址接入技术的性能分析与融合设计。内容涵盖OFDMA与NOMA的收发链路、编解码、功率分配及误码率评估等核心模块适合具备一定通信原理基础、希望借助MATLAB仿真验证NOMA-OFDMA融合方案的中高级读者。压缩包共13个文件以11个m脚本为主体辅以1个mat结果数据和1个zip归档整体约156KB脚本覆盖数据生成、OFDM收发、NOMA与OFDMA编解码、Turbo编译码及BER曲线绘制等环节结构紧凑便于按模块调试。目前已有381人学习下载可作为理解非正交与正交多址差异、复现误码率对比实验的参考素材帮助读者快速搭建仿真环境并观察不同接入方式在频谱效率与复杂度上的权衡。1. NOMA-OFDMA 仿真到底在算什么从一张误码率曲线说起如果你最近在调 NOMA-OFDMA 的仿真大概率遇到过这种场景功率分配因子调了半天BER 曲线要么在高信噪比段翘起来要么和理论值差了三四个数量级。这不是玄学而是 NOMA 和 OFDMA 在资源映射层面耦合之后很多默认假设不再成立。NOMA-OFDMA 仿真要解决的核心问题是在 OFDMA 的子载波网格上叠加功率域非正交多址让同一时频资源承载多个用户再通过 SIC 逐级剥离。它适合正在做多址接入对比、想验证功率分配与用户配对策略的通信方向从业者。这篇笔记按“先立模型、再跑链路、最后排错”的顺序展开所有参数都给出可改的入口你照着搭一遍就能得到自己的曲线。2. 先把 NOMA-OFDMA 的链路模型立住子载波、功率域与 SIC 怎么摆2.1 OFDMA 子载波网格与 NOMA 功率复用的叠加关系OFDMA 的本质是把带宽切成互不重叠的子载波每个用户独占若干子载波接收端靠频率正交性分离。NOMA 反过来允许两个或多个用户在同一个子载波上同时发送靠功率差异区分。把两者拼在一起常见的做法是先把用户分组组内用户共享同一组子载波组间仍按 OFDMA 分配不同子载波。这样既保留了 OFDMA 调度灵活的优点又在组内引入了 NOMA 的过载能力。仿真里最容易含糊的是资源网格的维度定义。我一般用三个维度描述子载波索引、OFDM 符号索引、用户索引。假设子载波总数 N_sc每组占用 N_sub 个子载波组内用户数 K。那么第 m 个符号上第 n 个子载波上的发送信号可以写成组内所有用户信号的叠加x[n,m] Σ_{k1}^{K} sqrt(p_k) * s_k[n,m]其中 p_k 是第 k 个用户的功率s_k 是调制符号。接收端第 k 个用户看到的是自己信号加上其他用户的干扰以及信道噪声。这个式子是后面所有代码的骨架功率分配因子怎么设、SIC 顺序怎么定都围绕它展开。2.2 功率分配因子与用户配对两个决定曲线走向的变量功率分配是 NOMA 里最敏感的参数。组内用户信道增益差异越大NOMA 相对 OFDMA 的增益越明显。常见做法是给远用户分配更大功率近用户分配更小功率这样远用户先被解码近用户靠 SIC 把远用户信号减掉。固定功率分配简单但不够稳分数功率分配更常用p_far P_total * α p_near P_total * (1 - α)α 一般取 0.6 到 0.8 之间。α 太小远用户信噪比不够SIC 第一级就出错α 太大近用户被压制整体吞吐下降。仿真时建议把 α 做成扫描变量先看 0.5 到 0.9 的 BER 变化再定工作点。用户配对策略同样关键。随机配对实现简单但增益波动大。基于信道增益排序的配对更稳把用户按增益从大到小排列最强配最弱次强配次弱。这样组内增益差最大NOMA 增益最明显。代码里用一个排序加首尾配对就能实现后面会给出具体写法。2.3 SIC 接收机的最小实现步骤SIC 是 NOMA 接收端的核心。它的逻辑是先解功率最大的用户判决后重构该用户信号从接收信号里减掉再解下一个用户。仿真里实现 SIC 要注意三点第一重构时要用估计到的信道和已知的功率分配因子第二判决错误会传播所以第一级用户的 BER 必须足够低第三减掉信号后的残余干扰要计入下一级。最小实现可以按下面这个流程走接收信号 y h * x nh 是信道n 是噪声。按功率从大到小排序用户得到解码顺序。对第一个用户直接做均衡和判决得到符号估计。用估计符号、信道和功率重构该用户信号。从 y 中减去重构信号得到剩余信号。对下一个用户重复步骤 3 到 5直到所有用户解完。这个流程在代码里就是一个循环循环次数等于组内用户数。注意每次减之前要确认重构信号和接收信号在同一个尺度上否则减完反而更糟。3. 用 Python 把 NOMA-OFDMA 链路跑通从参数表到 BER 曲线3.1 仿真参数表与初始化代码先把参数固定下来后面所有代码都引用这张表。参数不是死的你可以按自己的场景改但改完要重新检查量纲和归一化。参数含义典型值N_sc子载波总数64N_sub每组子载波数16K组内用户数2N_symOFDM 符号数1000mod_order调制阶数4QPSKalpha功率分配因子0.7SNR_dB信噪比扫描范围0 到 20channel信道类型瑞利衰落初始化代码负责生成用户位置、信道增益和功率分配。下面这段可以直接抄import numpy as np N_sc 64 N_sub 16 K 2 N_sym 1000 mod_order 4 alpha 0.7 SNR_dB np.arange(0, 21, 2) # 生成组内用户信道增益瑞利衰落 def gen_channel(K, N_sub, N_sym): h (np.random.randn(K, N_sub, N_sym) 1j * np.random.randn(K, N_sub, N_sym)) / np.sqrt(2) return h # 功率分配远用户 alpha近用户 1-alpha def power_alloc(alpha, K): p np.zeros(K) p[0] alpha p[1] 1 - alpha return p逻辑说明gen_channel 生成复高斯信道实部虚部各服从 N(0,1/2)保证总功率为 1。power_alloc 按远用户优先分配功率p[0] 对应远用户p[1] 对应近用户。参数 alpha 控制分配比例改它就能扫描功率分配对 BER 的影响。3.2 发送端QPSK 调制与子载波映射发送端要做三件事生成比特、QPSK 调制、把符号映射到子载波上。NOMA 的特点是同一子载波上叠加多个用户的符号所以映射时不是一人一份而是叠加后发送。def qpsk_mod(bits): # bits: (N_sym * log2(mod_order),) bits bits.reshape(-1, 2) symbols (1 - 2 * bits[:, 0]) 1j * (1 - 2 * bits[:, 1]) return symbols / np.sqrt(2) def tx_signal(K, N_sub, N_sym, p): tx np.zeros((N_sub, N_sym), dtypecomplex) user_bits [] for k in range(K): bits np.random.randint(0, 2, N_sub * N_sym * 2) sym qpsk_mod(bits).reshape(N_sub, N_sym) user_bits.append(bits) tx np.sqrt(p[k]) * sym return tx, user_bits逻辑说明qpsk_mod 把每两个比特映射成一个 QPSK 符号并做 1/sqrt(2) 归一化保证符号功率为 1。tx_signal 对每个用户生成独立比特流调制后乘以对应功率再叠加。返回的 tx 是叠加后的发送信号user_bits 留给接收端算 BER 用。参数 p 来自 power_alloc改 alpha 就改了 p。3.3 接收端SIC 循环与 BER 统计接收端是仿真里最容易翻车的地方。SIC 循环写错一步BER 曲线就会完全不对。下面这个实现按功率从大到小解码每解一个就减掉一个。def sic_receiver(rx, h, p, user_bits, N_sub, N_sym): K h.shape[0] order np.argsort(p)[::-1] # 功率从大到小 residual rx.copy() ber np.zeros(K) for idx in order: # 均衡 eq residual * np.conj(h[idx]) / (np.abs(h[idx])**2 1e-10) # 判决 bits_hat np.zeros(N_sub * N_sym * 2) bits_hat[0::2] (eq.real 0).astype(int).flatten() bits_hat[1::2] (eq.imag 0).astype(int).flatten() # 算 BER ber[idx] np.mean(bits_hat ! user_bits[idx]) # 重构并减掉 sym_hat qpsk_mod(bits_hat).reshape(N_sub, N_sym) residual - np.sqrt(p[idx]) * h[idx] * sym_hat return ber逻辑说明order 按功率降序排列保证先解远用户。均衡用最大比合并的简化形式分母加 1e-10 防止除零。判决把实部虚部分别与 0 比较得到比特估计。算完 BER 后用估计比特重新调制乘以功率和信道从 residual 里减掉。注意 residual 初始是接收信号每轮减一次减的顺序必须和解码顺序一致。参数说明h 是信道矩阵p 是功率向量user_bits 是发送端返回的原始比特。N_sub 和 N_sym 用来 reshape。如果你把 K 改成 3这个循环会自动多跑一轮但功率分配和排序逻辑要相应调整。3.4 主循环扫描信噪比并画出 BER 曲线把发送、信道、接收串起来外层扫 SNR内层跑蒙特卡洛。下面这段是主循环import matplotlib.pyplot as plt ber_avg np.zeros((len(SNR_dB), K)) for i, snr in enumerate(SNR_dB): noise_var 10 ** (-snr / 10) for trial in range(50): h gen_channel(K, N_sub, N_sym) p power_alloc(alpha, K) tx, user_bits tx_signal(K, N_sub, N_sym, p) rx np.zeros_like(tx) for k in range(K): rx h[k] * tx rx np.sqrt(noise_var / 2) * (np.random.randn(N_sub, N_sym) 1j * np.random.randn(N_sub, N_sym)) ber sic_receiver(rx, h, p, user_bits, N_sub, N_sym) ber_avg[i] ber ber_avg[i] / 50 plt.semilogy(SNR_dB, ber_avg[:, 0], o-, labelFar user) plt.semilogy(SNR_dB, ber_avg[:, 1], s-, labelNear user) plt.xlabel(SNR (dB)) plt.ylabel(BER) plt.legend() plt.grid(True) plt.show()逻辑说明外层扫 SNR每个 SNR 跑 50 次蒙特卡洛取平均。噪声方差按 10^(-SNR/10) 算实部虚部各分一半。接收信号是各用户信号经过信道后的叠加再加噪声。SIC 接收机返回每个用户的 BER最后画半对数曲线。远用户曲线通常下降慢近用户下降快这是 NOMA 的典型特征。参数说明trial 次数影响曲线平滑度50 次够看趋势要发论文建议 1000 次以上。noise_var 的计算假设信号功率归一化为 1如果你改了调制或功率归一化这里要同步改。4. 仿真里最容易翻车的五个地方排查与避坑4.1 现象BER 曲线在高 SNR 段不再下降反而翘起来原因SIC 重构时尺度不对减掉的信号比实际接收信号大或小残余干扰随 SNR 升高反而更明显。常见的是重构时忘了乘信道 h或者功率 p 用错。解决检查 residual - sqrt(p[idx]) * h[idx] * sym_hat 这一行确认 h 和 p 的索引与当前解码用户一致。可以在减之前打印 residual 的功率和重构信号的功率两者应该接近。4.2 现象近用户 BER 比远用户还高原因功率分配反了或者 SIC 解码顺序反了。NOMA 里远用户功率大先解近用户功率小后解。如果 order 按升序排先解近用户近用户会被远用户强干扰压死。解决确认 order np.argsort(p)[::-1] 是降序。再检查 power_alloc 里 p[0] 是不是远用户。如果用户编号和信道增益的对应关系乱了先固定用户顺序再调。4.3 现象曲线整体平移和理论值差固定 dB 数原因噪声方差计算和信号功率归一化不匹配。QPSK 归一化用了 1/sqrt(2)但噪声方差没跟着调导致等效 SNR 偏移。解决统一归一化基准。要么信号功率归一化为 1噪声方差按 10^(-SNR/10)要么都不归一化按实际功率算。改完在一个 SNR 点上手算理论 BER 对一下。4.4 现象蒙特卡洛次数够了但曲线仍然抖动原因信道生成每次独立但用户配对没固定导致组内增益差随机变化。随机配对会让 NOMA 增益时有时无。解决把用户配对改成基于信道增益排序的固定配对。每次生成信道后先排序再按首尾配对这样组内增益差稳定曲线更平滑。4.5 现象K 改成 3 之后 SIC 循环出错原因功率分配只写了两个用户第三个用户功率为 0或者排序后索引越界。解决power_alloc 要支持任意 K。常见做法是按信道增益排序后用分数功率分配公式给每个用户分功率而不是硬编码两个值。K 变大时SIC 循环本身不用改但每级减完后的残余干扰会累积BER 会变差这是正常的。5. 把 NOMA-OFDMA 仿真用起来从对比 OFDMA 到扫参数找边界5.1 加一条 OFDMA 基线看 NOMA 到底赢在哪光看 NOMA 的 BER 不够要证明它有价值得和 OFDMA 对比。OFDMA 里每个用户独占子载波没有组内干扰接收端不用 SIC。改法很简单把 tx_signal 里的叠加去掉每个用户只在自己的子载波上发。接收端直接均衡判决不跑 SIC 循环。对比时看两个指标一是相同总功率下NOMA 的和速率是否高于 OFDMA二是远用户的 BER 是否可接受。NOMA 的增益主要来自远用户近用户可能比 OFDMA 差这是功率分配的代价。我一般把 alpha 从 0.5 扫到 0.9画一条和速率曲线找峰值点。5.2 扫功率分配因子和用户数找工作边界仿真最大的价值是找边界。固定 SNR10dB扫 alpha 从 0.5 到 0.9步长 0.05看远用户和近用户的 BER 交叉点。交叉点对应的 alpha 就是公平性最好的点。再固定 alpha扫 K 从 2 到 4看和速率什么时候不再增长。K 太大时SIC 残余干扰累积和速率反而下降这个转折点就是组内用户数的上限。扫参数时记得把蒙特卡洛次数调大否则曲线毛刺太多看不出趋势。我一般扫参数用 200 次画最终曲线用 1000 次。5.3 一个容易忽略的技巧信道估计误差的影响前面所有仿真都假设接收端知道完美信道。实际系统里信道估计有误差SIC 重构时用的 h 不完美残余干扰会更大。加误差的方法是在接收端把 h 换成 h_hat h ee 是估计误差方差设为信道方差的某个比例。跑一遍会发现近用户对误差更敏感因为它在 SIC 后面前面减不干净全落到它头上。这个技巧能帮你判断方案鲁棒性。如果误差 5% 就崩说明功率分配太激进要留余量。如果误差 20% 还能用说明方案有实用价值。5.4 我踩过的坑和现在的习惯最早做 NOMA-OFDMA 仿真时我直接把 NOMA 的代码和 OFDMA 的代码拼在一起结果子载波映射对不上BER 曲线像噪声。后来养成习惯先单独跑通 OFDMA确认 BER 和理论 QPSK 一致再单独跑通 NOMA 单载波确认 SIC 逻辑对最后才拼成 NOMA-OFDMA。每次改参数只改一个改完立刻和上一版对比。这样出问题能快速定位是哪一层引入的。另外仿真代码一定要版本管理。功率分配、SIC 顺序、噪声归一化这三处最容易改乱每次改动记一笔不然过两天自己都忘了当时为什么那么写。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网