置信传播(Belief Propagation, BP)译码算法(公式推导+代码,超详细)
一、理论基础
此部分参考资料:
1.1 概述
置信传播(Belief Propagation,BP)算法,在编码理论中又常被称为和积算法(Sum-Product Algorithm,SPA),是一种在概率图模型上进行统计推断的消息传递机制,其核心思想是通过在Tanner图的变量节点(VN)与校验节点(CN)之间迭代传递置信度信息,逐步逼近最大后验概率(MAP)。
BP算法由朱迪亚·珀尔(Judea Pearl)于1982年提出,最初用于贝叶斯网络和马尔可夫随机场的概率推断,后被引入通信领域并发展为LDPC码的核心译码方法。但BP译码算法不是只能用于LDPC码,理论上可以用于任何拥有因子图(Factor Graph)表示的码型,包括所有的线性分组码,甚至卷积码(Turbo码)。
Turbo码:Turbo码的迭代译码本质上就是在两个分量码的因子图之间交换信息,属于BP算法的一个特例。Polar码(极化码):Polar码最经典的就是SC(串行消除)和SCL(串行消除列表)译码,但Polar码完全可以使用BP算法译码。Polar码的生成矩阵GN具有递归结构,可以展开成一个递归蝶形因子图,在此图上运行BP算法不仅可以实现并行译码(降低时延),还可以通过Soft-IC等手段进一步提升性能。PAC码(极化调整卷积码):PAC码在Polar码的极化编码之前加入了卷积预变换,虽然无法再用递归蝶形因子图完整表示整个过程,但其依然有确定的校验矩阵,理论上也可以使用BP译码算法,只是其校验矩阵的密度非常高,直接运行BP算法的性能会很差。卷积码:经典的BCJR算法其实就是定义在无环图(Trellis图/网格图)上的BP算法。RA码:这是一类结构化的类LDPC码,天然适合BP译码乘积码:常用于光通信和存储,其迭代硬判决或软判决译码也是BP思想的体现。
1.2 图论基础与结构化表示
前面提到,BP算法是一个通用的框架,理论上适用于所有可以用因子图表示的概率模型。在线性分组码中,通过校验矩阵H构建的Tanner图就是因子图的一个特例。下面以LDPC码为例。
1.2.1 线性分组码的矩阵定义
LDPC码属于线性分组码的范畴。一个 (n,k) 线性分组码将 k 比特的信息序列映射为 n 比特的码字序列,从而引入 n-k 个冗余比特用于纠错。该码由一个 m×n 的奇偶校验矩阵H唯一确定,其中,m=n-k 。对于任意合法码字 x = [x1,x2,...,xn],必须满足正交约束:
这意味着码字中的比特必须满足 m 个线性校验方程。LDPC码的核心特征在于校验矩阵H是“低密度”的,即矩阵中绝大多数元素为“0”,只有极少数元素为“1”。通常,每行和每列中“1”的数量远小于 n 和 m 。这种稀疏性保证了BP算法的计算复杂度随码长线性增长(O(n)),而非指数增长。
1.2.2 Tanner图:二部图模型
1981年,Tanner提出用二部图(Bipartite Graph)来表示奇偶校验矩阵,这种图后来被称为Tanner图。Tanner图由两个不同的节点集合组成:变量节点(Variable Node, VN)和校验节点(Check Node, CN),这些节点通过边相互连接,以图形化方式展示了校验矩阵H的结构。因此,Tanner图实际上就是校验矩阵的可视化表示,是定义BP算法消息传递路径的拓扑基础。
例:给定一个码长为7,信息位长度为4的LDPC码的校验矩阵H
表示第 j 个变量节点和第 i 个校验节点相连。
的每一列对应一个变量节点。
的每一行对应一个校验节点(也可以说对应一个校验方程)。
根据该校验矩阵就可以画出对应的可视化Tanner图:

