5G-NOMA功率分配仿真实践:从FTPA到SIC顺序的MATLAB实现与避坑指南
简介这套资源为5G-NOMA非正交多址系统功率分配算法提供完整MATLAB仿真环境适配MATLAB 2022a版本面向通信专业学生、研究人员及工程师用于快速复现单小区多用户场景下的功率域资源分配性能评估。包内共8个文件涵盖可直接运行的主程序、逐行中文注释的核心计算逻辑、Python辅助脚本、仿真结果图片以及AVI格式操作录像压缩包大小8.87MB。录像详细演示了从启动MATLAB、设置工作目录到运行脚本并查看结果图表的全流程并重点说明路径设置对程序执行的影响避免常见运行错误。仿真输出包括SINR计算、用户可达速率对比、功率系数影响分析等关键指标所有容量计算均基于标准香农公式注释清晰便于理解功率分配策略对用户速率的作用机理。已有156人学习使用适合需要快速上手NOMA仿真或在此基础上开展二次开发的读者。1. 5G-NOMA功率分配仿真到底在仿什么先搞懂功率差与SIC顺序很多人拿到一套 MATLAB 实现的 5G-NOMA 系统功率分配仿真代码后第一件事就是满屏找“分功率”的那几行改改用户数、调调总功率跑出一张图就以为完事了。结果画出来的频谱效率曲线和论文对不上甚至出现了“信道更差的用户速率反而更低”这种反常识结果。问题几乎都不在编码上而在功率分配和 SIC 解调顺序没有对齐。NOMA 的核心是在功率域做叠加基站给信道条件差的用户多分功率、给信道好的用户少分功率接收端再利用串行干扰消除SIC从强信号到弱信号依次解调。功率分配的顺序、SIC 的解调顺序本质上是同一个问题。顺序反了仿真从第一步就开始变质。这篇文章按“算法选型 → 系统建模 → MATLAB 实现 → 避坑排查 → 验证技巧”这条线展开目标是让你手里的功率分配代码不仅能跑还能改参数、换场景得到稳定可对比的结果。适合刚接触 NOMA 的研究生也适合想把 OMA 基线换成 NOMA 做性能对比的工程开发者。2. 功率分配算法选型FTPA、FSPA 与固定功率分配的取舍2.1 SIC 解调顺序决定功率分配方向功率域 NOMA 的下行场景里基站把多个用户的信号做线性叠加后发送。接收端并不是每个用户都做完整的干扰消除而是按信号功率从大到小依次解调功率大的信号先被解出来解完后从混合信号里减掉剩下的再交给下一个用户。信道条件差的用户会因为分到的功率更大而被先解调信道条件好的用户分到的功率小反而要承担更重的 SIC 任务。这个逻辑直接决定了功率分配的约束方向弱用户的功率分配因子必须大于强用户。如果按“公平”想当然地给每个用户平分功率SIC 就会乱套——两个用户功率接近时先解调谁都不稳定叠加信号的星座点间隔变小误码率立刻上升。常见的做法是根据信道增益排序把信道增益从小到大排列功率分配因子从大到小排列再代入功率分配算法。信道增益排序后h(1) h(2) ... h(N) 功率分配排序后P(1) P(2) ... P(N) SIC 解调顺序先解用户 1再解用户 2依次类推这里需要特别注意排序的对象是信道增益的线性值不是 dB 值。不少代码里直接用 dB 值排序信道差异被压缩后排序结果虽然不会变但后续计算功率分配因子时用 dB 值参与运算会出大问题第 5 章会单独讲这个坑。2.2 FTPA 分数阶功率分配一个公式搞定但参数要会调FTPAFractional Transmit Power Allocation是 NOMA 仿真里最常见的功率分配算法因为它只需要一个公式就能算出所有用户的功率分配因子计算量几乎可以忽略。公式的思路是让信道增益越小的用户拿到越大的功率但分配比例受一个衰减因子 α 控制。常见的写法是先计算每个用户的加权信道系数即信道增益的 -α 次方再对所有这些系数求和最后用每个用户的系数除以总和得到归一化功率分配因子再乘以总功率得到实际分配功率。α 的取值范围是 0 到 1。α 0 时所有用户分到相同功率退化成等功率分配α 越大弱用户拿到的功率越多用户间速率差距越小但系统总吞吐量会下降。α 的取值范围与系统总吞吐量、用户公平性直接相关。仿真里 α 0.4 ~ 0.6 是常见区间适合做默认曲线。如果你是想复现某篇论文的曲线先查清楚论文用的 α 值不同 α 下的频谱效率差距能到 20% 以上这往往是“代码没问题但曲线对不上”的最大原因。2.3 FSPA 全搜索功率分配穷举搜索与约束过滤FSPAFull Search Power Allocation的思路最朴素把所有可能的功率分配组合全部枚举出来用约束条件过滤掉不满足最小速率或最大功率限制的组合再在剩下的候选中挑一个目标函数最优的。目标函数可以是系统吞吐量最大也可以是吞吐量与公平性的加权和。FSPA 的计算量与用户数和离散粒度呈指数关系。假设 2 个用户、功率步长 0.01组合数量就是 100 种左右4 个用户时组合数会膨胀到 10 万量级每一步还要计算 SIC 后的速率仿真时间从秒级跳到小时级。FSPA 适合做小规模场景的性能上界参考不适合做大规模系统仿真。实际仿真中 FSPA 通常只用于 2 用户或 3 用户场景作为“性能天花板”和 FTPA 对比。如果用户超过 4 个建议直接放弃 FSPA改用 FTPA 或后面要说的比例公平类算法。2.4 固定功率分配、比例公平与注水算法的对比固定功率分配是另一类常用方法即按固定比例给用户分功率比如强用户和弱用户按 1:4 分配。它的好处是实现零开销、可复现性强坏处是无法随信道变化自适应信道一旦变化固定比例就变成次优甚至劣于 OMA。固定功率分配适合做基线对比不推荐作为主算法。注水算法Water-filling是从信息论里来的思路会根据每个用户的等效信道增益往“水位线”注水信道好的用户分到更多功率。但 NOMA 的 SIC 顺序决定了弱用户需要更多功率注水算法在这类场景里方向是反的直接套用效果很差。若确实要用注水思想需要先做一步信道反转或按 SIC 后的等效信噪比重新计算水位。比例公平Proportional Fair, PF则是在每个调度周期内最大化所有用户速率对数的和天然权衡吞吐量和公平性。实现上比 FTPA 复杂需要迭代计算但在多用户动态场景里更实用。做单点静态仿真时FTPA 已经够用做多时隙动态调度时PF 更合适。3. 搭建可复现的仿真模型信道生成、用户配对与 SIC 建模3.1 用户配对信道差异越大NOMA 增益越明显NOMA 的增益来源是用户间信道差异。两个信道条件几乎一样的用户做功率域叠加SIC 解调难度大功率分配又拉不开差距最后得到的频谱效率可能还不如 OMA。所以仿真第一步不是选算法而是选用户。常见的用户配对策略是把所有用户按信道增益排序最强和最弱的配对次强和次弱的配对这样每组的信道差异最大。另一种做法是设置一个最小信道差阈值小于阈值的用户自动切换到 OMA 模式避免 NOMA 失效。仿真中建议把“用户配对”做成交互式变量而不是写死在主程序里。换个配对顺序重跑一次你能清楚看到 NOMA 增益来自信道差。某开发者之前做 2 用户仿真时随机配对的信道差只有 3 dBNOMA 相比 OMA 的增益不到 5%把用户改成强-弱配对后信道差拉到 15 dB增益直接到 30% 以上。3.2 信道模型路径损耗、阴影衰落与小尺度衰落的生成5G-NOMA 仿真里的信道系数通常由三部分组成路径损耗、阴影衰落和小尺度衰落。三者的单位要分清路径损耗和阴影衰落一般用 dB 表示小尺度衰落是线性值。生成信道系数时先把 dB 值转成线性值再乘小尺度衰落。常见的参数表如下参数参考值说明载频2 GHz影响路径损耗的穿透系数系统带宽10 MHz计算噪声功率用基站发射功率20 dBm ~ 30 dBm换算成瓦特参与运算路径损耗指数3.5城市宏站场景常用值阴影衰落标准差8 dB对数正态分布小尺度衰落瑞利分布单位均值指数分布MATLAB 里生成小尺度衰落最常用的是raylrnd但要注意生成的是幅度还是功率。幅度服从瑞利分布而功率服从指数分布。如果你只需要功率增益直接exprnd(1)生成指数分布随机数即可省一次平方运算。3.3 SIC 建模理想 SIC 与残余干扰的处理SIC 建模直接影响速率和误码率。理想 SIC 假设先解调的用户信号被完美消除残余干扰为零非理想 SIC 假设消除后有残余干扰残余干扰的大小通常用消除因子 β 表示β 0 对应理想 SICβ 0.01 ~ 0.1 对应不同程度的不完美消除。速率计算里理想 SIC 下用户 i 的信干噪比是自身功率除以噪声、以及所有比自身功率更小的用户信号功率之和这些还没被消除。非理想 SIC 下已经解调过的用户信号并不会完全消失而是以 β × 原功率的形式残留在分母里。仿真初期建议先跑理想 SIC验证算法和功率分配逻辑没问题后再引入残余干扰。直接把非理想 SIC 加进来一旦结果异常你很难判断是功率分配错了还是残余干扰参数不合理。两条路分开调效率高得多。4. MATLAB 实现功率分配中文注释代码与关键参数说明4.1 主程序框架参数初始化、循环仿真与结果统计先给一个完整的主程序框架。代码里用到了 MATLAB 的脚本和函数分离方式主脚本负责参数设置、循环和画图函数文件负责功率分配算法。这样改参数时不用碰算法逻辑排错时也不用在长脚本里来回翻。%% 5G-NOMA 功率分配仿真主程序 % 场景单小区下行2 用户 NOMA % 功能对比 FTPA 与 FSPA 的频谱效率和公平性 clear; clc; rng(2024); %% 参数初始化 N 2; % 用户数 P_total_dBm 23; % 基站总发射功率单位 dBm P_total 10^((P_total_dBm-30)/10); % 转成瓦特 bandwidth 10e6; % 带宽 10 MHz noise_psd -174; % 噪声功率谱密度 dBm/Hz No 10^((noise_psd-30)/10) * bandwidth; % 噪声功率瓦特 alpha 0.5; % FTPA 衰减因子 %% 生成信道增益线性值 % 用户 1近用户信道好用户 2远用户信道差 path_loss_dB [60 80]; % 路径损耗单位 dB shadow_dB [2 -3]; % 阴影衰落单位 dB channel_gain_linear 10.^(-(path_loss_dB shadow_dB)/10); % 加入小尺度衰落后的信道增益蒙特卡洛循环里更新 num_trials 1000; se_ftpa zeros(1, num_trials); se_fspa zeros(1, num_trials); %% 蒙特卡洛循环 for trial 1:num_trials h channel_gain_linear .* abs(raylrnd(1, 1, N)).^2; % FTPA 功率分配 p_ftpa ftpa_alloc(h, P_total, alpha); se_ftpa(trial) compute_sum_rate(h, p_ftpa, No); % FSPA 功率分配步长 0.01 p_fspa fspa_alloc(h, P_total, 0.01); se_fspa(trial) compute_sum_rate(h, p_fspa, No); end %% 结果统计 fprintf(FTPA 平均频谱效率%.2f bps/Hz\n, mean(se_ftpa)); fprintf(FSPA 平均频谱效率%.2f bps/Hz\n, mean(se_fspa));这版主程序把最重要的几个变量放在了最前面总功率、噪声功率、信道增益和 FTPA 衰减因子。rng(2024)固定随机种子保证每次跑的结果一致复现别人结果时这一点非常关键。没有固定种子的话蒙特卡洛结果每次都会浮动你可能把浮动的差异误当成算法优劣。4.2 FTPA 实现功率分配因子计算与归一化FTPA 的函数实现只需要十几行但有一个细节必须写对归一化是在全部用户之间做的不是每用户独立算。下面这个函数返回的是每个用户的实际分配功率调用方可以直接拿去算速率。function p ftpa_alloc(h, P_total, alpha) % FTPA 分数阶功率分配 % 输入 % h - 信道增益线性值1xN 向量 % P_total - 总发射功率瓦特 % alpha - 衰减因子0~1 % 输出 % p - 每个用户分配到的功率1xN 向量 % 计算加权信道系数 weight h.^(-alpha); % 信道差h小的用户 weight 大 % 归一化得到功率分配比例 ratio weight / sum(weight); % 分配实际功率 p ratio * P_total; end这段代码的核心在weight h.^(-alpha)。信道增益 h 越小-alpha 次方反而越大所以弱用户拿到更大的 weight也就是更大比例的功率。ratio weight / sum(weight)把权重归一化成总和为 1 的比例再乘总功率。调 α 时注意α 越大弱用户拿到的功率越多但总吞吐量会下降所以不能光看速率还要看公平性。4.3 FSPA 实现步长设定与暴力搜索FSPA 的经典思路是枚举所有可能的离散功率组合。为了不让枚举量爆炸先用一个固定功率步长把总功率从 0 到 P_total 按步长切分然后找所有两个用户功率之和等于总功率的组合逐个计算速率。function p fspa_alloc(h, P_total, step) % FSPA 全搜索功率分配 % 输入 % h - 信道增益线性值 % P_total - 总功率 % step - 功率离散步长如 0.01 % 输出 % p - 最优功率分配结果 N length(h); p_grid 0:step:P_total; % 离散功率候选集 best_rate -1; p zeros(1, N); % 枚举所有满足总和约束的功率组合 for i 1:length(p_grid) for j 1:length(p_grid) if abs(p_grid(i) p_grid(j) - P_total) 1e-8 continue; % 过滤掉总功率不等于 P_total 的组合 end p_cand [p_grid(i), p_grid(j)]; rate compute_sum_rate_mimo(h, p_cand, No); if rate best_rate best_rate rate; p p_cand; end end end end这里有两个易错点。第一浮点数比较不能直接要用误差容忍度1e-8判断两数之和是否等于总功率。第二搜索的是功率值而不是功率比例这会带来一个隐患当 P_total 改变时步长也需要跟着调整。比如 P_total 1 瓦特步长 0.01 有 100 个候选如果 P_total 10 瓦特步长还是 0.01组合数变成 10^6 量级仿真时间会暴涨。解决方法是把步长设为 P_total 的百分之一让离散粒度跟随总功率自适应。4.4 出图与结果对比频谱效率、公平指数和能效仿真不能只算一个总吞吐量就结束至少要看三个维度频谱效率、Jain 公平指数和能量效率。频谱效率衡量系统总速率公平指数衡量用户间速率的均衡程度能量效率衡量每焦耳能量对应的传输比特数。三者合在一起才能判断一种功率分配算法是否真的“更好”。%% 公平指数与能量效率统计 % Jain 公平指数1 表示完全公平1/N 表示完全不公平 r compute_per_user_rate(h, p_ftpa, No); jain_ftpa sum(r)^2 / (N * sum(r.^2)); % 能量效率单位 bit/J/Hz ee_ftpa sum(r) / P_total;Jain 公平指数在 MATLAB 里就一行公式但它对功率分配算法的区分度很高。FTPA 的 α 0 时公平指数接近 0.52 用户场景α 1 时接近 1。画出“公平指数 - α 曲线”比单纯画“频谱效率 - 用户数”更能让算法对比有说服力。能效计算则要注意单位换算带宽相关部分别漏乘。5. 仿真避坑排查功率对不上、SIC 顺序写反、搜索爆炸与编码乱码5.1 现象用户速率全部异常低总发射功率没用完仿真结果里所有用户的速率都偏小检查总功率发现分配出去的功率加起来远小于 P_total。这类问题多见于 FTPA 实现时没有做归一化直接把h.^(-alpha)当成功率分配因子导致分配功率总和不为 1总功率被“吃掉”了一部分。原因本质是权重没有归一化。解决方法是检查sum(ratio)是否等于 1或者直接看sum(p)是否等于P_total。可以在主程序里加一句断言assert(abs(sum(p) - P_total) 1e-6, 功率归一化失败);加完断言后再跑一次出问题会直接报错而不是让你对着曲线猜。这条血泪经验值得所有做功率分配仿真的人记住。5.2 现象SIC 解调顺序与功率分配顺序不一致误码率飙升误码率曲线异常先怀疑 SIC 顺序。典型的错误是功率分配按“强用户多分、弱用户少分”做了但 SIC 解调时却按信道增益从大到小依次解调结果两个用户都解不对。原因在于 NOMA 的 SIC 顺序必须遵循“功率越大越先解”的原则而不是“信道越好越先解”。解决方法是把 SIC 算法和功率分配算法共用同一个排序函数。常见做法是先按信道增益排序得到用户索引再根据功率分配结果做一次argsort(p, descend)确保解调顺序和实际功率大小一一对应。不要把排序逻辑藏在两个文件里否则后期改参一定翻车。5.3 现象把 dB 值当线性值参与运算信道增益差了六个数量级用一个用户路径损耗 80 dB、另一个 60 dB 的场景仿真出来弱用户的功率分配因子反而比强用户小或者频谱效率低到离谱。把中间变量打印出来发现信道增益“差了好几个数量级”但方向是对的。原因几乎可以肯定是混合使用了 dB 和线性值。路径损耗的 dB 值不能直接参与h.^(-alpha)计算必须先转成线性值。一个很隐蔽的坑是有人只在生成信道时做了一次10^(-dB/10)但后面在功率分配函数里又对 h 做了一次 dB 转换等于算了两遍。建议统一约定所有函数接口里只传线性信道增益dB 值只在参数初始化阶段出现一次。5.4 现象FSPA 步长设小后循环跑几小时不出结果FSPA 搜索组合数随用户数和步长线性增长4 用户 步长 0.005 时组合数轻松超过百万级。每组合还要调 SIC 速率函数仿真时间从分钟级跳到小时级。解决方法是先做复杂度估算组合数 ≈ (P_total/step)^(N-1)。步长从 0.01 改成 0.005组合数变为原来的 4 倍用户数从 2 改成 3组合数变为原来的 (P_total/step) 倍。如果估算结果超过 10^6直接放弃 FSPA或者把搜索空间缩小到功率分配比例区间。常见做法是把搜索限制在“弱用户功率占比 50%~90%”这个先验合理区间内组合数能降一个数量级结果几乎不受影响。5.5 现象中文注释在旧版 MATLAB 里显示乱码或直接报错代码里写了大量中文注释放到旧版 MATLAB 打开后注释变成乱码个别情况下函数名或变量名包含中文还会报“未定义”错误。这个问题的根源是脚本编码格式与 MATLAB 版本不兼容。新版 MATLAB 默认支持 UTF-8旧版本对中文字符集支持较差。解决方法是统一用matlab.desktop.editor.associate(m)查看当前编码或者直接改用纯英文注释关键中文说明的方式。更稳妥的做法是所有变量名、函数名保持英文中文只出现在注释里并保存文件时选 UTF-8。如果你要发别人复现在文档开头写一句“请用 R2020a 及以上版本打开”能省掉一大半兼容性问题。6. 进阶把仿真调到可复现论文曲线的三个验证习惯6.1 归一化自检与总功率守恒检查每次改完信道模型或功率分配算法先跑一个极短蒙特卡洛比如 10 次中途断言总功率守恒。除断言外还要打印每个用户的功率分配比例肉眼确认弱用户比例比强用户高。这个习惯能过滤掉 80% 的“低级错误”。6.2 退化测试让 NOMA 退化成 OMA验证模型基准把 NOMA 系统退化成 OMA 是验证模型正确性最直接的手段。做法是把用户数设为 1或者在时隙维度上让每个用户独占全部带宽和功率此时 NOMA 的系统速率应该等于 OMA 的基准值。如果退化成 OMA 后速率反而比 NOMA 还高说明 NOMA 叠加的增益没做出来问题在信道配对或 SIC 建模。这个测试在每次换新信道模型时都值得跑一遍。6.3 参数模板与结果存档习惯我一般会固定一张参数模板表把 α、用户配对方式、小尺度衰落类型、SIC 消除因子 β 全部记录在表格里跑完一次仿真就把结果变量存成 .mat 文件文件名带上关键参数缩写。不然隔几天回头想对比两组结果已经完全想不起来上次用的 β 是 0 还是 0.1。保存结果变量的代码很简单save(sprintf(result_alpha%.2f_beta%.2f.mat, alpha, beta), se_ftpa, se_fspa, params);这套习惯帮我在做参数扫描时省了大量反复调试的时间仿真里最玄学的部分往往不是算法本身而是你自己一星期前的参数组合。希望这些方法能帮到你让你在功率分配仿真这条路上少走几个来回。本文还有配套的精品资源点击获取