news 2026/9/23 19:00:19

SCMA瑞利信道下的PM-MPA检测器MATLAB仿真与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SCMA瑞利信道下的PM-MPA检测器MATLAB仿真与避坑指南

简介:面向5G非正交多址技术研究者的MATLAB实现包,聚焦SCMA系统下的PM-MPA检测算法。资源基于消息传递与最大后验概率思想,提供瑞利信道环境中的完整仿真链路,适合通信工程高年级学生、算法工程师及科研人员参考复现。包内共5个文件,4个m脚本承担核心功能:PM_MPA.m实现迭代消息传递过程,simulation.m用于配置系统参数并评估误码性能,scmaenc.m完成用户数据到稀疏码字的映射,log_sum_exp.m则为概率计算提供数值稳定的对数求和工具;另附一个压缩算法包,可与软判决MPA思路对照使用。整个资源包仅7KB,轻量便携,下载后即可快速运行。已有611人学习使用,是理解SCMA编码原理、PM-MPA迭代译码流程及非正交多址性能权衡的实用入门资料。

1. SCMA 的 PM-MPA 检测器:这个 matlab 仿真包把瑞利信道下的多用户链路凑齐了

PM-MPA(Product Matrix Message Passing Algorithm,积矩阵消息传递算法)是 SCMA(Sparse Code Multiple Access,稀疏码分多址)系统里很常用的一类多用户检测算法。我这次拆解的 matlab 源码包,把发射侧的scmaenc.m、检测侧的PM_MPA.m、数值工具log_sum_exp.m和顶层仿真脚本simulation.m串成了一条可以直接跑的完整链路,信道模型按瑞利衰落来处理。它适合刚接触 SCMA 仿真、想拿现成编码器和检测器跑 BER 曲线的研究生,也适合准备做 MPA 变体算法对比的工程师。下文按“文件怎么拆 → 编码侧怎么实现 → 检测侧怎么迭代 → 哪些参数坑最值得注意”的顺序,把这份资源讲透。

2. 拆包初看:从 simulation.m 到 PM_MPA.m 的调用链

2.1 五个文件各自的角色

压缩包里出现频率最高的五个文件,职责边界其实很清晰,我在下面按“谁调谁”的顺序列出来:

文件角色关键观察点
simulation.m顶层仿真脚本,负责定义 SNR 扫描点、用户数、迭代次数、信道类型瑞利信道建模方式、BER 统计方式
scmaenc.mSCMA 编码器,把用户比特映射成稀疏码字码本维度、非零元素位置
PM_MPA.mPM-MPA 检测算法核心实现消息初始化、因子节点更新、收敛判断
log_sum_exp.m对数域求和工具是否做最大值提取,能否防下溢
scma-SD-MPA.zip另一个 SCMA 检测算法压缩包,做软判决对比用可与 PM-MPA 对照性能

simulation.m是入口,它生成用户比特后调用scmaenc.m,经过瑞利信道加噪后交给PM_MPA.m做检测,检测结果再和原始比特比对统计误码率。log_sum_exp.m不单独运行,它被PM_MPA.m内部循环调用。scma-SD-MPA.zip则是一个独立对比版本,解压后可以参照同样的调用方式替换测试。

2.2 simulation.m 的主循环结构

我把这类 SCMA 仿真工程里最常见的顶层循环抽出来,结构基本如下:

%% simulation.m 主循环结构(与常见 SCMA 工程一致) clear; clc; % ------- 基础参数 ------- J = 6; % 用户数 K = 4; % 资源块数(子载波数) M = 4; % 码本星座点数量 maxIter = 6; % PM-MPA 最大迭代次数 EbN0dB = 0:2:12; % 每比特信噪比扫描 trialNum = 1e4; % 每个 SNR 点的蒙特卡洛帧数 % ------- 瑞利信道 ------- % 典型做法:每个资源块上信道系数独立, % h = (randn + 1i*randn) / sqrt(2) % ------- 主循环 ------- for ebnoIdx = 1:length(EbN0dB) errorCount = 0; bitCount = 0; for trial = 1:trialNum % 1) 生成随机比特,调用 scmaenc 得到 K 维发送向量 % 2) 乘上瑞利衰落系数,叠加复高斯白噪声 % 3) 调用 PM_MPA.m 做检测,得到估计比特 % 4) 与原始比特比对,累计 errorCount / bitCount end ber(ebnoIdx) = errorCount / bitCount; end % ------- 画图 ------- semilogy(EbN0dB, ber, '-o'); grid on;