只要建立了这个Tanner图,就可以在上面跑BP算法:变量节点向校验节点发信息,校验节点处理后再发回变量节点,如此迭代。
关于Tanner图还有几个比较重要的定义:
(1)边
Tanner图中的边仅连接变量节点和校验节点,不会连接同类节点。当且仅当校验矩阵元素
时,校验节点
与变量节点
之间存在一条边。
(2)度分布(Degree Distribution)
校验节点度(
):与校验节点相连的边的数量,等于
对应行的汉明重。它表示该方程约束了多少个比特。
变量节点度(
):与变量节点相连的边的数量,等于
对应列的汉明重。它表示该比特参与了多少个校验方程。
如果图中所有变量节点的度相同,且所有校验节点的度也相同,则该码称为规则LDPC码。如果度数不统一,则称为非规则LDPC码。研究表明,设计良好的非规则LDPC码通常具有比规则码更优异的瀑布区性能,因为高度数的变量节点能更早收敛,从而辅助低度数节点译码。
(2)环(Cycles)与围长(Girth)
- 环:从一个节点出发,经过若干条不同的边回到该节点的闭合路径。环的长度是指路径上边的数量。由于二部图的性质,环长必然是偶数(4、6、8...)。
- 围长:图中长度最短的环的长度。
在无环图(树)中,BP算法可以精确收敛到最大后验概率。然而,为了获得良好的距离特性,实用的LDPC码必然包含环。短环(特别是长为4的环)会破坏BP算法的独立性假设,导致信息错误在局部循环并被放大,引发译码失效或震荡。因此构造LDPC码的一个重要目标就是消除短环,最大化围长。
1.3 消息传递机制
在开始公式推导之前,先回答两个问题更有利于对后面公式的理解:在Tanner图上传播的消息具体是什么含义?为什么BP算法是有效的?
(1)传播消息的具体含义
在实际工程实现中,为了简化乘法运算为加法运算,通常在对数域进行计算,传播的消息是对数似然比(Log-Likelihood Ratio, LLR),定义为:
- 如果 L(x) > 0,表示该比特更倾向为0。
- 如果 L(x) < 0,表示该比特更倾向为1。
- | L(x) | 的大小表示置信度(绝对值越大,越确定)。
A. 变量节点 -> 校验节点(v -> c)
- 含义:根据信道给我的初始信息,以及除了你(该校验节点c)之外的其他校验节点告诉我的信息,我认为我自己是0或1的可能性是多少。
B. 校验节点 -> 变量节点(c -> v)
- 含义:为了满足我的校验方程(即所有连接到我的变量模2和必须为0),根据除了你(该变量节点v)之外的其他变量节点目前的状态,我推断你应该取值的可能性是多少。
- 两个维度
- 方向(逻辑推断):如果其他变量节点的模2和是0(或1),你(该变量节点v)也应该是0(或1),才能满足整个校验方程的模2和为0,决定了LLR的正负号。
- 强度(置信度):表示我推断你(该变量节点v)是0或1的这个消息有多确定,决定了LLR绝对值的大小。校验节点传出的置信度受限于与之相连的绝对值最小(最不可靠)的那个变量节点,这里用“木桶效应”比较好理解,因为只要有一个变量节点的消息不靠谱(例如L(x)=0.01),那么整个推断就是不靠谱的。
(2)BP算法的有效性
- 局部交互达成全局共识:BP译码的核心思想是将一个复杂的全局概率计算问题,分解为无数个简单的局部计算问题。每一个节点并不需要知道整个图的结构,它只与它的“邻居”进行通信。变量节点负责收集关于该比特概率的信息,校验节点负责检查这些概率是否符合奇偶校验约束。这种通信是双向迭代的。随着迭代次数增加,信息在图中扩散,原本不确定的节点(受噪声严重干扰的比特)会通过其他节点的强有力信息逐渐被纠正,最终整个网络达成共识。
- 利用了稀疏性:BP算法之所以能成为LDPC码的核心译码方法,就是因为LDPC码的校验矩阵是非常稀疏的,这意味着每个变量只参与少数几个校验,每个校验只关联少数几个变量,围长大,消息能够有效传播,且这种稀疏性保证了复杂度随码长呈线性增长(而非最大似然译码的指数级增长)。
- 外信息的交换:这是BP算法的精髓,当节点A向节点B传递消息时,不会把B刚才告诉的它的消息再传回B,这防止了信息在两个节点之间死循环,保证了每次迭代传递的都是“新鲜”的独立证据。
总而言之,BP译码算法可以看成是一个迭代的“投票”过程。每个比特最初只有信道的模糊观测,通过不断的变量节点(汇总意见)和校验节点(施加约束)之间的信息传播,消除不确定性,知道所有校验方程都被满足。
二、详细公式推导
此部分参考资料:
LDPC译码原理(公式推导)及其matlab代码实现(超详细)-ZEEKLOG博客
高斯白噪声信道下对数似然比的推导--- QPSK+BPSK - 哔哩哔哩
2.1 引理1
后面的推导需要用到引理1:给定一个长为
的相互独立的二进制序列
,第
个比特为 1 的概率为
,则二进制序列 a 中包含偶数个 1 的概率为:
包含奇数个 1 的概率为:
证明如下:
采用构造函数和数学归纳法进行证明。
首先构造关于
的
次多项式:
通过数学归纳法证明
的系数为序列中包含
个 1 的概率。
(1)当
时:
显然
的系数
为序列中包含 1 个 1 的概率,
的系数
为序列中包含 0 个 1 的概率。
(2)假设当
时,
的
次多项式为:
的系数
为其序列中包含
个 1 的概率。
(3)当
时,
的
次多项式 为:
由上式可求得
的系数为
,其中:
表示第
个比特为 0 的概率;
表示
的序列中有
个 1 的概率;
表示第
个比特为 1 的概率;
表示
的序列中有
个 1 的概率;
因此,当
时,
的系数
也表示序列中包含
个 1 的概率。综上,通过数学归纳法证明了
的系数为序列中包含
个 1 的概率。
在此基础上,再求序列中包含偶数个 1 的概率。
即需要求 m 次多项式中所有偶数次幂项的系数之和
,为此可以再构造一个函数
,则
会消掉所有奇数次幂项,得到所有偶数次幂项和的两倍:
再令
,得到序列中包含偶数个 1 的概率
:
则序列中包含奇数个 1 的概率
:
2.2 前置知识
在介绍具体的译码算法之前还有一点前置知识,需要先搞清楚整个通信链路。发送端的原始编码码字为
,经过调制得到发送信号
,再通过AWGN信道传输(引入高斯白噪声
),接收端收到的信号
,其中,噪声
服从高斯分布。译码就是通过接收信号
推断原始编码码字
的过程,推断的依据为原始编码码字
是满足校验矩阵的,如果译码结果
也满足相同的校验矩阵(即
),我们就认为找到了最可能的原始编码码字。
2.3 概率域BP译码算法
符号说明:

两个概念:
- 外信息原则:不将节点A告诉我的信息回传给A,以防止信息自我增强(正反馈)导致的过早收敛和偏差。
后验概率:通信系统中,我们关心的是在收到信号 y 的情况下,判断发送方发送的比特是 0 或 1 的概率,可以表示成
和
,一般被称为后验概率。
(1)初始化
对于每个变量节点
,根据信道接收值
计算初始后验概率,这是算法的输入,代表了仅由信道观测提供的信息,也是变量节点传递给校验节点的初始信息:
(2)校验节点更新(校验节点
传递给变量节点
的外信息)
校验节点
的任务是根据连接到它的变量节点(除变量节点
)的信息,计算它对变量节点
的约束意见:
理解含义:
表示在第
次迭代中,校验节点
推断变量节点
为 0 的概率,等价于除开该变量节点
外的其他变量节点的模2和应该为0的概率,这样才能满足连接到该校验节点
的所有变量节点的模2和为0的校验约束。也就等价于求其他变量节点中包含偶数个 1 的概率,即前面已经证明过的引理1。
表示在第
次迭代中除
外的其他变量节点为 1 的概率,由第
次迭代时变量节点
传递给校验节点
的外信息确定。
的含义同理可得。
(3)变量节点更新(变量节点
传递给校验节点
的外信息)
变量节点
的任务是综合来自信道的初始信息和连接到它的校验节点(除校验节点
)传来的外信息。
其中的
是校正因子,使每次计算出的
。
理解含义:
表示在第
次迭代中,变量节点
为 0 的概率,这个概率包括初始为0的概率、除开该校验节点
外的其他校验节点推测该变量节点
为 0 的概率,这些概率值相乘得到了第
次迭代中
的概率。
的含义同理可得。
(4)译码与终止条件
在每次迭代结束时,计算每个变量节点的总后验LLR,这里包含了所有校验节点的信息:
其中的
是校正因子,目的是使
。
- 若达到最大次数仍未满足校验条件,则宣布译码失败。
若
且未达到最大迭代次数,则回到步骤(2)继续下一轮迭代。
若
,说明所有校验方程满足,译码成功,提前终止。
校验:计算
。
硬判决:若
,则判
,否则判
。
2.4 对数域BP译码算法
前面介绍了概率域上的BP译码算法,但在实际工程实现中,并不会使用概率域BP译码算法,因为其包含大量的连乘运算,计算复杂度非常高。常见的做法是将概率信息用对数似然比表示,得到对数域上的BP译码算法,大量的连乘运算可以转化为加法运算。
2.4.1 标准和积算法(Sum-Product Algorithm, SPA)
和积算法是BP算法的标准实现(不包含任何近似),被公认为性能最优的软判决迭代算法。
(1)初始化
同样的,第一步是初始化算法的输入。对于每个变量节点
,根据信道接收值
计算初始信道LLR,即变量节点发送给校验节点的初始信息。对于AWGN信道、BPSK调制:
下面对该公式进行详细推导:
- 对数似然比(LLR)的定义
如前所述,根据接收信号
判断发送端发送的比特是 0 或 1 的概率可以表示为后验概率
或
。一般情况下,我们更关心两者的比值,因为如果
大于
,我们就有有理由判断
,否则,我们更倾向于判断
:
为了简化运算,一般还要再取对数,这就是对数似然比的定义式:
判决准则相应地变为:
- 信道初始LLR的计算
得到了LLR的定义式后,却不能直接根据该定义式进行计算,原因是后验概率
只是一个离散分布(即在接收端收到某一个具体的
值后,
只可能是 0 或者 1),我们无法写出它的函数解析式。在2.2节中提到过,编码码字
经过调制得到发送信号
(
与
是一一映射的,在BPSK中,0 一般映射为 +1 ,1 一般映射为 -1 ),
经过AWGN信道传输(加入高斯白噪声
)得到接收信号
,因为噪声
是服从高斯分布的,所以接收信号
也服从高斯分布。例如,发送端发送了
(即
),
也依然服从高斯分布,因为给定
的情况下,
是一个常数。
噪声
服从均值为 0 ,方差为
的高斯分布,可表示为
,满足以下概率密度函数:
代入
,这个式子可以写成:
进一步,
等价于
,所以式子最终可以写成:
这里的逻辑有点绕,但其实很好理解,因为
和
描述的是同一件事情。
表示噪声取某个值的概率,
表示已知
(等价于已知
,因为它们是一一映射的)的条件下,
取某个值的概率,因为当
已知时,
是一个固定的常数,
的值就完全由
决定,所以这两个表达式是完全等价的。还是前面的例子,发送端发送了
(即
),
,这个时候求
取某个值的概率,也就是求
时,
取某个值的概率。
被称为似然概率,根据上面的式子,它是服从高斯分布的。
还记得前面说的,后验概率
是一个离散分布,无法写出它的函数解析式。因此,我们可以利用贝叶斯公式,将其转化为服从高斯分布的似然概率
与先验概率
的表达式:
到这里,就可以对LLR进行计算了:
这里分成了两部分:
一是信道信息
,代入前面求的概率密度函数:
注意,这里
中代入的
值需要看BPSK调制的具体映射关系,这里是 0->+1,1->-1,如果是更高阶的调制,整个表达式会发生变化,求解更加复杂。具体可以看这位博主的文章:高斯白噪声信道下对数似然比的推导--- QPSK+BPSK - 哔哩哔哩
二是先验信息
,一般情况下发送比特是0或1的概率相等,因此
通常为0。
因此,信道初始LLR的计算式最终可以化简为:
这是对数域译码算法中信道初始化的常用式子,基本上是作为一个结论使用的,少有详细推导。其中,
是噪声方差,它的求法也有一个推导过程,这里就不赘述了,以后有机会再补上。
(2)校验节点更新(校验节点
传递给变量节点
的外信息)
根据前面的定义,对数域BP译码算法的校验节点更新公式为:
在数学中,双曲正切函数
有以下两个公式成立:
且有以下几个关系式成立:
可以利用这几个公式对
进行化简:
故校验节点更新的公式最终可以化简为:
这是SPA的标准公式。由于 tanh 的乘积计算复杂,该标准公式还有另外一种表达形式,可以使用
函数将乘积转换为加法:
是符号部分
。
是幅度部分
。
符号部分表示校验节点
推测变量节点
是 0 或 1(方向),由输入消息的符号异或决定;
幅度部分表示校验节点
推测变量节点
是 0 或 1 的确定性(强度)。
函数定义为:
是自逆函数,即
;同时,
是单调递减函数且为正值(当
),当
时,
,当
时,
。
(3)变量节点更新(变量节点
传递给校验节点
的外信息)
变量节点更新的操作在LLR域即为简单的加法:
(4)译码与终止条件
在每次迭代结束时,计算每个变量节点的总后验LLR:
这里包含了所有校验节点的信息。
- 若达到最大次数仍未满足校验条件,则宣布译码失败。
若
且未达到最大迭代次数,则回到步骤(2)继续下一轮迭代。
若
,说明所有校验方程满足,译码成功,提前终止。
校验:计算
。
硬判决:若
,则判
,否则判
。
2.4.2 最小和算法(Min-Sum Algorithm, MSA)
虽然SPA提供了最优的误码率性能,但 tanh函数(或
函数)作为非线性运算在FPGA或ASIC实现中不仅占用大量面积,还容易受到定点数量化误差的影响。为了解决这一问题,工程界广泛采用基于”最小近似“的简化算法,主要改进了校验节点更新的公式,推导如下:
函数
中,当
较大时,
会迅速衰减。因此,在求和
时,主要的贡献来自于数值最大的
项,而
是单调递减函数,这意味着最大的
值对应最小的输入幅度
。因此,我们可以用最小值来近似整个非线性运算:
MSA校验节点更新公式:
即符号部分不变,对幅值部分进行了近似操作。
这个近似结果是偏大的。如前所述,
是一个减函数且为正值(
),因为
都是正数,所以
一定大于等于其中最大的那一项
,又因为
也是减函数,因此
。
特性分析:
- 硬件优势:复杂的双曲函数被简化为“比较”和“符号异或”操作。这极大地降低了逻辑门数量,使得高速并行实现成为可能。
性能损失:由于
实际上总是大于等于真实的
值,MSA 倾向于高估校验节点输出的可靠性(LLR幅度偏大)。这种过估计会导致性能下降,通常在高斯信道下比SPA差约0.5dB到1dB。
2.4.3 修正最小和算法
1、归一化最小和算法(Scaled Min-Sum Algorithm, NMSA)
为了修正MSA的幅度过估计问题,NMSA引入了一个归一化因子(缩放因子)
(通常
)。
NMSA的校验节点更新公式修正为:
- 优势:NMSA保留了MSA的线性特性,仅增加了一个乘法(可以通过移位加法实现),却能恢复大部分因近似丢掉的性能。它是目前5G NR、DVB-T2/S2和Wi-Fi标准中LDPC译码器的主流选择。
参数选择:
的最优值取决于码率、度分布和信噪比,通常通过密度进化理论计算或蒙特卡洛仿真搜索得到。对于大多数规则码,
是常用值。
2、偏移最小和算法(Offset Min-Sum Algorithm, OMSA)
OMSA采用减法偏移来降低LLR幅度,修正过估计。
OMSA的校验节点更新公式修正为:
其中,
是非负偏移量。
- 优势:在硬件中,减法比乘法更容易实现(尤其对于定点数)。此外,OMSA在某些高信噪比场景下对错误平层的抑制效果略优于NMSA。
三、Matlab代码
3.1 SPA
基础版本严格对照译码流程,逻辑清晰,但包含大量for循环,效率较低,适合用来理解算法。
function [decoded_bits, iter] = SPA_BP(H, llr, max_iterations) % 实现SPA-BP译码算法 % 输入: % H - 校验矩阵 % llr - 接收到的LLR值 % max_iterations - 最大迭代次数 % 输出: % decoded_bits - 译码后的比特序列 % 获取校验矩阵尺寸 [m, n] = size(H); % 初始化变量节点到校验节点的消息 % v2c[i,j]表示第i个变量节点到第j个校验节点的消息 v2c = zeros(n, m); % 初始化校验节点到变量节点的消息 % c2v[j,i]表示第j个校验节点到第i个变量节点的消息 c2v = zeros(m, n); % 初始化变量节点的后验LLR值 posterior_llr = llr; % 创建校验节点和变量节点的连接列表,避免遍历整个矩阵 check_nodes = cell(m, 1); variable_nodes = cell(n, 1); for i = 1:m check_nodes{i} = find(H(i, :) == 1); end for j = 1:n variable_nodes{j} = find(H(:, j) == 1); end % BP迭代过程 for iter = 1:max_iterations % 校验节点到变量节点的消息更新 for i = 1:m connected_vars = check_nodes{i}; for j_idx = 1:length(connected_vars) j = connected_vars(j_idx); % 校验节点i到变量节点j的消息更新使用tanh-product规则 prod_tanh = 1; for k_idx = 1:length(connected_vars) k = connected_vars(k_idx); if k ~= j % 防止数值不稳定 v2c_clamped = max(min(v2c(k, i), 30), -30); prod_tanh = prod_tanh * tanh(v2c_clamped / 2); end end % 防止数值不稳定(避免log(0)) if abs(prod_tanh) > 0.9999 prod_tanh = sign(prod_tanh) * 0.9999; end c2v(i, j) = 2 * atanh(prod_tanh); % 限制消息的数值范围,防止数值溢出 c2v(i, j) = max(min(c2v(i, j), 30), -30); end end % 变量节点到校验节点的消息更新 for j = 1:n connected_checks = variable_nodes{j}; for i_idx = 1:length(connected_checks) i = connected_checks(i_idx); % 变量节点j到校验节点i的消息 = % 原始LLR + 所有其他连接的校验节点到该变量节点的消息 v2c(j, i) = llr(j); for k_idx = 1:length(connected_checks) k = connected_checks(k_idx); if k ~= i v2c(j, i) = v2c(j, i) + c2v(k, j); end end end end % 计算每个变量节点的后验LLR值 for j = 1:n posterior_llr(j) = llr(j); connected_checks = variable_nodes{j}; for i_idx = 1:length(connected_checks) i = connected_checks(i_idx); posterior_llr(j) = posterior_llr(j) + c2v(i, j); end end % 硬判决 decoded_bits = double(posterior_llr < 0); % 校验是否满足所有校验方程 syndromes = mod(H * decoded_bits', 2); if sum(syndromes) == 0 % 所有校验方程都满足,提前结束迭代 break; end end end优化版本利用了Matlab擅长矩阵运算的特点,去掉繁琐的for循环,运行速度可以提升10倍左右:
function [decoded_bits, iter] = SPA_BP_Optimized(H, llr, max_iterations) % SPA_BP_Optimized 实现矢量化BP译码算法 [m, n] = size(H); % 预处理:将H转换为逻辑矩阵,用于快速掩膜(Masking) H_mask = logical(H); % 初始化消息矩阵 (全部使用 m x n 维度) % V2C: 变量节点到校验节点 (初始为LLR值,仅在H_mask位置有效) V2C = repmat(llr, m, 1) .* H_mask; % C2V: 校验节点到变量节点 C2V = zeros(m, n); % 数值稳定性常数 SMALL_VAL = 1e-15; % 防止log(0) LARGE_VAL = 30; % LLR截断阈值,防止溢出 for iter = 1:max_iterations % ============================== % 1. 校验节点更新 (Horizontal Step) % ============================== % 目标:计算 2 * atanh( prod( tanh(V2C/2) \ i ) ) % 优化策略: 使用 φ 函数技巧,将乘积转为求和,并将符号和数值分离处理 % A. 处理符号 (Sign) Sign_V2C = sign(V2C); Sign_V2C(V2C == 0) = 1; % 避免0符号问题 Sign_V2C(~H_mask) = 1; % 非连接处设为1,不影响乘积 % 计算每行的总奇偶校验位 Total_Sign = prod(Sign_V2C, 2); % 排除自身的符号 (通过除法或异或,这里用乘法因为是1/-1) C2V_Sign = Total_Sign .* Sign_V2C; % B. 处理数值 (Magnitude) - 使用 -log(tanh(|x|/2)) 变换 Abs_V2C = abs(V2C); % 避免数值计算错误 log(tanh(0))=Inf,对极小值进行裁剪 Abs_V2C(Abs_V2C < SMALL_VAL) = SMALL_VAL; % 核心变换函数 phi(x) Phi_V2C = -log(tanh(Abs_V2C / 2)); Phi_V2C(~H_mask) = 0; % 非连接处设为0,不影响求和 % 行求和 Total_Phi = sum(Phi_V2C, 2); % 排除自身贡献 Phi_External = Total_Phi - Phi_V2C; % 逆变换回去 (函数形式相同) % C2V_Mag = -log(tanh(Phi_External / 2)); % 同样对极小值进行裁剪 Phi_External(Phi_External < SMALL_VAL) = SMALL_VAL; C2V_Mag = -log(tanh(Phi_External / 2)); % C. 组合符号和数值,并进行范围截断 C2V = C2V_Sign .* C2V_Mag; C2V = max(min(C2V, LARGE_VAL), -LARGE_VAL); % 应用掩膜,确保非连接处为0 C2V = C2V .* H_mask; % ============================== % 2. 变量节点更新 (Vertical Step) % ============================== % 目标: LLR_post = LLR_intrinsic + sum(C2V) % V2C_new = LLR_post - C2V_old % 计算后验 LLR (列求和) Sum_C2V = sum(C2V, 1); Posterior_LLR = llr + Sum_C2V; % 硬判决 decoded_bits = double(Posterior_LLR < 0); % 校验是否满足 H * x = 0 % 注意:Matlab中 dense matrix * dense vector 很快 syndrome = mod(H * decoded_bits', 2); if all(syndrome == 0) return; end % 更新 V2C 消息 (总和 - 来自该校验节点的消息) V2C = repmat(Posterior_LLR, m, 1) - C2V; % 再次截断并掩膜 V2C = max(min(V2C, LARGE_VAL), -LARGE_VAL); V2C = V2C .* H_mask; end end3.2 MSA、NMSA、OMSA
MSA基础版本,将校验节点更新的部分相应改为:
% 校验节点到变量节点的消息更新 for i = 1:m connected_vars = check_nodes{i}; for j_idx = 1:length(connected_vars) j = connected_vars(j_idx); % 校验节点i到变量节点j的消息更新使用Min-sum规则 min_val = Inf; sign_prod = 1; for k_idx = 1:length(connected_vars) k = connected_vars(k_idx); if k ~= j sign_prod = sign_prod * sign(v2c(k, i)); min_val = min(min_val, abs(v2c(k, i))); % 遍历找到最小值 end end c2v(i, j) = sign_prod * min_val; % 限制消息的数值范围,防止数值溢出 c2v(i, j) = max(min(c2v(i, j), 20), -20); end endNMSA基础版本,将校验节点更新的部分相应改为:
% 校验节点到变量节点的消息更新 for i = 1:m connected_vars = check_nodes{i}; for j_idx = 1:length(connected_vars) j = connected_vars(j_idx); % 校验节点i到变量节点j的消息更新使用Min-sum规则 min_val = Inf; sign_prod = 1; for k_idx = 1:length(connected_vars) k = connected_vars(k_idx); if k ~= j sign_prod = sign_prod * sign(v2c(k, i)); min_val = min(min_val, abs(v2c(k, i))); end end % 应用归一化因子alpha c2v(i, j) = sign_prod * alpha * min_val; % 限制消息的数值范围,防止数值溢出 c2v(i, j) = max(min(c2v(i, j), 20), -20); end endOMSA基础版本,将校验节点更新的部分相应改为:
% 校验节点到变量节点的消息更新 for i = 1:m connected_vars = check_nodes{i}; for j_idx = 1:length(connected_vars) j = connected_vars(j_idx); % 校验节点i到变量节点j的消息更新使用Mins_sum规则 min_val = Inf; sign_prod = 1; for k_idx = 1:length(connected_vars) k = connected_vars(k_idx); if k ~= j sign_prod = sign_prod * sign(v2c(k, i)); min_val = min(min_val, abs(v2c(k, i))); end end % 应用偏移操作 offset_val = max(min_val - beta, 0); c2v(i, j) = sign_prod * offset_val; % 限制消息的数值范围,防止数值溢出 c2v(i, j) = max(min(c2v(i, j), 20), -20); end end优化版本,将以上四种算法整合了一下,直接一步到位:
function [decoded_bits, iter] = BP_Decoder(H, llr, max_iterations, algo_type, param) % 实现统一的 BP 译码 % 输入: % H: 校验矩阵 % llr: 初始对数似然比 % max_iterations: 最大迭代次数 % algo_type: 'SPA', 'MSA', 'NMSA', 'OMSA' % param: 对于 NMSA 是缩放因子 alpha (如 0.75); 对于 OMSA 是偏移量 beta (如 0.15) if nargin < 4 algo_type = 'SPA'; end if nargin < 5 % 默认参数设置 if strcmp(algo_type, 'NMSA') param = 0.75; elseif strcmp(algo_type, 'OMSA') param = 0.15; else param = 0; end end [m, n] = size(H); H_mask = logical(H); % 初始化消息矩阵 V2C = repmat(llr, m, 1) .* H_mask; C2V = zeros(m, n); % 常数定义 LARGE_VAL = 30; % 截断阈值 SMALL_VAL = 1e-15; % 仅用于 SPA,防止 log(0)=Inf for iter = 1:max_iterations % ============================== % 1. 校验节点更新 (Horizontal Step) % ============================== % --- A. 符号处理 (所有算法通用) --- Sign_V2C = sign(V2C); Sign_V2C(V2C == 0) = 1; Sign_V2C(~H_mask) = 1; % 计算总奇偶校验 (行乘积) Total_Sign = prod(Sign_V2C, 2); C2V_Sign = Total_Sign .* Sign_V2C; % 排除自身 % --- B. 数值处理 (根据算法区分) --- Abs_V2C = abs(V2C); if strcmp(algo_type, 'SPA') % === SPA 算法 === Abs_V2C(Abs_V2C < SMALL_VAL) = SMALL_VAL; Phi_V2C = -log(tanh(Abs_V2C / 2)); Phi_V2C(~H_mask) = 0; Total_Phi = sum(Phi_V2C, 2); Phi_External = Total_Phi - Phi_V2C; % 防止数值问题 Phi_External(Phi_External < SMALL_VAL) = SMALL_VAL; C2V_Mag = -log(tanh(Phi_External / 2)); else % === MSA / NMSA / OMSA === % 1. 预处理:将非连接处(0)设为无穷大,避免影响求最小值 Abs_V2C_Temp = Abs_V2C; Abs_V2C_Temp(~H_mask) = inf; % 2. 寻找每一行的 最小值(min1) 和 次小值(min2) % [val, idx] = min(...) [min1_val, min1_idx] = min(Abs_V2C_Temp, [], 2); % 为了找次小值,先将最小值的位置设为无穷大 Abs_V2C_Temp_NoMin1 = Abs_V2C_Temp; % 使用线性索引快速替换 linear_indices = sub2ind([m, n], (1:m)', min1_idx); Abs_V2C_Temp_NoMin1(linear_indices) = inf; [min2_val, ~] = min(Abs_V2C_Temp_NoMin1, [], 2); % 3. 构建 C2V_Mag 矩阵 % 逻辑:如果所在位置正好是最小值 min1 则输出 min2,否则是 min1 % 先假设所有位置输出的都是全局最小值 C2V_Mag = repmat(min1_val, 1, n); % 将原本是 min1 的位置用 min2 代替 C2V_Mag(linear_indices) = min2_val; % 4. 针对变种进行修正 if strcmp(algo_type, 'NMSA') % Normalized MSA: 乘以归一化因子 alpha (0 < alpha < 1) alpha = param; C2V_Mag = C2V_Mag * alpha; elseif strcmp(algo_type, 'OMSA') % Offset MSA: 减去偏移量 beta, 并保证不小于0 beta = param; C2V_Mag = max(C2V_Mag - beta, 0); end end % --- C. 组合与截断 (所有算法通用) --- C2V = C2V_Sign .* C2V_Mag; C2V = max(min(C2V, LARGE_VAL), -LARGE_VAL); C2V = C2V .* H_mask; % ============================== % 2. 变量节点更新 (Vertical Step) % ============================== Sum_C2V = sum(C2V, 1); Posterior_LLR = llr + Sum_C2V; decoded_bits = double(Posterior_LLR < 0); % 校验 H*x^T = 0 syndrome = mod(H * decoded_bits', 2); if all(syndrome == 0) return; end V2C = repmat(Posterior_LLR, m, 1) - C2V; V2C = max(min(V2C, LARGE_VAL), -LARGE_VAL); V2C = V2C .* H_mask; end end