资讯动态

SD-MPA算法详解:SCMA接收机降复杂度检测实现与参数调优

发布时间:2026/10/18 16:32:10 来源:尧图企业网站定制
简介面向SCMA系统与消息传递算法研究者的一份MATLAB仿真实现聚焦瑞利信道下采用软判决消息传递算法SD-MPA的多用户检测问题。压缩包内代码完整覆盖从稀疏编码、信道仿真到迭代检测的闭环流程可用于复现不同迭代次数如6次对误码率性能的影响并在此基础上扩展参数实验。包体共4个文件均为.m脚本整体仅4KB轻量易读simulation.m搭建主仿真框架scmaenc.m完成用户稀疏编码SD_MPA.m实现核心检测迭代log_sum_exp.m提供对数域数值稳定计算。已有323人学习适合通信工程专业学生或无线接入技术初学者作为入门与调试参考。通过阅读这些代码可直观理解SCMA的稀疏扩频原理及概率消息传递的收敛过程也可为后续改进检测器或对比不同算法提供可运行的基础版本。1. 为什么SCMA接收机的复杂度会卡在检测这一步做SCMA链路仿真时几乎每个团队第一次撞墙都在接收机检测这一步。发送端很好办用户选码字、映射到资源块、叠加发送一句话就能说清楚。但接收端要从过载的叠加信号里把多个用户一层层解出来完整的最大似然检测复杂度随码本尺寸和每资源块关联用户数指数增长码本稍微大一点仿真时间就从分钟变成小时。这也是为什么MPA能成为SCMA的默认检测方案——它在因子图上做置信传播把全局枚举拆成局部合并。SD-MPASparse Detection-Message Passing Algorithm则更进一步在消息传递前面套一层深度优先搜素和路径度量剪枝用可控的误码性能损失换一个量级的复杂度下降。这篇笔记面向已经搭好SCMA发送端、想在接收检测上落地的从业者给出SD-MPA的最小复现路径、参数标定方法和实现时的具体坑。2. SCMA与MPA的关系稀疏结构是优势也是复杂度来源SCMA区别于传统多址接入的核心是稀疏码本和因子图结构。每个用户只占全部资源块中的一小部分典型配置是K个资源块承载J个用户每个用户映射到dv个资源块系统过载率λ J / K大于1。稀疏性让每个资源块上只有少数用户的信号叠加这给了接收机“局部检测”的可能性但另一方面只要用户数J增大、码本尺寸M增大即使被稀疏性约束到局部检测要搜索的组合数仍然增长得非常快。2.1 因子矩阵决定过载率也决定检测难度SCMA的稀疏性由因子矩阵F表达它是一个K×J的0-1矩阵F(k,j)1表示用户j在资源块k上有非零映射。例如K4、J6的配置里6个用户共享4个资源块过载率1.5。每个用户对应的列有dv个1每个资源块对应的行有若干个1这个数值直接决定检测的枚举复杂度。行上的1越多接收端在单个资源块上需要联合考虑的用户就越多列上的1越多单个用户被分散到更多资源块上消息在因子图上的传播路径就更长。仿真里最常用的dv2配置每个资源块只有两个用户叠加但这个“小”局部结构在M64时一个资源节点要枚举64×644096种组合这个数字在迭代检测里会被反复计算。因子图里还有一层容易被忽略的信息资源节点向用户节点传递的消息需要把所有连接用户的码字组合全部代入距离度量。dv2时还可以靠枚举硬扛一旦用到dv3或dv4的码本设计枚举组合数直接跳到M的三次方、四次方复杂度曲线几乎垂直上升。这也是为什么SCMA码本设计文档里很少推荐dv太大的原因检测端撑不住。设计检测方案前先看因子矩阵的行重行重4以上基本已经不适合拿原始MPA做实时链路了。2.2 MPA把全局枚举拆成局部合并但乘积累进仍然很贵MPA的核心思想是“把联合检测拆成因子图上的局部消息”它不试图一次性估计所有用户的码字而是让每个资源节点和用户节点反复交换概率消息。资源节点拿到连接用户传过来的先验消息后把所有相关用户的码字组合代入高斯距离核计算出新的后验消息再传回用户节点。这个过程的数学形式看起来很优雅但实现时每一步都要对M^dv个组合计算欧氏距离和指数函数。按乘法计数一个资源节点一轮迭代大约要做M^dv × dv次复数乘加加上exp计算6轮迭代下来乘法开销放大6倍。这里有个常见的误解以为MPA已经比最大似然快很多复杂度就没问题。MPA相比全局穷举确实把指数级降到了局部指数的集合但对实时仿真或硬件实现来说M64、dv3、K8的配置一轮迭代仍要处理上万个距离度量。在对误码性能要求不高的场景很多人会选择减少迭代轮数省时间但这样会牺牲收敛性高信噪比下容易出现错误地板。正确思路不是砍迭代而是从根上减少每个资源节点需要枚举的候选点数量。2.3 降复杂度方案怎么选对数域、串行更新还是SD-MPA降MPA复杂度有好几条路。对数域MPA是省掉exp函数把它变成查表或加减法但枚举量没有变只省了计算开销串行MPA改变消息更新顺序让信息在因子图上传播更快典型效果是迭代轮数从6轮降到3轮枚举量仍然没变SD-MPA走的是另一条路用深度优先搜素找到距离度量较小的候选码字组合只在这些候选上做消息更新枚举量直接变小。下面把三种方案做个对比。方案核心思路性能影响实现复杂度对数域MPA转换消息域省指数和除法基本无损低改动小串行MPA加速消息收敛减少迭代轮数轻微损失中需重排更新顺序SD-MPA剪枝候选点降低单轮枚举量损失可控高需设计候选生成逻辑从工程角度看如果你的码本M不大128以下、dv2直接跑原始MPA就行做点向量化优化就能接受真正需要SD-MPA的场景是M到256、dv3以上或者需要把仿真时间从小时压到分钟。另一个使用场景是硬件原型验证FPGA上算力有限MPA的乘法器消耗太高SD-MPA把候选列表控制在小范围内资源节点端的累加器规模可以显著缩小。SD-MPA本质上是用“更聪明的枚举顺序”换来“更少的枚举次数”所以它能和上面两种MPA变体叠加使用先做树搜索剪枝再换对数域做消息更新效果不冲突。3. SD-MPA的最小可运行实现从码本映射到剪枝检测的核心代码这一章直接给出能在MATLAB里跑通的最小实现。链路按最常见的下行SCMA模型搭建K4个资源块、J6个用户、dv2、M64。代码目标是验证SD-MPA的候选点剪枝是否真的能把枚举量压下来同时保持误码率在一个可接受范围。我没有在这里放完整工程文件重点是可复现的检测核心逻辑。3.1 发送端因子矩阵、码本与叠加信号发送端的做法在多数SCMA仿真代码里大同小异生成每个用户的K×M码本按因子矩阵把用户符号映射到对应资源块上叠加。码本可以用随机高斯方式生成后续再做能量归一化这一点影响噪声方差标定坑在避坑章里会详细说。下面代码包含码本生成和一次符号发送。% scma_tx_demo.m M 64; % 码本大小 K 4; % 资源块数量 J 6; % 用户数量 dv 2; % 每个用户占用的资源块数量 % 因子矩阵 F: K x J1 表示用户 j 在资源块 k 上有非零映射 F [1 1 1 0 0 0; 0 0 1 1 1 0; 1 0 0 1 0 1; 0 1 0 0 1 1]; % 每个用户的码本是一个 K x M 复矩阵 for j 1:J cb(:,:,j) (randn(K,M) 1i*randn(K,M)) / sqrt(2); cb(:,:,j) cb(:,:,j) / norm(cb(:,:,j), fro) * sqrt(K*M); end % 随机选一个传输符号 sym_idx randi(M, J, 1); tx zeros(K, 1); for j 1:J tx tx F(:,j) .* cb(:, sym_idx(j), j); end % 加复高斯噪声snr_db 为信噪比 snr_db 12; sigma2 10^(-snr_db/10); noise sqrt(sigma2/2) * (randn(K,1) 1i*randn(K,1)); rx tx noise;这段代码里因子矩阵F第1行有3个1第2行有3个1第3、4行各2个整体过载率1.5每个用户按列取2个资源块。码本用Frobenius范数做归一化是为了让每个用户的总发送功率一致否则噪声方差和剪枝门限会对不同用户产生不同的相对效果。sigma2直接由snr_db换算没有额外乘码本功率因子因为码本已经归一化。实际链路里信道如果不是单位阵需要把H的能量也纳入归一化这点很多仿真里容易漏。3.2 候选点生成深度优先搜素加路径度量剪枝这是SD-MPA和普通MPA差异最大的地方。普通MPA在资源节点上枚举该资源块所有连接用户的全部码字组合SD-MPA用一个树搜索代替这个枚举树的每一层对应一个用户每一条从根到叶的路径代表一组候选码字组合。搜索时用部分累积距离作为路径度量如果当前部分距离已经超过“当前最优路径距离 剪枝偏差”就把这个分支剪掉不再往下扩展。为了提升剪枝效率先对每个用户的码字按匹配滤波距离排序让更有可能的码字先被访问。function [cand, dist] gen_sd_list(rx_k, h_k, cb_k, Q, dev) % rx_k : 资源块 k 的接收值 % h_k : 该资源块上 dv 个用户的信道系数 % cb_k : dv x M 的码字切片 % Q : 每层最大分支数 % dev : 剪枝偏差 dv size(cb_k, 1); M size(cb_k, 2); % 匹配滤波得分假设单个用户单独传输距离越小优先级越高 score zeros(M, dv); for u 1:dv for m 1:M score(m, u) abs(rx_k - h_k(u) * cb_k(u, m))^2; end end [~, rank] sort(score, 1); % rank(:,u) 是用户 u 的候选码字优先级 % 深度优先搜素 stack struct(depth, 0, idx, [], acc, 0); cand []; dist []; best inf; while ~isempty(stack) node stack(end); stack(end) []; if node.depth dv dist(end1, 1) node.acc; cand(end1, :) node.idx; best min(best, node.acc); continue; end u node.depth 1; branches min(Q, M); for q 1:branches m rank(q, u); new_idx [node.idx, m]; % 用当前路径上的用户重新计算残差 res rx_k; for d 1:u res res - h_k(d) * cb_k(d, new_idx(d)); end acc abs(res)^2; % 剪枝部分距离超过当前最优加偏差就放弃该分支 if acc best dev break; end stack(end1) struct(depth, u, idx, new_idx, acc, acc); end end % 按距离排序只保留前 Q 个候选组合 if ~isempty(dist) [dist, order] sort(dist); cand cand(order(1:min(Q, end)), :); dist dist(1:min(Q, end)); end这个函数里rank的排序顺序是剪枝能否生效的关键。匹配滤波分数低的码字先进入树搜索真实最优路径大概率在早期就被访问到best很快被压低后面的分支更容易触发acc best dev这条剪枝条件。dev设成0时严格只保留当前最优路径候选点会非常少性能风险大dev设大时剪枝几乎不生效退化成枚举。Q参数限制了每层最大分支数等于给搜索宽度套了一个上限防止某个用户的所有码字得分都很接近时搜索树膨胀。这个函数返回的cand就是后续消息更新要用到的候选码字组合。注意这里把cand截断成前Q个如果实际搜索结果不足Q个按实际数量使用。3.3 在候选列表上做消息迭代拿到候选列表后资源节点不需要再对全部M^dv组合更新只对候选点计算高斯核与先验消息乘积再把概率累加到对应用户的码字上。这一步和原始MPA的区别相当于把“全枚举”换成了“稀疏枚举”消息更新的公式不变。下面代码实现一轮MPA消息更新。function msg mpa_update_on_sd_list(rx, h_k, cb_k, cand, dist, prior, M, sigma2) % cand : Nc x dv候选码字组合 % dist : Nc x 1对应组合的欧氏距离 % prior: dv x M用户节点传入的先验消息 Nc size(cand, 1); dv size(cb_k, 1); % 每个候选组合的高斯核乘以先验乘积 w exp(-dist / sigma2); for p 1:Nc pri 1; for u 1:dv pri pri * prior(u, cand(p, u)); end w(p) w(p) * pri; end % 累加回用户消息 msg zeros(dv, M); for p 1:Nc for u 1:dv msg(u, cand(p, u)) msg(u, cand(p, u)) w(p); end end % 归一化防止迭代中数值发散 for u 1:dv msg(u, :) msg(u, :) / (sum(msg(u, :)) eps); end这段代码里先把距离转成似然权重再乘以用户节点传过来的先验消息。严格的外消息MPA要求计算用户u的后验时排除用户u自己的先验上面的实现是简化合并版把所有用户的先验都乘进去了。在迭代轮数较多时两者差异很小但如果你想和别人的性能曲线严格对齐需要按排除当前用户的方式再修正一下做法是乘prior时跳过u最后归一化。sigma2在消息更新里直接控制高斯核的锐度取值必须和发送端噪声加载一致否则高信噪比下容易出现性能反而不如低信噪比的怪现象。主循环里把gen_sd_list和mpa_update_on_sd_list交替调用就可以完成SD-MPA迭代。一个值得注意的细节是每次迭代后可以用上一个迭代输出的最优先验更新匹配滤波得分的排序也可以固定第一次排序不再更新。前者性能略好后者节省排序开销如果因子矩阵和码本固定不变首个迭代的排序已经包含了大量信道信息后续改动效果有限工程实现里多数直接复用首次排序。4. SD-MPA前期必调的三个参数剪枝门限、搜索顺序与迭代轮数SD-MPA里最难调的不是算法结构而是几个参数之间的权衡。候选点越多性能越好但复杂度也越高剪枝偏差越小候选越少但可能把真正最优路径剪掉。不同链路对SNR范围、码本尺寸和实时性要求不同参数不会一套通吃。4.1 剪枝门限dev性能与复杂度的分水岭dev是SD-MPA最直接的旋钮。dev0时只保留当前最优路径相当于硬判决复杂度最低但每个资源块候选数量常常只有一条路径消息更新退化成近似单路径检测性能明显变差。dev从0逐步加大候选路径慢慢增多误码率曲线逐渐贴近穷举MPA但dev增加到一定值后剪辑条件形同虚设候选数量回到M^dv量级复杂度又回到原点。仿真调参时不要直接猜先跑一次无剪枝的MPA拿到性能基准再写一个统计脚本打印每个资源块的平均候选数量。我一般把dev从2.0开始往下调当平均候选数量降到M的一半以下再观察误码性能如果损失小于0.3dB就继续收紧。下面给出一组经验范围。参数常见取值影响方向dev0严格剪枝候选少复杂度最低性能风险大dev0.5~2.0常用区间性能损失0.1~0.8dB复杂度下降明显dev3.0接近穷举候选多复杂度接近原始MPA注意dev的单位是线性距离不是dB。很多同学直接把show出来的误码率dB差塞进dev结果剪枝行为完全不符合预期。线性距离和dB差之间需要考虑噪声方差的缩放正确做法是拿真实欧氏距离的绝对值来比。日志里最好同时打印“候选路径数 / 全枚举数”的比值控制在5%到20%区间内这个比值比误码率更能反映算法是否在正常工作。4.2 搜索顺序匹配滤波排序为什么比自然顺序强深度优先搜素的剪枝效率高度依赖初始分支顺序。自然顺序按码本索引排最优码字可能排在很后面导致搜索一开始访问的都是次优路径best迟迟压不下来剪枝条件很少触发。匹配滤波排序预处理成本低每用户只需计算M次距离并排序这个复杂度相对整个MPA迭代完全可以忽略但收益很明显。另一种排序是按上一轮消息的似然值排序用迭代信息反馈修正顺序适合迭代轮数较多的场景付出的代价是每轮都要重新排序候选路径少的时候不值当。第三个选择是随机排序那基本等于放弃剪枝效率只在做复杂度对比基线时才用。排序和后文要讲的Q参数需要一起考虑。Q设得比较大但排序质量差搜索宽度会分给大量次优分支排序好时Q8到Q16就够覆盖dv2的场景。我习惯在代码里把排序函数单独抽出来因为实际调优时经常要比较“排序用匹配滤波”和“排序用上一轮消息”两版的差异混在一起逻辑会很乱。4.3 迭代轮数T3轮还是6轮要看候选质量MPA的迭代轮数影响收敛程度SD-MPA里还要考虑候选列表的交互候选列表质量高收敛会快一些候选列表被剪枝削得很薄可能需要更多轮才能把概率集中到正确码字。常见做法是先固定T6然后对比T3和T6的性能变化如果差异小于0.2dB说明链路对该迭代轮数不敏感把T降下来换取速度。如果T3时性能明显变差通常不是轮数本身的问题而是候选列表漏掉了真实路径剪枝偏紧这时应该先调dev而不是加迭代轮数。迭代轮数适用场景备注3轮候选较密、性能需求宽松仿真速度快误差偏大5~6轮常规链路收敛基本饱和8轮以上恶劣信道、性能优先收益递减且耗时明显三轮迭代对小候选集来说风险较高因为消息还没传遍因子图后验概率容易出现偏移。比较稳的做法是先跑出“T6、dev设定欣赏”的一组结果再逐步降低T把每组结果的候选数量和误码率记录在同一张表里你很快能看到这个具体码本在哪个参数组合处开始退化。5. 把SD-MPA跑在自己链路上的避坑清单如果把SD-MPA直接搬进自己的SCMA链路而不做适配大概率会遇到下面几个问题。我列出的4条每一条都在实际仿真和对比中见过也是读别人的复现代码时最容易踩到的地方。5.1 现象剪枝后误码率比预期高1dB以上原因通常是dev给得过于乐观。很多实现默认dev0或一个很小的固定值在低信噪比下噪声很大距离度量波动剧烈真正正确的码字组合因为路径度量暂时偏大被提前剪掉高信噪比下也会因为多用户干扰让某个用户的分量叠加后距离变大导致漏检。解决方法是先跑无剪枝MPA性能基准再逐步从dev2.0下调。每个SNR点记录平均候选数如果某个SNR下候选数比M还小说明这个点剪枝过度了需要适当放松。不要用固定dev跑完整条SNR曲线信噪比越高噪声方差越小同样的dev值在高低SNR下的剪枝力度是完全不同的。5.2 现象复杂度统计不降反升仿真时间比普通MPA还长这种情形最容易让人怀疑SD-MPA是否真的有效。问题一般出在实现细节上深度优先搜素时逐层循环计算残差里面用了for循环而不是向量化矩阵运算候选列表的排序和大小的计算额外开销没有优化调试时还在循环里打印相关数值。复杂度下降是理论上的枚举量下降但如果你把常量系数搞大了实际运行时间就是会变长。解决方法是把gen_sd_list里的残差计算改成矩阵批量形式一次算出所有分支的acc值剪枝条件用向量比较而不是循环内分支。另外统计复杂度时不要用仿真总时间要单独统计“每资源节点实际访问的候选路径数”和“每次消息更新的乘加次数”这两个指标才能真正反映算法改进是否到位。5.3 现象高信噪比下误码率曲线出现错误地板高SNR时exp(-dist/sigma2)里的sigma2变得很小距离值稍大一点exp可能直接下溢成0所有候选路径的权重都变成零经过几轮迭代消息就全乱了。这种情况下误码率曲线在高SNR段平下来看起来像出现了错误地板。另一个候选原因是候选列表固定后正确路径从第一轮排序起就被放在Q之外后续迭代无论如何都无法把它带回来。解决办法有二一是消息更新改用对数域操作用加减法和最大值替换exp从根上规避动态范围问题二是适当提高Q上限保证排序误差不太大时仍有足够宽的候选空间覆盖真实路径。对后一种原因用上一轮消息做排序重排通常也能缓解但代价是每轮迭代多一次排序。5.4 现象sigma2和码本功率标定不一致剪枝门限在不同SNR段失灵这个坑非常隐蔽。发送端码本做完Frobenius归一化后接收信号的功率和噪声方差必须按同一基准计算有些代码在生成信道时给H乘了一个随机归一化系数但sigma2仍按原功率计算导致距离度量的绝对数值整体偏移。dev是按线性距离写的这个偏移量会直接影响剪枝条件。解决方法是固定一条功率链路码本归一化后先计算接收信号平均功率再按这个功率反推sigma2sigma2 接收功率 / 10^(snr_db/10)。如果信道矩阵不是单位阵把信道能量也纳入进来H的Frobenius范数平方乘上码本功率才是接收信号的真实功率。这个标定做完dev在0.5~2.0区间内的行为才是可预期的。日志里把sigma2和平均候选数一起打印每次调SNR都核对这两个属性很多玄学问题当场就能看出来。6. 工程化收尾对数域消息、软输出与并行化SD-MPA能在仿真里跑通只是第一步真正要用到工程链路里通常还有三件小事要做。第一件是把消息更新从概率域搬到对数域省掉指数和除法这个改动会让高SNR下的数值问题也一并消失。第二件是让检测器输出软信息而不是硬判决接外部的信道解码器或迭代接收机把SD-MPA从单点算法变成系统链路里的一个模块。第三件是在资源块数足够多时做并行化把每个资源节点的候选生成和消息更新摊到多核上。对数域消息的核心很简单概率域消息是各个候选路径似然的加权和对数域需要把乘法换成加法把归一化换成减去最大值。实现上原来msg(u, cand(p,u))累加w(p)现在改成累加exp(-dist/sigma2 log(prior)之和)最后减掉每行的最大值再exp回来。这一步对性能影响几乎为零但对动态范围和运算速度都有直接改善高SNR下的错误地板问题也会被彻底规避因为不再有下溢到零的场景。软输出方面SD-MPA在迭代结束后用户的码字级后验概率已经算好接下来需要把码字内的比特LLR算出来交给解码器。注意码字映射一般是符号级索引bit打散在码本生成时就决定了因此要在生成码本时保存码字与比特的对应表否则后续解码只能做硬判决白白浪费迭代接收机的增益。在常见配置下从硬判决改成软输出给信道解码器能换来大约0.5dB级别的链路增益这个收益做一次就能体会到。最后一步是并行化K4时没必要做K32或更高且候选生成占主耗时可以用MATLAB的parfor对每个资源节点独立跑gen_sd_list因为资源节点之间的候选生成互不依赖天然可以并行。我最早调SD-MPA时总想靠一个完美的dev值把性能提升和复杂度降低同时抓在手里结果在误码率曲线和候选数量统计之间反复横跳。后来形成的习惯是每次仿真都保存“候选路径数比”“误码率损失”“单轮迭代时长消耗”三张曲线参数一改就能立刻看出哪个环节出了问题。先跑通无剪枝版本作为基准再逐步收紧参数到现在都是我最依赖的方法。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