ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NOMA-OFDMA仿真对比:资源分配、SIC接收与功率优化实战

NOMA-OFDMA仿真对比:资源分配、SIC接收与功率优化实战 简介这份资源是面向无线通信学习者与科研人员的NOMA-OFDMA仿真代码包聚焦非正交多址接入与正交频分多址两种多址技术的对比与融合验证。内容围绕功率域复用、子载波分配、用户配对、干扰管理及功率分配策略等关键问题展开适合具备一定MATLAB基础、正在研究5G多址接入或准备相关课程设计、毕业课题的中高级读者。压缩包共13个文件约156KB以m脚本为主体辅以mat结果数据与嵌套zip涵盖OFDM收发、NOMA与OFDMA编解码、Turbo编译码、数据生成及BER性能绘图等模块可完整跑通链路级误码率仿真。目前已有381人学习下载。借助这套脚本读者能快速搭建对比仿真环境观察不同信噪比与用户密度下的性能差异并在此基础上调整功率分配与用户配对方案为NOMA-OFDMA融合设计的理论分析与实验验证提供可复用的代码基础。1. NOMA-OFDMA 仿真到底在算什么从一次资源分配翻车说起做无线通信仿真的同行大概率都遇到过这种场景明明按论文里的公式把功率分配系数算好了跑出来的和速率却比 OFDMA 还低甚至边缘用户的误码率高得离谱。这不是代码写错了而是 NOMA 和 OFDMA 的资源维度根本不在一个层面上——OFDMA 在频域给每个用户切一块互不重叠的子载波NOMA 却在同一块时频资源上叠加多个用户的信号靠功率域区分。把两者放进同一个仿真框架里对比核心要回答的问题就三个同样的总功率和带宽下谁的和速率更高、谁的用户公平性更好、谁的接收端复杂度更可控。NOMA-OFDMA 仿真就是干这件事的在 OFDMA 的子载波分配骨架上叠加 NOMA 的功率复用层用蒙特卡洛方式跑出不同信噪比下的性能曲线。适合正在做资源分配算法验证、准备写论文对比章节、或者要评估 SIC 接收机实际开销的工程师。下面从信道建模一路讲到串行干扰消除的排序坑每一步都给可复现的参数和代码。2. 先把信道和资源网格搭对OFDMA 子载波映射与 NOMA 功率叠加的实现细节2.1 为什么 OFDMA 和 NOMA 不能共用同一套信道系数矩阵OFDMA 的资源分配逻辑是每个子载波同一时刻只分配给一个用户信道系数矩阵按用户-子载波对独立生成。NOMA 则要求同一子载波上叠加多个用户的信号接收端看到的是多个用户信道系数的加权和。如果直接复用 OFDMA 的信道矩阵去跑 NOMA会出现功率分配系数和信道增益不匹配的问题——强用户的信号在接收端淹没弱用户SIC 第一层就解错了。常见做法是分别生成两套信道矩阵OFDMA 用H_ofdma[user][subcarrier]NOMA 用H_noma[user][subcarrier]但底层的小尺度衰落模型保持一致比如都用瑞利衰落这样对比才公平。大尺度路径损耗按用户到基站的距离单独计算保证边缘用户和中心用户的信噪比差异真实存在。import numpy as np def generate_channel(num_users, num_subcarriers, user_distances, path_loss_exp3.5): 生成 OFDMA 和 NOMA 共用的底层信道系数 num_users: 用户数 num_subcarriers: 子载波数 user_distances: 每个用户到基站的距离米 path_loss_exp: 路径损耗指数典型值 3.0~4.0 # 大尺度路径损耗参考距离 1 米处的路损设为 30dB path_loss_db 30 10 * path_loss_exp * np.log10(np.maximum(user_distances, 1)) path_loss_linear 10 ** (-path_loss_db / 10) # 小尺度瑞利衰落每个用户-子载波对独立 rayleigh (np.random.randn(num_users, num_subcarriers) 1j * np.random.randn(num_users, num_subcarriers)) / np.sqrt(2) # 信道系数 大尺度衰落 × 小尺度衰落 H rayleigh * np.sqrt(path_loss_linear)[:, np.newaxis] return H这段代码的关键参数是path_loss_exp室内场景取 2.0~3.0室外宏蜂窝取 3.5~4.0。user_distances决定了用户间的信噪比差距如果所有用户距离一样NOMA 的功率域复用就失去意义了。生成的H矩阵同时用于 OFDMA 和 NOMA 的后续计算保证对比基准一致。2.2 子载波分配OFDMA 的贪心策略和 NOMA 的分组逻辑OFDMA 的子载波分配目标是最大化总速率常用贪心算法每个子载波分配给信道增益最大的用户。NOMA 则先把用户分组每组内用户共享同一组子载波组内用功率域区分。分组策略直接影响性能——把信道条件差异大的用户分到一组SIC 更容易成功把信道条件相近的用户分到一组功率分配系数会非常接近接收端很难区分。def ofdma_subcarrier_allocation(H, num_subcarriers, num_users): OFDMA 贪心分配每个子载波给信道增益最大的用户 allocation np.zeros((num_users, num_subcarriers), dtypeint) for sc in range(num_subcarriers): best_user np.argmax(np.abs(H[:, sc]) ** 2) allocation[best_user, sc] 1 return allocation def noma_user_grouping(H, num_groups): NOMA 用户分组按信道增益排序后首尾配对 user_gains np.mean(np.abs(H) ** 2, axis1) sorted_idx np.argsort(user_gains) groups [] for i in range(num_groups): # 最强配最弱保证组内信道差异 groups.append([sorted_idx[i], sorted_idx[-(i1)]]) return groupsofdma_subcarrier_allocation返回的是 0/1 矩阵每列只有一个 1。noma_user_grouping采用首尾配对这是最常见也最容易复现的策略。如果用户数是奇数中间那个用户单独成组或者归入已有组具体看你的仿真设定。分组数num_groups一般取num_users // 2组数越多每组占用的子载波越少但组内用户数也越少。2.3 功率分配NOMA 的分数阶功率分配怎么设才不翻车NOMA 的功率分配直接决定 SIC 能否按顺序解出。常用分数阶功率分配FTPA信道增益越弱的用户分到越多功率。公式是P_i P_total * (|h_i|^2)^(-alpha) / sum(...)其中alpha是衰减因子取 0 到 1 之间。alpha0退化成等功率分配alpha1把功率全给最弱用户强用户直接断连。def ftpa_power_allocation(H_group, P_total, alpha0.5): 分数阶功率分配 H_group: 组内用户的信道系数形状 (num_users_in_group, num_subcarriers) P_total: 组内总功率 alpha: 衰减因子典型值 0.3~0.7 user_gains np.mean(np.abs(H_group) ** 2, axis1) weights user_gains ** (-alpha) power_coeff weights / np.sum(weights) return P_total * power_coeffalpha的取值是血泪经验0.3 以下强用户速率上不去0.7 以上弱用户虽然速率高了但强用户容易中断。我一般先在 0.4~0.6 之间扫一遍看和速率曲线的峰值位置再定。另外注意功率分配系数要在组内归一化不同组之间功率独立分配总功率约束是各组之和不超过基站总功率。3. 接收端 SIC 实现排序错了整个仿真白跑3.1 SIC 解码顺序为什么必须按信道增益降序串行干扰消除的核心逻辑是先解信道增益最强的用户因为它的信号在接收端功率最大最容易解对。解出来之后重构它的信号并从接收信号中减去再解下一个。如果顺序反了先解弱用户强用户的信号就是干扰信干噪比极低第一层就解错后面全部崩盘。def sic_receiver(received_signal, H_group, power_coeff, noise_var): SIC 接收机 received_signal: 接收信号形状 (num_subcarriers,) H_group: 组内用户信道形状 (num_users, num_subcarriers) power_coeff: 功率分配系数形状 (num_users,) noise_var: 噪声方差 返回: 每个用户的解码符号和 SINR num_users H_group.shape[0] user_gains np.mean(np.abs(H_group) ** 2, axis1) # 按信道增益降序排列 decode_order np.argsort(user_gains)[::-1] decoded {} sinr {} residual received_signal.copy() for idx in decode_order: h H_group[idx] p power_coeff[idx] # 当前用户的信号功率 signal_power p * np.abs(h) ** 2 # 剩余未解码用户的干扰功率 interference 0 for j in decode_order: if j idx: break interference power_coeff[j] * np.abs(H_group[j]) ** 2 sinr[idx] signal_power / (interference noise_var) # 理想 SIC直接重构并减去 decoded[idx] residual / (np.sqrt(p) * h 1e-12) residual residual - np.sqrt(p) * h * decoded[idx] return decoded, sinrdecode_order用argsort降序排列这是 SIC 能工作的前提。interference只累加尚未解码的用户已解码的用户信号已经被减掉了。1e-12是防止除零的微小量。实际仿真中还要考虑 SIC 的误差传播——如果第一层解码有误残余干扰会累积到下一层。可以在decoded后面加一个判决反馈或者直接引入解码错误概率模型。3.2 和速率计算别把 SINR 直接当速率用和速率的计算公式是sum(log2(1 SINR))但要注意 SINR 是线性值不是 dB。另外OFDMA 的速率计算是每个子载波独立算再求和NOMA 是每个用户在所有共享子载波上的速率求和。两者带宽分配方式不同对比时要统一到相同的总带宽上。def compute_sum_rate(sinr_dict, bandwidth_per_subcarrier, num_subcarriers): 计算和速率单位 bps/Hz total_rate 0 for user, sinr_val in sinr_dict.items(): # 每个用户占用全部子载波NOMA或部分子载波OFDMA rate bandwidth_per_subcarrier * np.log2(1 sinr_val) total_rate np.sum(rate) return total_ratebandwidth_per_subcarrier是子载波间隔LTE 里是 15kHz5G NR 里可以是 15/30/60kHz。sinr_val如果是数组np.sum会把所有子载波的速率加起来。注意 OFDMA 里每个用户只占部分子载波传入的sinr_val应该只包含分配给该用户的子载波。3.3 蒙特卡洛仿真循环跑多少次才能让曲线平滑单次信道实现的结果随机性很大必须跑蒙特卡洛。一般 1000 次以上曲线才比较平滑10000 次基本没有毛刺。每次循环重新生成信道、重新分配资源、重新计算速率最后取平均。def monte_carlo_simulation(num_iterations, num_users, num_subcarriers, snr_db_range): 蒙特卡洛仿真主循环 results {ofdma: [], noma: []} for snr_db in snr_db_range: ofdma_rates [] noma_rates [] for _ in range(num_iterations): distances np.random.uniform(50, 500, num_users) H generate_channel(num_users, num_subcarriers, distances) noise_var 10 ** (-snr_db / 10) # OFDMA 分支 alloc ofdma_subcarrier_allocation(H, num_subcarriers, num_users) # ... 计算 OFDMA 速率 # NOMA 分支 groups noma_user_grouping(H, num_users // 2) # ... 计算 NOMA 速率 results[ofdma].append(np.mean(ofdma_rates)) results[noma].append(np.mean(noma_rates)) return resultssnr_db_range一般取 0 到 30dB步长 5dB。distances在 50 到 500 米之间均匀分布模拟小区内随机散布的用户。noise_var由 SNR 反推注意这里的 SNR 是发射信噪比不是接收信噪比。跑完 1000 次循环大概需要几十秒到几分钟取决于子载波数和用户数。4. 避坑与排查NOMA-OFDMA 仿真里最容易翻车的五个地方4.1 和速率曲线异常NOMA 反而比 OFDMA 低现象在低信噪比区间NOMA 的和速率曲线低于 OFDMA跟理论预期相反。原因功率分配系数没有随信噪比调整。低信噪比下弱用户的信号本来就淹没在噪声里如果还给它分配大功率强用户的速率被严重压缩和速率自然上不去。解决让alpha随信噪比自适应。低信噪比时减小alpha接近等功率分配高信噪比时增大alpha偏向弱用户。可以在仿真循环里根据snr_db动态设置alpha 0.3 0.4 * (snr_db / 30)。4.2 SIC 解码顺序错乱强用户被当成干扰减掉了现象某个用户的 SINR 始终为负解码符号全是噪声。原因decode_order用了升序排列或者信道增益计算时用了 dB 值而不是线性值。dB 值的排序和线性值一致但如果中间做了10*log10再比较数值范围变了argsort的结果可能不符合预期。解决统一用线性值排序。在sic_receiver里加一行assert np.all(np.diff(user_gains[decode_order]) 0)确保降序排列无误。4.3 功率分配系数之和超过 1总功率约束被打破现象仿真跑出来的速率高得不正常或者 SIC 残差越来越大。原因ftpa_power_allocation里weights / np.sum(weights)已经归一化了但如果组内用户数变化或者手动设置了功率系数容易忘记重新归一化。解决在功率分配函数末尾加assert np.isclose(np.sum(power_coeff), 1.0)每次分配后检查。另外注意多组 NOMA 的情况下每组功率之和是P_total / num_groups不是P_total。4.4 信道矩阵维度对不上广播机制悄悄帮你算错了现象代码不报错但结果明显不对比如所有用户的 SINR 一模一样。原因H_group的形状是(num_users, num_subcarriers)power_coeff的形状是(num_users,)直接相乘时 NumPy 会广播成(num_users, num_subcarriers)看起来没问题。但如果power_coeff被误写成(num_users, 1)广播结果就变了。解决在关键计算前打印形状print(H_group.shape, power_coeff.shape)。或者用power_coeff[:, np.newaxis]显式指定维度避免隐式广播。4.5 蒙特卡洛次数不够曲线毛刺多到没法看现象和速率曲线上下抖动超过 10%根本没法对比。原因信道随机性太强100 次循环的样本均值方差太大。解决至少跑 1000 次最好 5000 次以上。如果时间不够可以用方差缩减技术比如对每次循环的信道做归一化保证平均信道增益恒定只保留小尺度衰落的随机性。5. 进阶技巧用自适应功率分配把 NOMA 的和速率再拉高 15%5.1 从固定 alpha 到动态规划功率分配还能怎么优化固定alpha的 FTPA 在某个信噪比点最优但整个信噪比范围内不是。我一般会跑一个二维扫描alpha从 0.1 到 0.9信噪比从 0 到 30dB每个点跑 500 次蒙特卡洛找到每个信噪比下的最优alpha。然后把这条最优曲线拟合出来仿真时直接查表。def adaptive_alpha(snr_db): 根据信噪比返回最优 alpha基于离线扫描拟合 # 拟合公式alpha a * snr_db ba 和 b 由扫描数据拟合得到 a, b 0.012, 0.25 return np.clip(a * snr_db b, 0.2, 0.8)np.clip把alpha限制在 0.2 到 0.8 之间防止极端值。这个拟合公式是经验性的不同场景用户数、子载波数、路径损耗指数下系数会变建议自己扫一遍再拟合。5.2 验证方法用理论上界检查仿真结果是否合理NOMA 的和速率上界是各用户独立信道容量的和下界是 OFDMA 的速率。仿真结果应该落在这个区间内。如果超出上界说明功率分配或 SINR 计算有误如果低于下界说明 SIC 没工作或者分组策略有问题。检查项理论值仿真值偏差允许范围NOMA 和速率上界sum(log2(1SNR_i))计算值仿真值 ≤ 上界NOMA 和速率下界OFDMA 和速率计算值仿真值 ≥ 下界单用户 SINR不超过 log2(1SNR_max)计算值偏差 5%功率系数和1.0计算值偏差 1e-6这个表每次跑完仿真都过一遍能拦住大部分低级错误。特别是功率系数和我习惯在代码里直接加断言不通过就抛异常。5.3 一个具体技巧用对数域计算避免数值下溢信噪比高的时候np.log2(1 sinr)里的sinr可能到 1e6 以上直接算没问题。但信噪比低的时候sinr可能到 1e-61 sinr在浮点数里精度损失严重。我一般用np.log2(1 sinr)之前先判断sinr是否小于 1e-10如果是就直接用sinr / np.log(2)近似避免精度问题。def safe_log2_1p(sinr): 安全计算 log2(1sinr)避免低信噪比下的精度损失 sinr np.asarray(sinr) result np.zeros_like(sinr, dtypefloat) mask_low sinr 1e-10 mask_high ~mask_low result[mask_low] sinr[mask_low] / np.log(2) result[mask_high] np.log2(1 sinr[mask_high]) return result这个函数在低信噪比区间能把速率计算的相对误差从 1e-3 降到 1e-8 以下。跑蒙特卡洛的时候低信噪比点的样本本来就少精度损失会放大方差用这个函数能明显改善曲线平滑度。做这个仿真最大的教训是别急着跑完整曲线先用单次信道实现把每个模块的输出打印出来确认信道系数、功率分配、SIC 解码顺序、速率计算每一步都对再上蒙特卡洛。我见过太多人直接跑 10000 次循环结果发现信道矩阵生成时距离单位写成了千米整个曲线全错。希望帮到你。本文还有配套的精品资源点击获取
返回列表