简介:这份资源面向通信、编码理论方向的学生与工程师,聚焦LDPC码的比特翻转译码与PEG构造方法,适合作为课程实验、算法验证或入门研究的实践素材。压缩包共5个文件,以3个mat数据文件和2个m脚本为主,整体约71KB:mat文件保存不同规格的LDPC校验矩阵实例,m脚本则实现比特翻转译码流程与主运行入口,便于直接加载矩阵并复现译码过程。资源覆盖PEG构造生成的规则码矩阵、扩展图LDPC码实例以及小型教学用码字,可帮助读者对比不同构造方式下的码字结构与纠错表现。已有164人学习下载,适合希望从代码层面理解LDPC稀疏校验矩阵、迭代翻转译码终止条件与性能评估的读者参考。
1. 从一份 BF.zip 说起:LDPC PEG 构造与比特翻转译码到底能跑出什么
如果你手头正躺着一份名为BF.zip_LDPC PEG_LDPC 构造_PEG LDPC_teacherzl4_比特翻转译码的压缩包,点开之前大概率会犯嘀咕:这到底是能直接跑的仿真工程,还是只丢了几段伪代码的课程作业。我拆过不少这类 LDPC 相关的资源包,名字里同时出现 PEG 构造和比特翻转译码,基本可以判定它是一套「从校验矩阵生成到硬判决译码」的完整链路,而不是只给一个孤立的编码函数。PEG 负责把稀疏校验矩阵搭出来,比特翻转负责在接收端用最朴素的方式把错误比特翻回去,两者凑在一起,正好覆盖了 LDPC 入门到能出误码率曲线的关键环节。
这份资源适合两类人:一类是刚接触信道编码、想亲手把 H 矩阵和译码循环跑通的学生或转行工程师;另一类是做通信链路仿真、需要快速验证不同构造参数对译码性能影响的从业者。它不解决高阶的软判决译码问题,也不涉及 5G NR 里的 QC-LDPC 标准流程,但作为理解 PEG 构造逻辑和 BF 译码门限的起点,够用且不绕弯。下面我按「构造原理 → 参数落地 → 译码实现 → 避坑 → 进阶验证」的顺序,把这份资源拆成能照着复现的步骤。
2. PEG 构造的底层逻辑:为什么它能把围长撑到 6 以上
2.1 PEG 算法到底在「贪心」什么
PEG 的全称是 Progressive Edge Growth,翻译过来叫渐进边增长。它的核心动作只有一句话:往校验矩阵里逐条加边,每加一条边,都尽量让当前变量节点到校验节点子图里的环最长。LDPC 码的性能对 Tanner 图里的短环非常敏感,尤其是长度为 4 的环,会让迭代译码的消息在几轮内就自我强化,导致译码器误判。PEG 的做法是每加一条边就做一次广度优先搜索,找到距离最远的校验节点,把边挂上去,从而把围长一点点撑大。
常见做法是维护一个变量节点列表,按列依次处理。对当前变量节点,先看它已经连了哪些校验节点,再从这些节点出发做 BFS,记录每个校验节点的深度。如果某一层里还有未满的校验节点,就优先选它;如果所有可达节点都满了,就选当前深度最大且度数最低的那个。这个「优先未满、其次最低度数」的策略,就是 PEG 能在稀疏约束下把环长拉开的根本原因。
2.2 用 Python 把 PEG 构造跑起来
下面这段代码是我按 PEG 标准流程重写的,输入是码长 n、校验节点数 m、每个变量节点的列重分布,输出是稀疏校验矩阵 H。你可以直接把它存成peg_construct.py,和资源包里的构造脚本对照着看。
import numpy as np from collections import deque def peg_construct(n, m, col_weight): """ n: 变量节点数(码长) m: 校验节点数 col_weight: 每个变量节点的列重列表,长度应为 n 返回: H (m x n) 的稀疏矩阵,用 0/1 表示 """ H = np.zeros((m, n), dtype=int) # 记录每个校验节点当前已连接的边数 check_degree = np.zeros(m, dtype=int) # 每个校验节点的最大允许度数,这里按均匀分配估算 max_degree = int(np.ceil(n * np.mean(col_weight) / m)) for var in range(n): # 当前变量节点需要连的边数 need = col_weight[var] # 已连接的校验节点集合 connected = set() for _ in range(need): # BFS 从当前变量节点已连的校验节点出发 visited = np.zeros(m, dtype=bool) depth = np.full(m, -1, dtype=int) queue = deque() # 把已连的校验节点作为第 0 层 for c in connected: visited[c] = True depth[c] = 0 queue.append(c) # 记录每一层里度数未满的候选 candidate = None while queue: cur = queue.popleft() # 遍历与 cur 相连的所有变量节点(通过 H 的列反查) for v in range(n): if H[cur, v] == 1 and v != var: # 再找这个变量节点连的其他校验节点 for c2 in range(m): if H[c2, v] == 1 and not visited[c2]: visited[c2] = True depth[c2] = depth[cur] + 1 queue.append(c2) if check_degree[c2] < max_degree: candidate = c2 if candidate is not None: break # 如果 BFS 没找到未满的,就选当前度数最低的 if candidate is None: candidate = int(np.argmin(check_degree)) H[candidate, var] = 1 check_degree[candidate] += 1 connected.add(candidate) return H这段代码里最关键的参数是col_weight,它决定了每列连几条边。列重越大,变量节点参与校验越多,译码时信息更充分,但矩阵密度上升,BF 译码的单轮翻转判断会变慢。我一般会先用均匀列重跑通,比如码长 512、列重 3,再看误码率曲线是否合理。max_degree是校验节点的度数上限,如果设得太小,BFS 会频繁找不到未满节点,退化成随机选点,围长就保不住了。
2.3 构造结果怎么验证:围长和度数分布
跑完构造后别急着丢进译码器,先做两件事:统计 H 矩阵里长度为 4 的环数量,以及检查每列每行的度数是否和设定一致。长度 4 的环在矩阵上的表现是存在两行两列交叉位置全为 1。你可以用下面这段检查脚本快速扫一遍。
def count_4_cycles(H): """统计 H 中长度为 4 的环数量""" m, n = H.shape count = 0 for r1 in range(m): for r2 in range(r1 + 1, m): # 两行同时为 1 的列 common = np.where((H[r1] == 1) & (H[r2] == 1))[0] if len(common) >= 2: # 任意两列组合都构成一个 4 环 count += len(common) * (len(common) - 1) // 2 return count如果这个数不是 0,说明 PEG 构造过程中 BFS 提前退出了,常见原因是max_degree设得太紧,或者列重分布本身就不合理。我一般会把max_degree放宽到ceil(平均列重 * n / m) + 1,再跑一次对比。
3. 比特翻转译码:硬判决里最朴素的那条路怎么走
3.1 BF 译码的判决规则和迭代终止
比特翻转译码是 LDPC 硬判决译码里最容易实现的一种。它接收的是 0/1 硬判决序列,每次迭代先算每个校验节点的校验和,再把不满足的校验信息回传给变量节点,统计每个变量节点收到的「不满意」数量。如果某个变量节点收到的错误校验数超过阈值,就翻转它的比特。重复这个过程,直到所有校验方程都满足,或者达到最大迭代次数。
阈值的选择是个经验活。设得太低,每轮翻转太多比特,容易把正确比特翻错;设得太高,错误比特翻不过来,迭代次数用完了误码率还是下不去。常见做法是取变量节点度数的中位数,或者直接用固定值 2 到 3 先跑通。资源包里如果带了bf_decode函数,你可以先看它的阈值是写死的还是按度数动态算的。
3.2 把 BF 译码写成可调参的函数
下面这段实现把阈值和最大迭代次数都做成入参,方便你后续做参数扫描。
def bf_decode(H, received, max_iter=50, threshold=None): """ H: 校验矩阵 (m x n) received: 接收到的硬判决序列,长度 n,元素为 0/1 max_iter: 最大迭代次数 threshold: 翻转阈值,None 时按列重中位数自动设定 返回: 译码后的比特序列,以及实际迭代次数 """ m, n = H.shape y = received.copy() if threshold is None: col_weight = H.sum(axis=0) threshold = int(np.median(col_weight)) for it in range(max_iter): # 计算每个校验节点的校验和 syndrome = (H @ y) % 2 if np.all(syndrome == 0): return y, it # 每个变量节点收到的错误校验数 error_count = H.T @ syndrome # 超过阈值的比特翻转 flip_mask = error_count > threshold if not np.any(flip_mask): # 没有比特可翻,提前退出 return y, it y[flip_mask] = 1 - y[flip_mask] return y, max_iter这里syndrome是校验方程是否满足的向量,全 0 表示译码成功。error_count统计的是每个变量节点参与的不满足校验方程个数。翻转条件是严格大于阈值,如果你想让译码更激进,可以改成大于等于。max_iter一般设 50 到 100,再大对硬判决 BF 的增益很有限,反而浪费时间。
3.3 用误码率曲线判断参数是否合理
跑完译码后,最直观的验证是画 BER 随信噪比变化的曲线。你可以用 BPSK 调制加 AWGN 信道生成测试数据,码长 512、码率 1/2 左右,信噪比从 0 dB 扫到 4 dB。如果曲线在 2 dB 之后明显下降,说明构造和译码链路是通的;如果一直平着不动,大概率是 H 矩阵的围长没撑住,或者 BF 阈值设得太高导致错误比特翻不过来。
def simulate_ber(H, snr_range, n_trials=1000): """简单 BER 仿真,BPSK + AWGN""" m, n = H.shape ber_list = [] for snr_db in snr_range: snr = 10 ** (snr_db / 10) errors = 0 total = 0 for _ in range(n_trials): bits = np.random.randint(0, 2, n) # BPSK 映射: 0 -> +1, 1 -> -1 tx = 1 - 2 * bits noise = np.random.randn(n) / np.sqrt(2 * snr) rx = tx + noise hard = (rx < 0).astype(int) decoded, _ = bf_decode(H, hard) errors += np.sum(decoded != bits) total += n ber_list.append(errors / total) return ber_list这段仿真跑起来不快,但能给你一个量级上的判断。如果资源包里自带测试脚本,优先用它的,因为它的码长和码率可能已经调好了。
4. 避坑与排查:PEG 构造和 BF 译码里最容易翻车的五件事
4.1 现象:H 矩阵里出现全零列或全零行
原因通常是列重列表长度和码长对不上,或者 PEG 在 BFS 阶段提前退化成随机选点,把某一行填满了导致后续变量节点无处可连。解决方法是先检查col_weight的长度是否等于 n,再把max_degree放宽,最后在构造完成后加一行断言,确保每列每行的和都大于 0。
4.2 现象:BF 译码迭代次数跑满但误码率不降
先看阈值是不是设得太高。如果阈值等于最大列重,那只有收到所有校验节点都报错的变量节点才会翻转,实际中几乎不会触发。把阈值降到列重中位数或 2 到 3,再跑一次。另一个可能是 H 矩阵的围长太短,消息在几轮内就形成正反馈,这时候需要回到 PEG 构造阶段检查 4 环数量。
4.3 现象:仿真曲线在低信噪比下正常,高信噪比反而变差
这种反直觉的情况多半是 BF 译码的翻转逻辑把正确比特翻错了。硬判决 BF 没有软信息,在高信噪比下错误比特本来就少,但一旦某个正确比特被误判为错误并翻转,就会引入新的错误。解决办法是加一个「翻转后校验和是否减少」的判断,只翻转能让校验和下降的比特,相当于给 BF 加一个简单的禁忌规则。
4.4 现象:PEG 构造耗时随码长急剧上升
PEG 的 BFS 复杂度不低,码长上千后纯 Python 实现会明显变慢。常见优化是把 H 矩阵按行和列分别存成邻接表,BFS 时直接查表而不是遍历整个矩阵。如果资源包里用的是矩阵乘法做 BFS,那码长超过 1024 后基本没法用,需要自己改成稀疏结构。
4.5 现象:资源包里的脚本跑不通,报缺少模块或路径错误
这类资源包常见的问题是作者把多个脚本平铺在根目录,互相 import 时用了相对路径,换台机器就找不到文件。先看报错信息里缺的是哪个模块,如果是本地文件,把工作目录切到脚本所在目录再跑;如果是第三方库,检查requirements.txt里有没有列全。我一般会先建一个虚拟环境,把依赖装干净再复现,避免和系统里的旧版本冲突。
5. 进阶验证:用密度进化粗略判断 BF 译码的门限
5.1 为什么硬判决 BF 也值得看门限
比特翻转译码虽然简单,但它也有一个理论上的信噪比门限,低于这个门限,迭代次数再多也翻不回来。密度进化是分析 LDPC 译码门限的常用工具,对 BF 译码可以做一个简化版:假设每轮翻转的比特比例固定,跟踪错误概率的迭代变化,找到错误概率收敛到 0 的信噪比临界点。这个分析不需要跑大量仿真,能帮你在动手前判断参数是否值得调。
5.2 用几行代码估一个粗略门限
下面这段代码用简化的密度进化思路,估算 BF 译码在给定列重分布下的门限信噪比。它不追求精确,但能给你一个量级参考。
def bf_threshold_estimate(col_weight, max_iter=50): """ 简化密度进化:跟踪错误概率 p 的迭代 col_weight: 列重列表 返回: 门限信噪比估计值 (dB) """ import math avg_w = sum(col_weight) / len(col_weight) # 从高信噪比往低扫,找 p 不再收敛到 0 的临界点 for snr_db in [x * 0.1 for x in range(40, -20, -1)]: snr = 10 ** (snr_db / 10) # 初始错误概率用 BPSK 在 AWGN 下的理论值 p = 0.5 * math.erfc(math.sqrt(snr)) converged = True for _ in range(max_iter): # 简化更新:错误概率按列重加权后重新估计 p_new = p ** (avg_w - 1) if p_new > p: converged = False break p = p_new if p < 1e-6: break if not converged: return snr_db + 0.1 return None这个估计非常粗,只考虑了平均列重,没有区分变量节点和校验节点的度数分布。但如果你把资源包里的列重分布代进去,算出来的门限和仿真曲线开始下降的位置不会差太远。我一般会用它来快速排除明显不合理的参数组合,比如列重全为 2 的矩阵,门限会高得没法用。
5.3 把构造、译码、验证串成一条可复现的流水线
最后一步是把前面几块拼起来:先按目标码长和码率生成列重分布,跑 PEG 构造得到 H,检查 4 环数量,再用 BF 译码跑一组信噪比扫描,最后把 BER 曲线和密度进化估计的门限画在一起对比。如果曲线在门限附近开始下降,说明整条链路是自洽的;如果曲线远高于门限才开始降,优先回头查 H 矩阵的围长和 BF 阈值。
从那以后我每次拿到这类 LDPC 资源包,都会先跑一遍 4 环检查,再拿最小码长做一次端到端仿真,确认构造和译码能对上,才敢往大码长上扩。希望帮到你。
本文还有配套的精品资源,点击获取