逻辑说明:这里用EbN0dB而不是SNR是通信仿真里的习惯,因为不同调制阶数下每个符号携带的比特数不同,只有折算到每比特能量,多条 BER 曲线才有可比性。循环内每帧数据都走一遍“编码 → 信道 → 检测 → 比对”四个步骤,最后统计误码数除以总比特数得到 BER。

参数说明:J=6表示 6 个用户共用 4 个资源块,过载率 150%,这是 SCMA 参考设计中常见的配置;maxIter不建议一开始设太大,先设 6 跑通流程,再逐步加大观察性能变化;trialNum在低误码率区域要适当增大,否则曲线尾部会抖动得很厉害。

2.3 拿到代码后先做三项自查

第一次运行这份资源,先别急着改参数,我一般会做三个快速检查。第一是看当前目录是否已经addpath到所有.m文件所在路径,MATLAB 报“未定义函数或变量”八成是路径问题。第二是在simulation.m里搜索码本定义位置,这类代码的码本矩阵有时直接写在主脚本里,有时单独放在一个codebook.m里,务必保证它能被scmaenc.mPM_MPA.m同时访问。第三是检查log_sum_exp.m文件尾是否有多余测试代码,很多下载版代码文件尾部残留调试输出,会影响仿真效率。做完这三项,基本可以确认代码环境是可复现的状态。

3. 编码侧实现:scmaenc.m 如何把比特变成稀疏码字

3.1 从比特到稀疏码字的两次映射

SCMA 的编码过程和传统 CDMA 最大的区别在于“稀疏”两个字。传统 CDMA 每个用户都会扩展占用全部资源,SCMA 则让每个用户只占用其中一部分资源,留下大量零元素。scmaenc.m做的事情,本质上是两次映射:第一次把比特组合映射成星座点索引,第二次把星座点索引映射成 K 维码字。

% scmaenc.m 的核心思路(非原包逐行照贴,变量名按习惯改写) % codebook: K x M x J 三维矩阵 % dataBits: J x log2(M) 的逻辑比特矩阵 function tx = scmaenc(dataBits, codebook) [J, bitsPerSym] = size(dataBits); [K, M, ~] = size(codebook); tx = zeros(K, 1); % 发射向量初始化为 0 for j = 1:J % 第一次映射:比特 -> 符号索引(1~M) symIdx = bi2de(dataBits(j, :), 'left-msb') + 1; % 第二次映射:符号索引 -> 稀疏码字,并叠加到资源上 tx = tx + codebook(:, symIdx, j); end end

逻辑说明:codebook(:, symIdx, j)取出第 j 个用户在第symIdx个星座点上的 K 维码字。由于 SCMA 码字是稀疏的,这个向量里大部分位置是 0,只有少数几个位置非零。循环内做的是多用户信号在同一组资源上的叠加,这也是 SCMA “非正交”的直接体现。

参数说明:bi2de(..., 'left-msb')的左右 MSB 设置会影响索引顺序,如果发射端和接收端用的映射规则不一致,BER 曲线会直接崩溃。码本矩阵的第三维是用户序号,第二维是星座点序号,第一维是资源序号,这个维度约定在PM_MPA.m里同样适用,改动时两边必须同步。

3.2 码本结构与稀疏因子图参数

SCMA 的性能很大程度上取决于码本设计。我常见到的资源包里,码本采用 6 用户 4 资源的结构,也就是 J=6、K=4、M=4,每个用户只占据 2 个资源块。

参数典型值含义
J6用户数
K4资源块数
M4每个用户的星座点数量
df2每个用户非零资源块数
过载率150%J/K,表示频谱资源复用程度
单资源重叠用户数3每个资源块上叠加的用户数

这组参数的含义是:6 个用户的数据挤在 4 个资源块上发射,每个资源块上的信号由 3 个用户的信号叠加而成。df=2意味着每个用户只在 2 个资源块上放置能量,其余 2 个资源块上为空。这种稀疏性让接收端可以用因子图描述用户和资源之间的关系,从而用消息传递算法以较低复杂度完成多用户分离。

3.3 验证编码器是否正确的两个自检点

