简介SCMA稀疏码分多址是5G非正交多址接入中的热点技术这份MATLAB仿真资源面向通信工程学生与算法研究者用于快速开展SCMA编解码与多用户检测仿真。压缩包共11个文件以9个m脚本为主要内容分别对应编码映射、MPA/SD-MPA/DS-MPA等迭代译码算法以及误码率统计模块另有md与txt说明文档辅助理解代码结构与参数设置。包体仅10KB代码精简、无冗余依赖适合直接在MATLAB中运行也方便按需修改星座映射、迭代次数等参数进行扩展实验。目前已有274人学习下载适合初涉非正交多址技术、希望结合具体程序理解SCMA原理的读者。通过阅读源码可掌握稀疏码字生成、因子图构建、对数域消息传递等关键实现技巧并对比不同检测算法的性能与复杂度为后续研究编码优化或低复杂度接收机设计提供可复用的实验基础。1. SCMA仿真代码怎么用先让BER曲线掉下来再谈优化SCMA仿真这活儿说难不难说容易也容易翻车。我最早在MATLAB里跑SCMA稀疏码分多址仿真时最深的感受就是讲原理的文章一大堆能直接跑出BER曲线的MATLAB代码却没几套尤其是编码器、检测器、画图脚本齐全、改几个参数就能换场景的那种。这套scma-simulation提供的就是这个从scmaenc.m编码、MPA/PM_MPA/SD_MPA/DS_MPA四种检测策略到draw1.m出图一条链路完整闭环。适合两类人一是刚入门做多址接入研究、想先把非正交接入链路跑通的新手二是要做性能评估、需要一份能改码本、改用户数、改迭代次数的底座代码而不是每次从零开始搭台的工程师。2. 拆开scma-master文件分工、编码器与码本设计的底层约定2.1 六个文件三块功能链路是怎么拼起来的先说结论这套代码的结构比我预想的干净没有把几百行全塞进一个文件里而是按功能拆成了函数和脚本。拿到压缩包解压出scma-master文件夹先别急着运行把文件列表过一遍。仿真链路本质上是三段——发端编码、信道传输、收端检测再加上数值工具和画图工具两个辅助。我把文件按角色整理成下面这张表对照看不容易乱文件名角色在仿真链路里的位置simulation.m主仿真脚本顶层入口比特生成→编码→信道→检测→BER统计scmaenc.mSCMA编码器把比特映射为稀疏码字并在K维资源上叠加MPA.m标准MPA检测器对数域消息传递内部调用log_sum_exp.mPM_MPA.m概率域MPA变体直接在概率域做消息乘加SD_MPA.m列表球面MPA用候选码字收缩降低复杂度DS_MPA.m动态调度MPA重排消息更新顺序加速收敛log_sum_exp.m数值工具稳定计算log(sum(exp(x)))防溢出interation_num.m迭代对比脚本批量跑不同迭代次数的BER对比draw1.m画图脚本输出BER曲线和星座图readme.txt / README.md说明文档版本记录、运行注意事项第一件要做的事找到simulation.m确认它是唯一的顶层入口。其他文件要么是它调用的函数要么是独立实验脚本直接单独运行大概率报错。readme.txt和README.md两份说明文档我建议都打开看一遍README.md一般记录代码结构readme.txt往往写版本和改动点两个都不长几分钟过完能省下后面几个小时猜谜时间。很多人第一次跑不起来的真正原因不是代码有bug而是路径没对上。MATLAB默认工作目录不对脚本里相对路径引用的文件就找不到。所以运行之前先把scma-master整个文件夹加进MATLAB路径右键文件夹 → 添加到路径 → 选定文件夹再加到当前文件夹双保险。2.2 SCMA码本6用户4资源的经典配置与因子图约束SCMA和LDPC有个相似的地方都建在因子图上。这套仿真最常用的配置是J6个用户、K4个时频资源每个用户只占用其中2个资源N2。码本本身是一个K×M×J的三维数组K是资源数M是每个用户星座点数J是用户数。第j页的K×M矩阵就是第j个用户的码本每一列是一个候选码字。由于稀疏性每个码字的K个位置里只有N2个位置上有非零星座点其余全是0。正是这个大部分位置为0的特性让不同用户的叠加信号在接收端还有分离的可能。因子图描述了码字和资源的连接关系我习惯用矩阵表示行是资源列是用户% 6用户4资源的因子图矩阵0表示无连接1表示连接 F 1 1 1 0 0 0 1 0 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1每列恰好两个1对应每个用户占用2个资源每行三个1对应每个资源上叠加3个用户的信号。这个结构直接决定MPA里资源节点的计算量穷举组合数是M的3次方而不是M的6次方。改用户数时必须同步改因子图矩阵这是后面第5章要重点讲的坑。码本设计方面SCMA的性能上限其实是在发端就定死的。常见做法是先生成一个低维母星座比如QPSK星座点0.5±0.5j然后通过旋转、置换、缩放为每个用户生成独立码本目标是让叠加后星座点的最小欧氏距离尽可能大。代码包里应该已经有生成好的三维码本数组直接用就能跑想研究码本对性能的影响就只改码本的旋转角度或星座间距其余不动对比BER曲线的变化这是在复现SCMA论文时最常用的实验维度之一。2.3 scmaenc.m里发生了什么比特到码字的映射与叠加编码器的任务总结成一句话每个用户把log2(M)个比特映射成自己码本里的第m个码字再把这J个码字按因子图位置填进K维发射向量叠加成最终的发送信号。核心逻辑在下面这段代码里% scmaenc.m 主干逻辑示意代码具体参数以源文件为准 function tx scmaenc(bits, codebook, J, N) % bits: J×log2(M) 每行是一个用户的比特 % codebook: K×M×J 第三维区分用户 K size(codebook, 1); % 资源数经典配置为4 M size(codebook, 2); % 码字数星座点数 tx zeros(K, 1); for j 1:J idx bi2de(bits(j, :), left-msb) 1; % 比特转码字下标 tx tx codebook(:, idx, j); % 稀疏码字叠加 end end几个细节值得展开。第一codebook(:, idx, j)取出的是用户j的第idx个码字这个码字是K×1的列向量里面只有N个非零元素第二idx的范围是1到M对应星座图的M个点比特到下标的映射顺序必须和接收端解映射保持一致否则性能直接对不上第三叠加是无权重的直接相加如果想引入功率控制或衰落系数就把最后一行改成tx tx h(j) * codebook(:, idx, j)h是用户j的信道增益。注意bi2de是Communications Toolbox里的函数。机器上没装这个工具箱时仿真一运行到编码就报错。替换方案很简单log2(M)若等于2用bits(j,1)*2 bits(j,2) 1就能得到idx两行代码的事。跑通AWGN之后往衰落信道扩展也是在这段代码周围动手发送端给每个用户乘上独立的瑞利信道系数接收端把y sum(h_j * x_j) n作为观测。这时候你会看到SCMA真正的看点六个用户在相同资源上叠加经过深度衰落之后检测器还能不能把用户区分开。这部分扩展做完整套代码就不只是教学demo而是可以支撑一篇小论文的仿真平台了。3. 三种MPA检测器怎么选消息传递细节与性能差异3.1 MPA在因子图上到底在传什么消息接收端拿到的是所有用户叠加后的K维向量y理论上最优解是联合最大后验概率检测但6用户×M种码字的联合搜索在M4时是4096种组合M16时直接爆炸。MPA把联合搜索拆到因子图上通过节点之间的消息传递近似逼近最优解。MPA更新的是两种消息用户节点发给资源节点的消息用户认为各码字的概率和资源节点发给用户节点的消息资源节点根据叠加约束和接收值计算的各码字条件概率。一个完整的迭代分两步第一步每个资源节点收集与之相连的所有用户的消息枚举这些用户码字的所有组合按叠加方程算出条件概率送回给用户节点第二步用户节点把它连接的多个资源节点送来的消息相乘、归一化更新自己对码字的信念。迭代若干轮后把信念转成每个比特的LLR对数似然比硬判决得到比特估计。这里最容易低估的是资源节点更新。每个资源上叠加3个用户、每用户M个码字穷举M^3种组合M4是64种M16是4096种。乘以资源数4、乘以迭代次数再乘以帧数这个量级决定了仿真时间。MPA.m作为基准版本做的是完整穷举后面的SD_MPA和DS_MPA本质上都是在想方设法砍掉这M^3里的低概率项。3.2 PM_MPA、SD_MPA、DS_MPA各自在改什么代码包里MPA之外还有三个变体第一次看容易懵其实它们改的维度完全不同PM_MPA把对数域消息改成概率域消息。标准MPA在对数域运算防数值下溢PM_MPA直接在概率域做乘加省去了log和exp的来回转换代码更直观但消息概率动态范围一大就容易下溢更适合当教学版本看。SD_MPA基于列表球面译码的MPA。资源节点不再穷举M^3种组合而是先按欧氏距离挑出每个用户最可能的L个码字只在L^3的子集里做消息更新。L远小于M时复杂度下降明显代价是在低SNR下可能把真正的最优组合提前裁掉。DS_MPA动态调度的MPA。标准MPA每一轮按固定顺序更新资源节点DS_MPA根据上一轮消息的不确定性排序优先更新收敛慢的节点。相同迭代次数下BER通常更好代价是每轮多了一点排序开销。检测器主要改动复杂度典型适用场景MPA基准对数域穷举高小规模对比基准PM_MPA概率域消息中教学演示、概率域数值观察SD_MPA候选码字收缩中低M较大、高SNR范围DS_MPA消息更新调度中迭代次数受限的场景选型我的建议是先用MPA跑通拿到基线BER再换SD_MPA看复杂度收益最后用DS_MPA看同样迭代次数下能多压一点BER。不要一上来就追求最复杂的检测器因为后续的坑大概率出在数值和维度上而不是出在检测算法本身。3.3 迭代次数和收敛用interation_num.m画收敛曲线MPA的迭代次数是唯一一个调了必然影响BER曲线的参数。迭代太少消息在因子图上扩散不充分低SNR段出现误码平台迭代太多曲线不再往下走白烧CPU时间。interation_num.m这个脚本就是为量化这个收敛行为准备的。% interation_num.m 的核心结构示意 iter_list [2, 5, 10, 20, 30]; for i 1:length(iter_list) for snr snr_list ber(i, snr_idx) run_single(snr, iter_list(i), MPA); end end跑完会发现两条规律第一所有SNR点大致在同一迭代次数比如10次左右进入收敛区第二低SNR点需要的迭代次数比高SNR点更多因为消息概率分布更平坦传播得更慢。我的习惯是先固定2个SNR点一个低一个高按[2,5,10,20]个迭代次数跑一轮找到BER相对上一次改善小于5%的那个迭代次数作为默认值再用这个值去铺整条BER曲线能省下大量仿真时间。提示判定收敛别只看误码率绝对数值。BER本身是统计量帧数不够时波动很大建议在固定帧数下看两条曲线一次把迭代次数从2加到30另一次把帧数从100加到5000如果BER曲线不再随帧数明显抖动说明统计量足够这时再判断迭代次数的拐点才有意义。4. 把simulation.m跑起来环境检查、参数修改与第一张BER曲线4.1 运行前检查版本、路径、工具箱这套代码对MATLAB版本不太挑R2019b及以后的版本基本都能跑。真正要检查的是三样路径、工具箱、当前目录。先做一个环境自检把最常见的问题一次性暴露出来% 环境自检脚本放在scma-master文件夹内运行 cd(fileparts(mfilename(fullpath))); % 切到脚本所在目录 assert(exist(scmaenc.m, file) 2, 编码器文件缺失确认路径已添加); assert(exist(MPA.m, file) 2, 检测器文件缺失确认路径已添加); if exist(bi2de, file) ~ 2 warning(未找到bi2de需要Communications Toolbox或手动替换); end disp(环境检查完成);这段代码里有几个点值得解释。exist函数第二个参数传file是检查文件是否在MATLAB搜索路径上返回值2表示找到了普通文件mfilename(fullpath)返回当前脚本的完整路径配合cd就能保证无论从哪个目录启动MATLAB工作目录都切到代码包里。bi2de的问题我在第2章提过这里直接用warning暴露出来避免等跑进编码器才报错。工具箱检查还有一种更快的办法在命令行窗口输入ver看输出列表里有没Communications Toolbox。没有的话要么装工具箱要么把bi2de替换成自己写的函数。个人建议直接替换因为SCMA仿真本身对通信工具箱依赖极少核心函数用基础MATLAB就都能实现。另外提醒一句如果换了MATLAB版本尤其是2023b之后的新版遇到脚本路径相关报错别急着怀疑代码先确认工作目录和当前文件夹是否一致。新版MATLAB对相对路径的行为有点变化这是这类代码包最容易莫名翻车的点。4.2 主仿真流程与输出解读simulation.m的流程是经典通信仿真的四板斧参数定义、批量蒙特卡洛循环、BER统计、画图。我把它简化成下面这段可读性更好的结构% simulation.m 核心循环简化示意 snr_list [0, 2, 4, 6, 8, 10, 12]; for snr snr_list noise_var 10^(-snr/10); % 复数AWGN噪声方差 err 0; total 0; for frame 1:num_frames bits randi([0 1], J, log2(M)); % 每用户随机比特 tx scmaenc(bits, codebook, J, N); % SCMA编码叠加 rx tx sqrt(noise_var/2) * (randn(K,1) 1j*randn(K,1)); [bhat, llr] MPA(rx, codebook, noise_var, max_iter); err err sum(bhat(:) ~ bits(:)); total total numel(bits); end ber(snr_idx) err / total; end semilogy(snr_list, ber, o-);逐行看关键点。noise_var 10^(-snr/10)是复数域信噪比换算SNR定义为每符号平均能量除以噪声功率复数高斯噪声的实部虚部各占一半方差所以加噪声时用sqrt(noise_var/2)分别乘randn的实部虚部。MPA的输入除了接收向量rx和码本还有噪声方差noise_var——它直接影响资源节点算条件概率时的高斯分布宽度给错方差BER曲线会整个偏移。最后用semilogy画对数纵轴的BER曲线是通信论文的标准画法。跑起来的预期输出命令行窗口逐行打印当前SNR和BER数值上低SNR时BER在10^-1附近SNR升高逐步降低。BER统计量需要足够比特数支撑经验值是用num_frames和J、log2(M)乘出来的总比特数至少上万否则高SNR段的BER值抖动很大。如果某个SNR点输出NaN或Inf别急着调参先单独把这个点跑一遍定位数值问题最常见的源头在下一章要讲的log_sum_exp。4.3 改参数的四条惯用套路绝大多数场景下拿到这套代码要做的不是重写而是改参数。我按踩坑频率排了四条第一改SNR范围。直接改snr_list数组即可但注意高SNR段BER低于10^-4之后每个点需要更多比特才能统计出非零误码数建议把num_frames同步加大否则曲线末端会抖动得像锯齿。第二改用户数J。从6改成4或8表面是改一个数字实际要动三处码本第三维的尺寸、因子图矩阵的列数、scmaenc里的循环范围。三处不同步几乎必然报维度错误。我一般先写一段代码打印size(codebook)、size(bits)、size(F)确认对齐再跑。第三改迭代次数max_iter。这是最安全的改动不影响维度只影响性能和耗时配合interation_num.m找收敛拐点即可。第四换检测器。统一接口的写法是% 换检测器的通用写法示意 detector DS_MPA; % 可选 MPA / PM_MPA / SD_MPA / DS_MPA f str2func(detector); [bhat, llr] f(rx, codebook, noise_var, max_iter);这样写的好处是后续做批量对比时外层套一层for循环遍历detector元胞数组就行主脚本结构不用动。顺带一提把M从4改成16时比特维度和MPA的候选码字集合都要跟着变这个联动是最容易漏的地方。改完先用一行代码size(bits)确认维度再跑完整仿真。5. 避坑指南SCMA仿真最常见的五个翻车现场跑SCMA仿真的人十个有九个最后都折在数值和维度上而不是算法理解上。下面这五条是我在这类代码上反复踩过的坑每条都按现象、原因、解决三步写碰到直接对号入座。5.1 现象编码器输出和检测器输入维度对不上报错信息基本是Matrix dimensions must agree或下标越界。原因九成是改了用户数或码本维度时没有同步更新因子图矩阵。比如J从6改成8codebook第三维变成8但因子图矩阵还是4×6编码器按8个用户循环传给MPA后资源节点按6个用户列做消息聚合两边对不上必然崩。解决以scmaenc.m的输入为准做自检。在simulation.m开头加一行assert(isequal(size(codebook,3), J), 用户数与码本维度不一致)再写个循环打印因子图每列的和确认每列等于N。维度自检过了90%的报错能提前拦住。我个人的习惯是改任何维度参数前先把旧的size打印出来存个变量改完对比一次一目了然。5.2 现象BER曲线在高SNR段突然上翘或消失现象是低SNR段曲线正常下降到了高SNR段突然出现NaN、Inf或整条曲线断掉。原因几乎都是数值下溢/溢出MPA消息里大量exp运算SNR一高消息概率趋向极端直接写log(sum(exp(x)))时exp(x)要么变成inf要么变成0log一算就是NaN。解决统一走log_sum_exp.m做对数域累加实现上先减最大值再指数function y log_sum_exp(x) m max(x(:)); y m log(sum(exp(x - m))); end这条称为数值稳定改法。凡是MPA相关代码里出现log(sum(exp(...)))的地方一律换成这个函数高SNR段的曲线就回来了。这个坑是SCMA仿真里最隐蔽的因为它只在高SNR下出现低SNR一切正常非常容易让人误以为是算法在高SNR下失效。5.3 现象迭代次数从5改到50BER几乎没有改善这不是bug是消息已经收敛再迭代也榨不出增益。常见做法是先用interation_num.m跑一条迭代次数vs BER曲线找到BER相对上一档改善小于5%的那个点作为默认值。反过来如果低SNR段出现明显误码平台且随迭代次数上升持续存在问题多半在别处——比如噪声方差用错了或者码本本身设计得叠加后最小距离太小。碰到这种情况就别再和迭代次数较劲了回第4章查noise_var的换算或者去看码本的距离分布。5.4 现象把QPSK换成16-QAM码本后性能反而变差很多人觉得星座点数越多性能越好在SCMA里完全不是这回事。码本性能取决于叠加后星座点的最小欧氏距离而稀疏叠加会把星座点压缩盲目增大星座可能让叠加后的最小距离变小。常见误用是只替换星座映射层、保留原旋转参数结果旋转角是为QPSK调的换到16-QAM完全不匹配。解决换星座时把码本生成里的旋转角度、功率归一化因子一起调逐项对比BER。判断码本好坏有个简单办法打印叠加后码字的距离分布统计最小距离距离小的那一版BER必然差这在修改前就能预判。简单说在SCMA里星座大小不是性能指标码字间距才是。5.5 现象draw1.m画出来的图是空的debug时最容易忽略的一种假报错命令行没有报错但figure窗口里什么都没有或只有坐标轴。原因极可能是BER矩阵里存了NaN或Infsemilogy遇到非有限值会直接跳过不画。解决在统计BER之前强制做一次isfinite检查把非有限值替换成占位值同时确认画图前figure和hold on都已设置多条曲线用不同线型区分。这个坑虽然浅但它经常在把前四条都查完之后才被发现所以放在最后提醒。排查顺序建议反过来先看数据里有没有NaN再看维度再看迭代再看码本最后才怀疑算法本身。6. 一台检测器对比实验从一份代码到一套验证方法跑通了基础链路之后我很推荐做第一件事固定一个SNR点把MPA、SD_MPA、DS_MPA三种检测器在同一条件下各跑一遍记录BER和运行时间。这个实验不改变算法结构只是把代码包里已有的资源拼起来用但对理解这套代码的边界非常有效。% 检测器对比示意脚本 snr_fixed 8; noise_var 10^(-snr_fixed/10); for det {MPA, SD_MPA, DS_MPA} f str2func(det{1}); tic [bhat, ~] f(rx, codebook, noise_var, 10); t toc; fprintf(%s: BER%.4e, time%.2fs\n, ... det{1}, mean(bhat(:) ~ bits(:)), t); end跑完你大概率会看到MPA最慢但BER最好SD_MPA快一些且在8dB这个SNR点可能几乎无损DS_MPA在10次迭代下BER接近MPA但耗时更短。把SNR分别放到2dB和12dB各做一次能总结出每个检测器适用的信噪比区间——这套结论在写报告或论文时可以直接用。顺手把迭代次数也加到对比变量里你手上就有一张完整的检测器×迭代次数×SNR三维对比表这也是判断代码包边界最直接的方式。那一次我在MPA和SD_MPA之间反复横跳最后发现是SNR点数太少导致对比结论完全失真之后我就给自己立了规矩凡是拿到新的SCMA代码包第一件事固定SNR跑一遍检测器对比再用interation_num.m确认迭代收敛拐点把改动前的结果截图存档然后才动码本和参数。从那以后每次改码本、改信道我都强制走一遍这个流程省了不少深夜排错的时间。这套scma-simulation资源直接解压就能用文件清单就是第2章那张表按第4章的检查步骤跑一下午就能拿到自己的第一张SCMA BER曲线。希望帮到你。本文还有配套的精品资源点击获取