简介:这份资源面向通信、数据存储方向的学习者与研究人员,聚焦LDPC码的稀疏矩阵构造与解码实现。压缩包内共2个文件,均为MATLAB脚本(.m),整体约2KB,分别承担校验矩阵H与生成矩阵G的生成任务,可根据用户预设的列重与行重参数,构造符合要求的LDPC稀疏矩阵。列重指校验矩阵每列中1的个数,行重指每行中1的个数,二者直接影响码字的纠错能力与解码复杂度,合理配置有助于在性能与效率之间取得平衡。生成矩阵用于将信息位映射为含冗余位的码字,校验矩阵则服务于信念传播等消息传递解码算法,其稀疏性可显著降低迭代计算量。已有401人学习关注。读者可借助脚本理解LDPC码从参数设定到矩阵生成、再到编码解码的完整链路,并可按需调整行重列重,适配不同信道环境与性能要求,适合作为系统级仿真与算法验证的入门工具。
1. 从两个 .m 文件说起:LDPC 稀疏矩阵生成到底能解决什么问题
很多人第一次接触 LDPC,卡住的地方不是置信传播的迭代公式,而是手里根本没有一个能跑起来的 H 矩阵。通信仿真里想验证译码性能,得先有校验矩阵;做数据存储的纠错链路,也得先有稀疏矩阵。这个LDPC.zip里就两个文件:ldpc_gen_h.m和ldpc_gen_g.m,一个负责按指定列重、行重生成校验矩阵 H,一个负责从 H 推出生成矩阵 G。它解决的就是「给定列重和行重,快速拿到一对可用的 H/G」这件事,适合做链路级仿真、算法验证、教学复现的工程师。列重(Column Weight)是 H 矩阵每列 1 的个数,行重(Row Weight)是每行 1 的个数,这两个参数直接决定码率和译码复杂度,是 LDPC 稀疏矩阵设计里最先要定下来的东西。
2. 列重行重怎么落到 H 矩阵:生成逻辑与参数约束
2.1 为什么 LDPC 要用稀疏矩阵而不是随便一个校验矩阵
LDPC 的全称是低密度奇偶校验码,低密度指的就是 H 矩阵里 1 的占比极低。假设码长 N=1024,列重 dv=3,那么整个 H 矩阵里 1 的总数就是 N×dv=3072 个,如果校验方程数 M=512,矩阵总元素是 512×1024≈52 万,1 的占比不到 0.6%。这个稀疏性带来两个直接好处:一是译码时置信传播算法只需要在非零元素上传递消息,计算量从 O(N²) 降到 O(N×dv);二是稀疏结构让 Tanner 图上的环更少,迭代译码不容易陷入局部最优。
如果随手写一个稠密矩阵当 H,译码复杂度会爆炸,而且校验方程之间高度相关,纠错能力反而下降。所以ldpc_gen_h.m的核心任务不是「生成一个矩阵」,而是「生成一个满足列重行重约束、且尽量没有短环的稀疏矩阵」。常见做法是 Gallager 随机构造法或者 PEG(Progressive Edge Growth)算法,前者简单但容易出四环,后者复杂度高但围长更好。这个脚本大概率走的是 Gallager 那一套,因为代码量小、参数直观。
2.2 列重、行重、码率三者的换算关系
在动手调脚本之前,得先把三个参数的关系理清楚,不然生成出来的矩阵要么维度对不上,要么码率不是你要的。
| 参数 | 符号 | 含义 | 约束 |
|---|---|---|---|
| 码长 | N | 码字比特数 | 正整数,通常 2 的幂或准循环倍数 |
| 校验方程数 | M | H 矩阵行数 | M = N - K,K 为信息位长度 |
| 列重 | dv | 每列 1 的个数 | 通常取 2~6,太大稀疏性变差 |
| 行重 | dc | 每行 1 的个数 | dc = N×dv / M,必须为整数 |
| 码率 | R | K/N | R = 1 - dv/dc |
关键约束是N×dv = M×dc,也就是 1 的总数按行算和按列算必须相等。如果你指定 N=1024、dv=3、dc=6,那么 M = 1024×3/6 = 512,码率 R = 1 - 3/6 = 0.5。如果 dc 除不尽,脚本要么报错要么自动调整,这就是后面避坑章节要讲的问题。
2.3 跑通 ldpc_gen_h.m:从参数到 H 矩阵
MATLAB 里调用这个脚本,一般流程是先设参数,再调函数,最后检查矩阵稀疏度。下面是我常用的调用骨架:
% 参数设置 N = 1024; % 码长 dv = 3; % 列重 dc = 6; % 行重 M = N * dv / dc; % 校验方程数,必须为整数 % 检查参数合法性 if mod(N * dv, dc) ~= 0 error('行重列重与码长不匹配,N*dv 必须能被 dc 整除'); end % 调用生成脚本(函数名以实际文件为准) H = ldpc_gen_h(N, dv, dc); % 验证稀疏性与维度 [rows, cols] = size(H); fprintf('H 矩阵维度: %d x %d\n', rows, cols); fprintf('非零元素占比: %.4f%%\n', nnz(H) / numel(H) * 100); % 检查每列每行的重是否满足设定 col_weight = sum(H, 1); row_weight = sum(H, 2); fprintf('列重范围: %d ~ %d\n', min(col_weight), max(col_weight)); fprintf('行重范围: %d ~ %d\n', min(row_weight), max(row_weight));这段代码的逻辑说明:先算 M,确保N×dv能被dc整除,这是 Gallager 构造法的硬性前提。然后调ldpc_gen_h拿到 H,接着用nnz统计非零元素占比,正常应该在dv/N量级,也就是 0.3% 左右。最后检查每列每行的重,如果列重不全是 dv、行重不全是 dc,说明脚本内部做了随机化或者填充,需要看代码确认。
参数怎么改:N 一般取 648、1296、1944 这些准循环 LDPC 常用长度,dv 取 3 性能比较均衡,dc 根据码率反推。如果要做 0.75 码率,dv=3 时 dc=12,M=N/4。注意 dc 越大,行重越高,Tanner 图上的校验节点度数越大,译码收敛越快但单次迭代计算量也越大。
2.4 从 H 到 G:ldpc_gen_g.m 的生成矩阵推导
拿到 H 之后,编码还需要生成矩阵 G。ldpc_gen_g.m的任务就是从 H 推出 G,使得G×H^T = 0(在 GF(2) 域上)。常见做法是对 H 做高斯消元,化成[I | P]形式,然后 G =[P^T | I]。这个过程在 MATLAB 里可以用gf或者mod手动实现。
% 假设 H 已经生成,维度 M x N % 对 H 做 GF(2) 高斯消元,化为系统形式 [I_M | P] H_sys = rref(mod(H, 2)); % 注意:rref 在 GF(2) 下需自行处理 % 提取 P 矩阵 P = H_sys(:, M+1:N); % 构造生成矩阵 G = [P^T | I_K] G = [P' eye(N-M)]; % 验证正交性:G * H^T 应为全零矩阵 check = mod(G * H', 2); if any(check(:)) warning('G*H^T 不为零,检查消元过程'); else disp('G 与 H 正交性验证通过'); end逻辑说明:rref是 MATLAB 内置的行最简形函数,但它不保证在 GF(2) 下正确,因为浮点运算会引入误差。更稳妥的做法是自己写 GF(2) 消元,用xor代替加减。参数方面,M 和 N 从 H 的维度直接取,K=N-M 是信息位长度。验证步骤不能省,G×H^T如果不是全零,编码出来的码字根本不在零空间里,译码必然失败。
提示:如果
ldpc_gen_g.m内部已经处理了 GF(2) 消元,直接调就行;如果它假设 H 已经是系统形式,那得先用ldpc_gen_h.m生成再手动消元。两个脚本的接口约定要看文件头的注释。
3. 避坑与排查:列重行重生成 LDPC 矩阵时最容易翻车的五件事
3.1 现象:脚本报「维度不匹配」或生成的 H 矩阵行列数不对
原因:N×dv不能被dc整除,或者脚本内部把 M 算成了浮点数。Gallager 构造法要求M = N×dv/dc必须是整数,否则最后一行的行重会和其他行不一致,脚本可能直接报错或者静默截断。
解决:在调用前加一行assert(mod(N*dv, dc) == 0, '参数不满足整除约束')。如果确实需要非整数比,得改用 PEG 或者准循环构造,不能硬套 Gallager。
3.2 现象:生成的 H 矩阵列重不均匀,有的列重是 2 有的列重是 4
原因:随机放置 1 的时候没有做列重控制,或者用了randperm但没检查每列已放置的数量。Gallager 构造法是把 H 分成 dv 个子矩阵,每个子矩阵每列只有一个 1,如果子矩阵内部随机化时冲突了,就会出现列重偏差。
解决:生成后立刻统计sum(H,1)和sum(H,2),如果偏差超过 1,说明脚本的随机化逻辑有缺陷。可以在脚本里加一个 while 循环,对不满足列重的列重新随机,直到所有列重严格等于 dv。
3.3 现象:译码仿真时 BER 曲线异常高,几乎不收敛
原因:H 矩阵里出现了大量四环(4-cycle),也就是 Tanner 图上两个校验节点和两个变量节点之间形成了长度为 4 的环。四环会让置信传播的消息在环上反复震荡,迭代译码无法收敛。
解决:生成 H 后检查四环数量。简单方法是计算H×H^T,如果非对角线上有大于 1 的元素,说明存在共享两个变量节点的校验节点对,即四环。可以用 PEG 算法重新生成,或者在 Gallager 基础上做列交换来打散四环。
3.4 现象:ldpc_gen_g.m跑完得到的 G 矩阵维度不对,或者G×H^T不为零
原因:高斯消元过程中没有在 GF(2) 域下运算,MATLAB 的rref默认是浮点运算,遇到1+1会得到 2 而不是 0,导致消元结果错误。
解决:自己写 GF(2) 消元,所有加减用xor,乘用&。或者用通信工具箱的gf对象,但要注意gf的运算速度较慢,大矩阵下可能卡住。消元后必须验证mod(G*H',2)是否全零。
3.5 现象:码长 N 取 1024 时脚本能跑,取 648 时报错或结果异常
原因:某些 LDPC 构造法对码长有隐含要求,比如必须是准循环的倍数,或者 N 必须能被 dv 整除。648 能被 3 整除,但 648/6=108,如果脚本内部假设 N 是 2 的幂,就会出问题。
解决:先看脚本文件头有没有对 N 的约束说明。如果没有,用几个不同 N 值试跑,记录哪些能过哪些不能,反推约束条件。常见做法是 N 取 2 的幂或者 3×2^k 形式,兼容性最好。
4. 进阶用法:用生成矩阵做编码、用校验矩阵做译码验证
4.1 从 G 矩阵完成一次完整编码
拿到 G 之后,编码就是信息位向量乘以 G。假设信息位msg是 1×K 的二进制向量,码字cw = mod(msg * G, 2),得到 1×N 的码字。下面是一个完整的编码加验证流程:
% 生成随机信息位 K = N - M; msg = randi([0 1], 1, K); % 编码 cw = mod(msg * G, 2); % 验证码字满足校验方程 syndrome = mod(cw * H', 2); if any(syndrome) error('编码失败:码字不满足 H*cw^T = 0'); else fprintf('编码成功,码字长度 %d,校验通过\n', length(cw)); end逻辑说明:msg * G在 GF(2) 下就是模 2 乘法,mod(...,2)保证结果只有 0 和 1。syndrome是伴随式,全零说明码字合法。这一步是后续译码仿真的前提,如果伴随式不为零,译码器再强也救不回来。
4.2 用 H 矩阵跑一次置信传播译码
译码部分这个压缩包没有直接提供脚本,但有了 H 矩阵,置信传播(BP)译码可以自己写。核心是初始化对数似然比(LLR),然后迭代更新变量节点和校验节点的消息。下面是一个最小化的 BP 译码框架:
% 假设接收到的 LLR 为 llr,长度 N % H 为 M x N 稀疏矩阵 max_iter = 50; [M, N] = size(H); L = llr; % 初始 LLR for iter = 1:max_iter % 校验节点更新 for j = 1:M idx = find(H(j, :)); for k = idx others = idx(idx ~= k); sign_prod = prod(sign(L(others))); min_val = min(abs(L(others))); R(j, k) = sign_prod * min_val; end end % 变量节点更新 for i = 1:N idx = find(H(:, i)); L(i) = llr(i) + sum(R(idx, i)); end % 硬判决与伴随式检查 cw_hat = L < 0; if ~any(mod(cw_hat * H', 2)) fprintf('第 %d 次迭代译码成功\n', iter); break; end end逻辑说明:这是最小和(Min-Sum)译码的简化版,用min代替了tanh乘积,复杂度低但性能略差。R(j,k)是校验节点 j 传给变量节点 k 的消息,L(i)是变量节点的后验 LLR。每次迭代后做硬判决,如果伴随式全零就提前退出。参数max_iter一般取 50,码长越长需要迭代次数越多,但超过 100 次基本没有增益。
4.3 验证生成矩阵和校验矩阵是否匹配的一个小技巧
有时候ldpc_gen_h.m和ldpc_gen_g.m是分开调的,两个脚本可能用了不同的随机种子,导致生成的 H 和 G 不匹配。验证方法很简单:随机生成一组信息位,用 G 编码得到码字,再用 H 算伴随式。如果伴随式全零,说明匹配;否则两个矩阵来自不同的构造过程。
我一般会在脚本开头固定随机种子rng(42),这样每次生成的 H 和 G 都是确定性的,方便复现和调试。如果要做蒙特卡洛仿真,再在外层循环里改种子。
注意:MATLAB 的
rng在不同版本间行为可能略有差异,跨版本复现时最好把生成的 H 和 G 保存成.mat文件,直接加载而不是每次重新生成。
4.4 从列重行重到性能调优的一个习惯
列重和行重不是随便设的。dv=3 是大多数标准 LDPC 码的默认选择,因为它在稀疏性和纠错能力之间平衡得最好。dv=2 的码虽然更稀疏,但容易出现停止集,译码性能会掉。dv 超过 6 之后,H 矩阵的稀疏性明显变差,BP 译码的单次迭代计算量上升,而纠错增益递减。
行重 dc 由码率决定,R=0.5 时 dc=2×dv,R=0.75 时 dc=4×dv。如果要做高码率,dc 会比较大,这时候要注意 Tanner 图上校验节点的度数,度数太高会让最小和译码的近似误差累积。
从那以后我每次生成 H 矩阵,都会先跑一遍sum(H,1)和sum(H,2)确认列重行重严格符合设定,再算一次H*H'看四环数量,最后用随机信息位做一次编码-译码闭环。这三步走完,才敢把矩阵丢进仿真链路里。希望帮到你。
本文还有配套的精品资源,点击获取