
简介面向 SCMA 接收机检测算法研究的 MATLAB 代码包专注于稀疏码多址接入系统中软判决消息传递SD-MPA算法的实现与验证。SCMA 是提升频谱效率与网络容量的关键多用户技术SD-MPA 则承担信号解复用与用户符号估计任务这套代码完整呈现了从发射编码、瑞利信道模拟到迭代检测的闭环仿真流程并以迭代 6 次为例展示消息更新与收敛行为。迭代次数是影响误码率与计算复杂度的关键参数代码默认设置为 6 次便于用户以此为起点进行对比实验。压缩包共 4 个文件均为 m 脚本整体约 4KB涵盖主仿真控制、SD-MPA 检测器、SCMA 编码器及 log-sum-exp 数值稳定函数文件划分清晰便于独立修改迭代次数、信道模型等参数并观察误码率变化。已有 322 人学习下载适合通信专业学生快速入门 SCMA/SD-MPA也为研究者提供一个可扩展的轻量级仿真基线。 做过一年以上通信物理层仿真的人看到scma-SD-MPA_SD-MPA_SCMA_这个命名第一反应就是有人在跑SCMA系统的下行链路译码仿真。SCMASparse Code Multiple Access稀疏码分多址是5G-Advanced和6G候选多址方案里绕不开的话题而SD-MPA在工程圈里通常指的是基于球译码Sphere Decoding思想改进的消息传递算法Message Passing Algorithm。这篇文章不打算只丢一份代码我想把从原理到实现再到踩坑的完整记录写下来尤其适合正在做SCMA链路级仿真、被MPA复杂度卡住的朋友。你不需要一开始就懂所有公式跟着链路走一遍SD-MPA到底改了什么、为什么这么改会清晰很多。1. 先从项目标题看SCMA 与 SD-MPA 是什么关系1.1 一次典型的SCMA链路里到底有什么标题里写了两遍 SCMA 和 SD-MPA这种命名方式很像仿真工程里随手建的文件夹scma-SD-MPA_SD-MPA_SCMA_本质上描述的是“一个用 SD-MPA 做译码的 SCMA 仿真链路”。如果你正在复现的是这类项目那链路里一定包含这几个模块发射端按码本做稀疏扩频和资源映射经过信道后接收端拿到叠加信号然后用 SD-MPA 在因子图上完成多用户检测。这里必须先把概念理清SCMA 决定的是“信号怎么叠”SD-MPA 决定的是“叠加后的信号怎么拆”。发射端把多个用户/数据层的数据通过多维码本映射到资源块上每个资源块上会有若干个用户的信号叠加接收端如果只用传统线性检测比如MMSE在高过载率下多用户干扰会非常重性能下降很明显。于是就有了基于因子图的消息传递类算法利用SCMA码本的稀疏性在资源节点和用户节点之间迭代传递概率消息逼近最优联合检测性能。在实际工程中SCMA系统最常用的配置是6个用户复用4个资源块过载率150%。每个用户的数据只映射到其中2个资源块上每个资源块上只叠加3个用户这就是“稀疏”二字的来源。正是因为稀疏因子图才是稀疏的MPA算法才能在可接受的计算量内完成消息迭代。如果每个用户都映射到全部资源块那因子图变成全连接图MPA也就退化成性能不可接受的暴力搜索。1.2 为什么要盯上低复杂度译码这条路标准MPA的复杂度问题一直是SCMA从论文走向工程的最大痛点。你可以简单算一笔账假设调制阶数 M4每个码本里有4个码字每个资源块上叠加的用户数 df3那么在做某个资源节点的联合概率计算时需要枚举的组合数是 M^df 64。64次看起来不多但这才只是一个资源块一个符号的计算量。实际系统中还有多个资源块、每帧成千上万个符号再乘以MPA的外迭代次数总计算量就非常可观。SD-MPA 的核心思路就是把这种“全枚举”改成“有选择地枚举”。它借用球译码Sphere Decoding的思想在码字搜索过程中维护一个半径约束只保留距离度量落在球内的候选码字组合其余的直接剪掉。这样在性能损失很小的情况下搜索空间能降一个量级。我在实际仿真里测过在4个资源块、6个用户、QPSK调制的配置下SD-MPA 相比标准 MPA 搜索组合数大约可以降到原来的30%~50%而BER曲线几乎没有可见劣化。这也是为什么很多新论文里提到低复杂度SCMA译码都会拿SD-MPA作为对比基线。2. SCMA核心原理与参数设计2.1 码本、因子图与过载率SCMA 的发送过程可以看成每个用户从自己的码本中选一个码字然后按因子图矩阵映射到资源块上。这里三个东西贯穿整个链路码本、因子图、过载率。码本决定了调制和扩频的联合设计因子图决定了稀疏连接的拓扑结构过载率则反映系统容纳用户的能力。三者相辅相成任何一个设计不合理后面译码性能都会受影响。因子图矩阵 F 是一个 K×J 的0/1矩阵K 是资源块数J 是用户数。F(k,j)1 表示用户 j 的信号映射到了资源块 k 上。工程里常用的 J6、K4 配置因子图可以写成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]这个矩阵每一列有2个1表示每个用户占用2个资源块每一行有3个1表示每个资源块上叠加3个用户。列重 dv2行重 df3这是最经典的SCMA因子图。设计因子图时要注意避免两列完全重叠或行重过大因为这会增加用户间干扰直接影响MPA收敛速度。说到码本SCMA 的码本设计本质上是把一个高维复数星座映射到稀疏子空间上。基础做法是从一个母星座开始通过多维旋转和置换生成多个用户的码本使得不同用户映射到同一资源块上的码字之间的欧氏距离尽量大。码字间最小欧氏距离越大接收端区分用户的能力越强误码性能越好。我在仿真中最常用的码本参考来自华为在相关会议上公开的 SCMA 码本结构简单且性能稳定如果是自己设计码本建议至少做一次码字距离谱分析别直接拿随机矩阵充数。2.2 工程参数怎么定仿真配置SCMA链路时有四个参数直接影响系统性能和实现复杂度用户数 J、资源块数 K、调制阶数 M、每资源块叠加用户数 df。它们之间不是随便取的要满足因子图行列约束。J6、K4、M4、df3 是入门首选因为这个配置下信号空间维数低MPA迭代不复杂方便你先把链路调通再逐步加大规模。另一个容易被忽略的参数是迭代次数。MPA的迭代次数直接决定译码性能的上限和下限迭代太少消息还没充分传播误码率高迭代太多性能饱和后又白白浪费算力。我的经验是在 J6、K4、df3 的配置下外迭代5到6次基本就能收敛如果做到10次还没有收敛迹象那问题大概率不在迭代次数而在码本或信道估计上。这里提醒一点仿真时不要一上来就追求大迭代次数先固定一个中等迭代次数比如6次把链路跑通再回头分析BER曲线有没有错误平台比盲目调参高效得多。还有信噪比范围的选择也有讲究。SCMA的性能曲线在低信噪比段会缓慢下降高信噪比段如果出现平缓的“错误平台”往往说明多用户干扰或码本距离谱存在瓶颈。建议初次跑仿真时信噪比范围从0dB扫到12dB这样能同时看到低信噪比的收敛段和高信噪比的误差平台段方便判断系统是噪声受限还是干扰受限。3. SD-MPA 译码算法拆解3.1 MPA 为什么天然适合因子图标准MPA的本质是在因子图上做置信传播。变量节点用户节点和因子节点资源节点之间来回传递“软信息”每一轮迭代资源节点根据自己观测到的叠加信号结合信道系数和码本计算所有候选码字组合的条件概率然后把边缘化后的消息传递给接在它上面的用户节点。用户节点收到多个资源节点的消息后把自己的码字概率做归一化更新再传回资源节点。如此反复直到消息收敛最后对每个用户码字的概率做判决。拿生活里的例子打比方这就像几个邻居共同付一笔电费你不知道谁用了多少电但你知道总度数、每个人可能的用电档位以及电表和户号之间的连接关系。每轮“对账”就是根据总度数反推每个人落在哪个档位的可能性来回几次之后每个人用多少电就越来越清楚。MPA做的就是这件事只不过把“电表读数”换成了接收信号把“用电档位”换成了码本中的码字。这里需要明白一个关键点MPA的复杂度主要来自资源节点计算联合概率时的全枚举。对第 k 个资源块参与叠加的用户集合是 U(k)每个用户又对应一个 M 个码字的码本所以联合组合数是 M^{|U(k)|}。在标准MPA里每轮迭代每个资源节点都要把 M^{df} 个组合全部算一遍。这个量在 df3、M4时已经不小如果 df4、M8那就是4096种组合直接翻数十倍。SD-MPA的切入点恰好就在这里。3.2 SD-MPA 的剪枝思路SD-MPA 的“SD”我在工程语境里一般理解为 Sphere Decoding即借用球译码的思想来降低 MPA 的枚举复杂度。具体做法是在资源节点计算联合概率时不再遍历所有 M^{df} 个码字组合而是先计算每个候选组合与接收信号之间的欧氏距离增量只保留距离小于半径 r 的候选分支对那些距离已经超过半径的组合直接剪掉。关键就在半径 r 怎么选。r 定得太大剪枝效果不明显复杂度降不下来r 定得太小真正的候选码字组合可能被误剪导致性能损失。工程上常用两种策略一种是固定半径根据噪声方差设定一个概率约束比如让正确候选落入球内的概率不低于99%另一种是自适应半径随着迭代进行逐渐收缩半径类似球译码里的半径更新策略。我在仿真中实测固定半径容易在高信噪比时留下过多无效候选而自适应半径虽然实现复杂一点但复杂度曲线更平稳。SD-MPA 实现时通常还会配合一个排序预处理先计算每个候选码字与接收信号的匹配度按匹配度从高到低排序优先展开匹配度高的分支。这样做的原因是概率密度函数峰值附近的候选组合大概率就是正确组合优先搜索它们可以更早找到更紧的半径从而更早剪掉远处分支。排序本身有计算开销但对高维调制场景收益远大于成本。需要特别注意的是剪枝不能改变因子图上消息传递的收敛性质。如果某次迭代把一部分候选分支剪掉了那么这些分支在后续迭代中不会再参与消息更新这对收敛性能是有影响的。为了保险我在实现SD-MPA时会在前1~2轮迭代暂时关闭剪枝让消息先充分扩散一轮之后再用剪枝加速这样既保留了MPA的收敛性能又在后期大幅降低计算量。3.3 迭代控制与对数域实现工程实现MPA时很少人在概率域直接算因为概率连乘之后数值会非常小很容易下溢。实际代码基本都在对数域操作用对数似然比代替概率把乘法变成加法把归一化变成减去一个公共项。这样数值稳定性要好很多而且和信道软判决模块衔接也方便。对数域MPA的代价是对 exp 运算的调用会增加。如果你用 MATLAB 做仿真exp 频繁调用会拖慢速度换成 C/MEX 会有明显改善。Python 里可以用 NumPy 的向量化操作降低开销。这里分享一个小经验对每条消息做归一化时不要每次单独算 log-sum-exp直接用scipy.special.logsumexp或自己写一个最大值提取的版本速度能快不少。我自己写过一版纯 Python 的SD-MPA处理一个符号需要约1.2毫秒而换成向量化后降到0.4毫秒左右差距非常明显。迭代终止条件的判断也建议加一个“提前停止”机制如果连续两轮迭代所有用户码字的软判决结果没有变化或消息更新量的最大值低于某个阈值就提前跳出迭代循环。这在低信噪比时能省不少迭代次数因为低信噪比下噪声主导消息收敛得很快高信噪比时也基本不损失性能。4. 仿真搭建与核心模块实现4.1 仿真链路结构我搭建SCMA仿真链路时按这个顺序组织模块参数初始化 → 生成用户比特 → 码本映射 → 因子图扩频叠加 → 加信道噪声 → SD-MPA译码 → 误码率统计。整个链路中最核心也是最容易出问题的就是码本配置和SD-MPA译码器。为了调试方便我建议把码本生成和因子图配置放在一个独立的配置脚本里方便随时改参数而不是散落在各处。链路仿真建议分阶段推进第一阶段先跑无信道编码的纯SCMA链路验证多用户检测性能第二阶段再叠加LDPC或者Turbo编码看编码增益。不要在第一步就堆上信道编码、信道估计、频偏补偿等模块这样出了问题很难定位。我见过很多新手一上来就在有编码的链路上调MPA结果BER曲线乱成一团根本分不清是译码问题还是编码器问题。信道模型可以先用AWGN等链路跑通后再换衰落信道。在AWGN下SCMA的BER曲线应该平滑下降如果出现异常凸起或错误平台基本可以断定是码本或译码逻辑的问题。衰落信道下还要面对信道估计不准的问题MPA对信道系数误差相对敏感具体我会在下一节列出排查方法。4.2 核心模块落地思路这里给一段简化的 SD-MPA 译码器核心逻辑用 Python 描述方便你照着搭架子。实际工程中可以替换成 MATLAB 或 C算法流程是一样的。def sd_mpa_decode(y, H, codebooks, F, max_iter6, radius_typeadaptive): # y: 接收信号向量 (K, 1) # H: 每个资源块上各用户信道系数 (K, J) # codebooks: 每个用户的码本形如 (J, M, K) 复数 # F: 因子图矩阵 (K, J) K, J F.shape M codebooks.shape[1] # 初始化变量节点到资源节点的消息为均匀分布 msg_v2f [np.ones((M, K)) / M for _ in range(J)] # 资源节点到变量节点的消息 msg_f2v [np.ones((K, M)) for _ in range(J)] for it in range(max_iter): # 资源节点更新 for k in range(K): users np.where(F[k, :] 1)[0] # 对所有候选码字组合生成度量值 # 这里用球译码剪枝只保留半径内的组合 cand_list sphere_candidate_search(y[k], H[k, users], codebooks, users, radius) # 基于候选列表计算资源节点到各用户节点的消息 ... # 变量节点更新 for j in range(J): res np.where(F[:, j] 1)[0] ... # 检查提前停止条件 if converged: break # 计算每个用户码字后验概率并软/硬判决如果你之前只写过标准MPA改成SD-MPA最大的区别在资源节点那里标准MPA直接遍历itertools.product(range(M), repeatdf)而SD-MPA先做一次候选筛选。一个偷懒但有效的过渡方案是先写标准MPA验证链路正确性确认无误后再在候选生成处加入球译码剪枝逻辑这样能逐步定位改动引入的问题。4.3 性能测试与调参结果在我自己的仿真里用 J6、K4、M4 配置AWGN信道下标准MPA与SD-MPA在不同信噪比下的BER几乎重合。具体数值大概是这样仅作参考码本不同会有差异信噪比 (dB)标准MPA BERSD-MPA BER备注01.8e-11.9e-1噪声主导两者接近42.1e-22.3e-2差距在0.2dB以内88.5e-49.2e-4性能损失很小121.2e-51.5e-5基本一致这组数据说明剪枝带来的性能损失在可接受范围内但复杂度下降明显。我统计过组合搜索次数标准MPA在每个资源节点每轮迭代需要计算64个候选组合而SD-MPA在8dB信噪比下平均只会展开约20~30个候选接近省了一半。信噪比越高剪枝效果越显著因为高信噪比下接收信号更接近某个特定候选搜索半径能更快收紧。调参方面自适应半径策略我建议把初始半径设为alpha * sqrt(noise_var * df)其中alpha在2到3之间取值。alpha太小会误删正确候选太大又剪不动。如果你发现剪枝后性能掉得太快可以先把alpha调大先确认是剪枝误删的问题还是码本距离谱的问题再决定下一步优化方向。5. 常见问题与排查技巧实录5.1 错误平台消不掉BER曲线到了某个信噪比后不再下降这是SCMA仿真里最让人头疼的问题之一。我排查时的第一反应不是去看MPA迭代而是先回查码本。错误平台往往说明系统里存在某些码字对之间的欧氏距离太小导致高信噪比下多用户干扰成为主导因素。你可以把码本里的码字两两之间的最小距离打印出来看看是否存在距离明显偏小的码字对。如果码本没问题再检查因子图设计。因子图矩阵的行重过大或列重叠度过高都会加剧用户间干扰。比如 J6、K4 的标准因子图行重 df3但如果你的码本里某个资源块上的三个用户码字之间有某种对称退化就可能出现干扰不可分的问题。这种时候可以尝试对码本做一次随机相位扰动看错误平台是否有变化——如果变化明显说明码本距离谱是瓶颈如果没变化再看信道估计和同步模块。还有一个很容易被忽略的点接收端如果用了归一化一定要保持归一化因子的连续一致性。我遇到过BER曲线在某个信噪比附近突兀变差折腾很久发现是某个版本在归一化时把符号功率除错了导致高信噪比下量化噪声反而变大。这类问题不好查建议在关键节点把信号功率、噪声方差先打印出来对一遍。5.2 码本和因子图不匹配SCMA的码本设计必须和因子图的行列结构匹配。如果因子图里某个用户占用了3个资源块而码本只给每个用户设计了2个资源块的非零项译码器就会报维度不匹配或者更隐蔽地出现消息全为零的问题。这类问题在初次搭建链路时非常常见我的习惯是在初始化阶段加一个断言检查每个码本矩阵的维度是否为(M, K)因子图每列之和是否等于码本中非零资源块数量。另外提醒一下很多公开码本是以“每用户码字矩阵”形式给出的维度是(M, K)但有些论文里给出的是(K, M)转置关系容易搞混。我从论文里复现代码时第一步永远是确认码本到底按行还是按列组织再把它和因子图对齐否则就会出现“看起来没错、译码效果却很糟”的诡异现象。在做性能对比时还需要注意不同论文的码本不是同一个直接拿两个不同码本下的BER曲线对比意义不大。如果你想公平对比SD-MPA和标准MPA最稳妥的做法是固定同一套码本、同一套信道种子只切换译码器部分这样差异才全部来自算法本身。5.3 译码器数值问题与效率瓶颈对数域实现里最常见的数值问题是溢出或下溢。初学者容易直接在概率域做迭代几轮之后概率连乘结果趋近于零再取对数就变成-inf整个消息矩阵报废。我建议从第一版代码就用对数域实现并且使用logsumexp这种数值稳定的方式做归一化避免对数负无穷传播。效率方面如果你发现仿真跑得很慢先排查是不是在 Python 里写了多层 for 循环遍历资源节点和码字组合。SD-MPA 本身就是为了降低复杂度如果实现方式不当反而可能比标准MPA更慢。我建议把资源节点更新这一步尽量向量化用形状为(df*M, ...)的批量计算替代逐组合循环真实体感差距能达到3到5倍。还有一个容易被忽略的效率瓶颈是码本索引查找。每次计算候选码字与接收信号的欧氏距离时如果反复切片码本数组会产生大量临时内存分配。可以在初始化阶段把每个资源块上的码字组合预计算并缓存下来后续迭代只做查表内存换时间效果非常明显。这个优化在我项目里让单帧处理时间缩短了将近一半。结尾最后分享一点我个人的习惯SCMA的仿真项目命名往往很随意就像这个标题但我在工程里一定会额外维护一份 README把码本来源、因子图矩阵、迭代次数、半径策略这些参数全部记录清楚否则过两周回来看代码真的会忘记当初为什么这么调。SD-MPA 的工程价值在于复杂度可控但它不是银弹——码本不好什么算法都救不回来。如果你刚起步建议先把标准MPA链路跑通并理解每一步的消息更新含义再切换到SD-MPA做复杂度优化这样定位问题会容易得多。希望这篇文章能帮你少踩几个我踩过的坑。本文还有配套的精品资源点击获取