下载资源最容易出的问题就是码本文件被误改或复制错位,我每次拿到新代码都会跑一下这个自检脚本:

% 检查第 j 个用户的码字稀疏度是否等于 df j = 3; codewords = reshape(codebook(:, :, j), K, M); nzCount = sum(abs(codewords) > 1e-12, 1); % 统计每列非零数 disp(unique(nzCount)); % 期望输出: 2 % 检查每个码字能量是否归一化 energy = sum(abs(codewords).^2, 1); disp(energy); % 期望接近 1

逻辑说明:第一个检查保证码本的稀疏结构与PM_MPA.m里因子图矩阵的假设一致。如果unique(nzCount)输出的是 3 而不是 2,说明码本数据错位,后续检测算法会把不存在的连接关系当成有连接,导致消息更新混乱。

参数说明:1e-12是判断是否为 0 的阈值,因为浮点运算中真正的 0 可能被存成极小的残留值。能量归一化检查则确保每个码字等概率发射,避免某个星座点功率异常偏高,影响 BER 结果的真实性。

4. 检测侧核心:PM_MPA.m 里的消息迭代、乘积矩阵与 log_sum_exp

4.1 从 MAP 到 MPA 再到 PM-MPA:三次复杂度取舍

接收端要做的事,是从叠加了多用户信号和噪声的 K 维向量里恢复每个用户的比特。最理想的是 MAP 检测,但对 6 用户 4 星座的配置,一次联合遍历就是M^J = 4096种组合,调制阶数再高就完全跑不动。MPA 的做法是在因子图上做消息传递,每个资源块只需遍历M^df种组合。

检测方案单资源遍历量复杂度特征
联合 MAP/ML4096指数爆炸,不可实际使用
标准 MPA16对每个用户状态遍历邻居码字组合
PM-MPA约 M²用乘积矩阵缓存,减少重复计算

PM-MPA 的改进思路在于:当某个资源块上重叠了多个用户时,标准 MPA 对每个用户都要重新计算一遍邻居用户的联合概率,PM-MPA 把这些重复计算整理成一份乘积矩阵缓存,更新单个用户时通过“整体乘积”剔除自己那一路。在PM_MPA.m里,这个技巧体现为因子节点更新时先算临时累乘,再逐用户取值。

4.2 PM_MPA.m 的迭代骨架

function [bitsEst, llrOut] = PM_MPA(y, H, codebook, N0, maxIter, convTh) % y: Kx1 接收向量 % H: KxJ 瑞利信道系数矩阵 % codebook: KxMxJ 码本 % N0: 噪声单边功率谱密度 % maxIter: 最大迭代次数 % convTh: 收敛门限 [K, M, J] = size(codebook); % 变量节点消息初始化为等概率 Mv2f = ones(M, J) / M; for iter = 1:maxIter % ---- 因子节点更新 ---- Mf2v = ones(M, J); for k = 1:K % 找到占用第 k 个资源的用户集合 userIdx = find(abs(codebook(k, 1, :)) > 1e-12); % 先算该资源上所有用户消息的乘积矩阵(PM 核心) prodMsg = ones(M, 1); for jj = 1:length(userIdx) prodMsg = prodMsg .* Mv2f(:, userIdx(jj)); end % 对每个用户单独生成因子节点消息 for jj = 1:length(userIdx) j = userIdx(jj); % 剔除自己后与信道、噪声相关的指数项相乘 % 常见实现里会调用 log_sum_exp 做累加 Mf2v(:, j) = prodMsg ./ Mv2f(:, j) .* exp(-abs(y(k) - H(k,j) * codebook(k,:,j).').^2 / N0); Mf2v(:, j) = Mf2v(:, j) / sum(Mf2v(:, j)); end end % ---- 变量节点更新 ---- Mv2f = ones(M, J); for j = 1:J resIdx = find(abs(codebook(:, 1, j)) > 1e-12); % 将自己所占资源上的因子节点消息做乘积 for kk = 1:length(resIdx) Mv2f(:, j) = Mv2f(:, j) .* Mf2v(:, resIdx(kk), j); end Mv2f(:, j) = Mv2f(:, j) / sum(Mv2f(:, j)); % 归一化 end % ---- 收敛判断 ---- if iter > 1 && max(abs(Mv2f - Mv2f_old), [], 'all') < convTh break; end Mv2f_old = Mv2f; end % 硬判决:取每列最大概率对应的符号 [~, symIdx] = max(Mv2f, [], 1); bitsEst = de2bi(symIdx - 1, log2(M), 'left-msb'); llrOut = Mv2f; end

逻辑说明:因子节点更新那段里,prodMsg就是 PM-MPA 的乘积矩阵缓存。它先把某个资源上所有用户的消息乘在一起,然后对某个用户更新时直接拿总乘积除掉自己,省去了逐个重新遍历邻居状态的开销。这个“先整体乘、再逐个除”的操作,正是 PM-MPA 相比标准 MPA 的核心区别。

参数说明:convTh是收敛门限,典型值在1e-31e-5之间。设太大,迭代提前终止,BER 变差;设太小,迭代次数拉满,复杂度优势消失。N0必须和simulation.m里的噪声功率保持一致,否则检测器内部指数项的权重是错的,高 SNR 区域的表现会非常奇怪。

4.3 log_sum_exp 在消息更新中的用法

MPA 类算法的消息更新里经常出现形如log(exp(a) + exp(b))的运算,而 matlab 原生log(sum(exp(x)))在 x 取较大负值时,exp会直接下溢成 0,log 再取就变成-Inf。资源包里单独放一个log_sum_exp.m就是为了解决这个数值问题。

function y = log_sum_exp(x, dim) % 对数域求和:计算 log(sum(exp(x))) 的数值稳定版本 if nargin < 2 dim = 1; end maxVal = max(x, [], dim); y = maxVal + log(sum(exp(x - maxVal), dim)); end

逻辑说明:核心技巧是先减去最大值再求指数,把指数函数的自变量整体平移到非正区间。这样exp(x - maxVal)的最大值是 1,不会上溢,最小值受精度限制,也不会轻易下溢成 0。

参数说明:dim指定求和维度,默认是 1。在实际使用中,如果消息矩阵是M x J维,想按用户维度求和就把dim设为 2。需要注意的是,这个函数只接受实数输入,复数相加必须先拆成实部虚部分别处理。

5. 避坑排查:瑞利信道归一化、log_sum_exp 溢出与迭代不收敛

5.1 现象:BER 曲线比文献差好几个 dB,平躺下不去

原因:最常见的是simulation.m里瑞利信道系数没有归一化。(randn + 1i*randn)产生的信道功率是 1,但很多人会忘记除sqrt(2),导致信道增益偏大,等效噪声被低估。另一个原因是把EbN0SNR混用,SCMA 多用户叠加后每个资源上的符号能量不等于单个用户的比特能量。

解决:检查信道生成代码,确认h = (randn + 1i*randn) / sqrt(2);再看N0的计算是否用10^(-EbN0dB/10)并除以每比特对应资源数。我一般会在simulation.m里加一行disp(norm(h, 'fro')^2 / K),理想值接近 1。

5.2 现象:log_sum_exp 输出 NaN 或 Inf,BER 曲线出现断崖

原因:log_sum_exp输入里出现NaN,通常是消息矩阵中出现了零概率值,归一化时除数为 0。另一个可能是exp(x - maxVal)x - maxVal全部为非常大负数,sum 之后为 0,取 log 得到-Inf而不是有效数值。

解决:在PM_MPA.m的变量节点更新后加一个保护:Mv2f = max(Mv2f, eps);然后再归一化。同时检查收敛判断时是否用了abs(Mv2f - Mv2f_old),如果消息矩阵本来就是概率值,diff量级很小,直接用maxDiff < convTh即可,不需要再取对数。

5.3 现象:高信噪比区域误码率下降变缓,出现错误平台

原因:PM-MPA 是近似算法,迭代次数固定为 6 时,高 SNR 区域残留误差主要来自消息近似而非噪声。另外一个容易被忽视的点是收敛门限convTh设得太大,算法在还没收敛时就提前退出。

解决:把maxIter从 6 增大到 10,convTh1e-3收紧到1e-5,观察曲线尾段是否改善。如果平台还在,检查信道补偿逻辑:H(k,j)在深衰落位置幅度接近 0,直接除会放大噪声,常规做法是在补偿时加一个小常数delta=1e-6保护分母。

5.4 现象:PM-MPA 和标准 MPA 性能完全重合,复杂度优势体现不出来

原因:代码里没有真正缓存乘积矩阵,只是在循环内重复计算,本质还是标准 MPA。很多版本的PM_MPA.m只是把标准 MPA 的变量名改成 PM,内部逻辑没有体现“先乘整体、再除自己”的操作。

解决:检查因子节点更新部分是否在for jj循环外先算了prodMsg。如果没有,参考本文 4.2 节的写法,把乘积矩阵提到内层循环外面。验证方式很简单:在PM_MPA.m里记录每次迭代的乘法次数,和标准 MPA 版本对比,差距不明显就说明缓存逻辑没生效。

6. 进阶:把 PM-MPA 和 SD-MPA 画到同一张 BER 图上做对比

6.1 复用一份仿真脚本的快速做法

拿到scma-SD-MPA.zip后,不需要另写仿真框架。把simulation.m里调用的检测函数封装一层,用函数句柄切换算法,是最省事的做法:

% 统一检测接口 detectMPA = @(y, H, codebook, N0) PM_MPA(y, H, codebook, N0, 6, 1e-4); detectSDMPA = @(y, H, codebook, N0) SD_MPA(y, H, codebook, N0, 6, 1e-4); % 在 SNR 循环里调用,其余代码完全复用 [bitsEst, ~] = detectFunc(y, H, codebook, N0);

对比时建议固定同一组随机种子,确保两种算法经受完全相同的信道和噪声样本。这样画出来的 BER 曲线差异,才能只反映算法本身的能力。

6.2 判读对比结果时看什么

性能上,SD-MPA(软判决)通常比 PM-MPA 有零点几个 dB 的增益,尤其在低迭代次数下。复杂度上,PM-MPA 在中低 SNR 区域迭代 3 到 4 次就能收敛,而标准 MPA 往往需要 6 次以上。如果两条曲线完全重合,要检查迭代次数是否被固定成相同值,PM-MPA 的优势场景是“相同迭代次数下更快收敛”。我习惯在代码里加tic/toc记录每个 SNR 点的平均耗时,这条时间曲线往往比 BER 曲线更能说明 PM-MPA 的价值。从那以后,我每次换信道模型或调制阶数,都强制走一遍“信道归一化检查、消息保护、迭代收敛性观察”这三个步骤,短则五分钟长则半小时,能替后面省下大把调参时间。希望这份拆解能让你少踩几个我踩过的坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 18:55:57

DeepSeek企业知识库微调实战:从文档清洗到LoRA部署

简介&#xff1a;本资源是一份面向企业AI工程师与知识系统架构师的实战指南&#xff0c;聚焦DeepSeek大模型在跨行业知识库建设中的落地路径与微调方法论&#xff0c;解决传统知识管理系统语义理解弱、数据孤岛难打通、个性化服务缺失等共性难题。文档共24页PDF&#xff0c;结构…

作者头像 李华
网站建设 2026/9/23 18:55:55

DeepSeek+MIDI实现AI作曲:从乐谱生成到工程落地的完整指南

简介&#xff1a;面向AI音乐创作开发者的实战指南&#xff0c;聚焦DeepSeek与MIDI技术的融合应用&#xff0c;系统讲解从MIDI数据采集、清洗、特征提取&#xff0c;到模型架构设计、训练调优&#xff0c;再到音乐参数生成与MIDI文件输出的完整链路。文档共26页&#xff0c;以“…

作者头像 李华
网站建设 2026/9/23 18:54:03

微信小程序农产品销售平台毕设:SSM+MySQL源码部署与二次开发指南

简介&#xff1a;这是一套面向高校计算机相关专业毕业设计的微信小程序农产品销售平台完整项目源码&#xff0c;采用微信开发者工具配合Java、SSM框架与MySQL数据库实现&#xff0c;适合正在准备毕设或需要小程序全栈练手的同学参考。压缩包共1195个文件&#xff0c;约14.86MB&…

作者头像 李华
网站建设 2026/9/23 18:52:39

SAP WebService发布实战:从RFC函数到WSDL的完整指南

简介&#xff1a;面向SAP实施顾问与开发人员&#xff0c;围绕ERP系统间接口集成场景&#xff0c;讲解如何在SAP中发布和调用Web Service。文档以docx格式提供&#xff0c;共1个文件&#xff0c;压缩包约1.64MB&#xff0c;内容包含完整操作截图与关键配置说明。文档从SE37创建函…

作者头像 李华