简介:面向通信与编码理论研究者及高年级学生,这份压缩包提供了一套完整的非二进制LDPC码EXIT特性分析MATLAB代码。非二进制LDPC码在GF(2^m)域上运行,相比二进制版本具有更优性能,而EXIT图是评估迭代解码潜力的关键手段。包内覆盖非二进制互信息函数计算、校验矩阵构造、消息传递/贝叶斯解码、EXIT图绘制及不同信噪比与迭代次数下的性能仿真闭环,可直接修改参数运行,便于复现论文结果或开展编码设计实验。资源共34个文件,其中30个m脚本为算法主体,2个mat数据文件提供互信息与校验节点/变量节点多项式查找表,2个txt文档为使用说明。整个压缩包仅362KB,体量轻、模块划分清晰。目前已有227人学习下载,适合需要深入理解非二进制LDPC码迭代解码过程、或从事5G/6G及存储信道编码研究的入门与进阶用户。
1. 非二进制LDPC的EXIT分析:解码阈值怎么被两条曲线算出来
做非二进制LDPC码设计的前几周,我一直在跟二进制工具较劲:把校验矩阵换到GF(4)上之后,EXIT图怎么画都不对,两条曲线永远差一截。后来才意识到问题不在译码器,而在互信息的度量方式——非二进制系统里消息不是单个LLR,而是一组符号概率,EXIT曲线必须重新定义。这份MATLAB包就是干这件事的:它用经验估计加多项式逼近两条路线,把变量节点(VND)和校验节点(CND)的外信息曲线算出来,两条曲线的交点直接给出迭代译码阈值,之后还能用线性规划反推度分布。适合正在设计NB-LDPC码、需要快速验证某个度分布能不能收敛,或者要给论文补非二进制EXIT图的人。码率、域阶数、节点度都能调,跑通main.m就能出图,这在同类工具里算很省事的。
2. GF(2^m)域运算与LLR映射:加减乘除、概率卷积和符号对数化
非二进制LDPC和二进制最大的区别,是所有校验方程都在GF(q)上做。二进制里校验节点就是一个异或门,翻到GF(4)、GF(8)之后,校验矩阵里的非零元素都是域元素,消息更新也变成域上的运算。所以拆这套代码的第一步,是先把GFqAdd、GFqMul、GFqConv这三个基础函数看明白。
2.1 GFqAdd与GFqMul:先确认你算的是域运算,不是整数运算
我见过不止一个新手把GF(4)乘法当整数乘法写,结果整条EXIT曲线都是错的说不出原因。域上加法和二进制比特串的按位异或等价,乘法则要靠本原元和对数表。包里GFqAdd、GFqMul就是干这个的,核心逻辑如下。
% GFqAdd.m —— GF(2^m) 加法:对二进制表示按位异或 function c = GFqAdd(a, b) % a, b 是 [0, 2^m-1] 的整数,二进制位即 GF(2^m) 元素 c = bitxor(a, b); end加法这一步没有玄学,bitxor就结束了。注意入参是整数0到q-1,不是比特向量,用bitxor刚好落在域上。传进来之前先确认a, b < 2^m,越界了运算结果没有意义。
% GFqMul.m —— 用对数-指数表做 GF(2^m) 乘法 function c = GFqMul(a, b, expTab, logTab) % expTab(k) = alpha^(k-1),logTab(alpha^(k-1)) = k-1 % alpha 是本原元,expTab/logTab 在初始化脚本里预先生成 if a == 0 || b == 0 c = 0; % 0 没有对数,必须单独处理 return; end k = mod(logTab(a) + logTab(b), length(expTab)); c = expTab(k + 1); end乘法用查表,logTab(a)+logTab(b)对应指数相加,模q-1是因为非零元构成循环群,alpha^(q-1)=1。两个表在初始化时生成一次,之后所有节点更新都能复用。注意MATLAB索引从1开始,所以取expTab(k+1)。除法不用单独写函数,乘上逆元就行,逆元也能用同一张表查。
2.2 GFqConv:校验节点更新里最核心的概率卷积
GFqConv这个名字容易误读成"域上的多项式卷积",实际上在EXIT计算里它的用途是概率域卷积:两条独立的域元素消息,它们的和(域加法意义下)的分布。校验节点做更新时,dc-1条输入消息的概率分布要两两卷积,得到输出消息。直白点说,这就是非二进制版本的"校验约束传播"。
% GFqConv.m —— GF(q) 上的概率卷积:Z = X + Y(域加法)的分布 function pz = GFqConv(px, py, q) % px, py: 长度为 q 的概率向量,分别对应 X、Y 的符号分布 % pz: Z = X + Y 的分布,X、Y 独立 pz = zeros(1, q); for x = 0 : q - 1 for y = 0 : q - 1 z = bitxor(x, y); % 域加法等价于按位异或 pz(z + 1) = pz(z + 1) + px(x + 1) * py(y + 1); end end end逻辑上就是穷举所有符号对,把概率乘起来累加到和的符号上。复杂度是O(q²),GF(4)、GF(8)时完全够用;真跑到GF(64)以上,就得换加群上的快速卷积(Walsh-Hadamard变换),但包里默认域阶数不会那么大。调用前确认px、py各自求和为1,否则卷积结果会走偏,EXIT曲线在低SNR段会莫名下坠。
2.3 LLR与unLLR:符号概率和对数域之间的双向切换
消息在变量节点和校验节点之间来回传,但两边的"坐标系"不一样。变量节点更新时消息用LLR相加,校验节点做卷积时又必须回到概率域。所以包里的LLR.m和unLLR.m负责来回切换,这也是非二进制实现比二进制麻烦的核心点。
% LLR.m —— q 维概率向量转 (q-1) 维 LLR 向量,参考符号取 0 function L = LLR(p) q = length(p); L = zeros(1, q - 1); for a = 1 : q - 1 L(a) = log(p(a + 1) / p(1)); % 参考符号 0 对应 p(1) end end % unLLR.m —— LLR 向量还原成完整 q 维概率,用于校验节点卷积 function p = unLLR(L) q = length(L) + 1; p0 = 1 / (1 + sum(exp(L))); % 由归一化条件反解 P(0) p = zeros(1, q); p(1) = p0; for a = 1 : q - 1 p(a + 1) = p0 * exp(L(a)); end endLLR的参考符号固定取0,向量长度是q-1而不是q,因为归一化条件把最后一个自由度消掉了。unLLR是逆变换,p0由归一化条件解出来,再逐个指数还原。注意这两个函数在网络里被反复调用,每次迭代VND进CND之前都要走一遍unLLR,出来再走LLR。参考符号一旦在初始化时改过,后面所有J_Empirical的估计都要跟着改,否则后面算出来的互信息系统性偏低。
3. EXIT曲线的三根支柱:J函数、经验估计、VND与CND两条曲线
拿到域运算之后,下一步是理解EXIT曲线本身。非二进制EXIT图里横轴纵轴都是互信息,单位是比特/符号,取值范围0到log2(q)。包里的J函数族、经验估计函数和两条节点曲线,分别对应"理论互信息"、"实测互信息"、"整条EXIT曲线怎么拼"三件事。
3.1 J函数族辨析:Calc_J、Calc_J_R和带Minus后缀的差异
二进制EXIT图里有个经典的J函数,把高斯分布的方差映射成互信息。非二进制沿用这个概念,但消息从一维LLR变成q-1维向量,理论J函数不再有闭式解,所以包里有Calc_J、Calc_J_R和一堆带Minus后缀的变体。命名规律是这样的:
| 函数 | 含义 | 使用场景 |
|---|---|---|
Calc_J.m | 通用J函数,LLR向量到互信息的映射 | 变量节点输出的理论估计 |
Calc_J_R.m | 信道侧J函数,由信道SNR换算信道互信息 | 初始化阶段的信道消息 |
Calc_J_Minus.m | 扣除先验后的外信息互信息 | VND外信息曲线 |
Calc_J_R_Minus.m | 信道相关的扣除先验版本 | CND曲线初始化参考 |
带Minus后缀的版本做的是同一件事:EXIT图画的是外信息,不是后验信息。每次迭代里消息的互信息包含一部分来自自身先验的贡献,外信息要把这部分扣掉,否则曲线会整体偏高,交点位置失真。这组函数常见做法是用经验数据拟合多项式系数,把系数存到JPols.mat里,运行时直接代入求值,省得每次都跑蒙特卡洛。
3.2 经验互信息怎么算:GaussianSamples、InitialSamples与J_Empirical
理论J函数只能覆盖部分场景,真正要出可发表的图,还是得靠经验估计。包里的GaussianSamples.m在给定方差下生成高斯LLR采样,InitialSamples.m生成信道初始消息的采样,J_Empirical.m则做最后一步:从采样估计互信息。
% J_Empirical.m —— 用采样估计 LLR 向量与发送符号之间的互信息 function I = J_Empirical(L, X) % L: N x (q-1) 的 LLR 采样矩阵,X: N x 1 的发送符号(范围 0..q-1) q = size(L, 2) + 1; N = size(L, 1); logLik = zeros(N, q); logLik(:, 2:end) = L; % 参考符号 0 对应的 LLR 恒为 0 logLik = logLik - max(logLik, [], 2); % 每行减最大值,防 exp 溢出 p = exp(logLik); p = p ./ sum(p, 2); idx = sub2ind([N, q], (1:N)', X(:) + 1); pX = p(idx); I = mean(log2(q .* pX)); % 均匀先验下的经验互信息 end核心是mean(log2(q .* pX)):发送符号均匀分布时,互信息等于log2(q)减去条件熵,而条件熵的采样估计就是每个样本被正确识别概率的对数平均。减去行最大值那一步是数值稳定处理,q大一点或者SNR高了之后exp(L)会爆,减掉最大值再指数,结果不变但不会溢出。采样数N建议不低于1e5,低于这个数曲线在低SNR段抖动非常明显。
3.3 VND与CND的经验曲线:Calc_VND_Empirical和Calc_CND_Empirical的算法骨架
两条曲线的算法方向正好相反。变量节点外信息在LLR域做加法,各条输入消息的LLR直接相加,所以Calc_VND_Empirical的核心是生成采样、按度相加、再估计输出互信息。校验节点则要回到概率域做卷积,Calc_CND_Empirical会重得多。
% Calc_CND_Empirical.m —— 校验节点外信息的经验估计(简化骨架) function Ie = Calc_CND_Empirical(IaVals, dc, q, numSamples) % IaVals: 输入互信息扫描点,dc: 校验节点度,q: 域阶数 Ie = zeros(size(IaVals)); for i = 1 : length(IaVals) sigma = J_inv(IaVals(i)); % 互信息反推高斯方差 Ls = GaussianSamples(numSamples, dc - 1, sigma); % dc-1 条输入消息 Xs = randi([0, q - 1], numSamples, 1); % 发送符号均匀分布 Le = zeros(numSamples, q - 1); for n = 1 : numSamples p = unLLR(Ls(n, 1, :)); % 先回到概率域 for j = 2 : dc - 1 p = GFqConv(p, unLLR(Ls(n, j, :)), q); % 逐条卷积 end Le(n, :) = LLR(p); % 再转回 LLR 域 end Ie(i) = J_Empirical(Le, Xs); end enddc-1条输入消息是因为算外信息要扣掉目标边自己那条。先unLLR回概率域做dc-2次卷积,再LLR转出去估计互信息,这套流程在包里的实际实现完全一致,只是采样组织方式更省内存。复杂度是O(numSamples * dc * q^2),所以跑GF(16)、dc=8以上会明显变慢,这也是为什么包里要做Compute_CND_Approximation多项式逼近——逼近版本把整条曲线秒算出来,经验版本留作最终验证。
4. 从main.m跑通整套流程:参数设置、调用链与结果落盘
函数看明白了,就该把整包跑起来。按我的习惯,先看main.m怎么调,再回头抠细节,最后才信结果。
4.1 main.m的调用链:从参数到两条曲线的完整路径
包内main.m的调用链大致如下,顺序是固定的:参数初始化 → 加载J函数多项式表 → 扫描SNR算VND曲线 → 扫描SNR算CND曲线 → 画图 → 存结果。下面是从主脚本里整理出来的核心段落。
% main.m —— 非二进制 EXIT 主流程(按包内脚本整理) clear; clc; % ---- 域参数与码参数 ---- q = 4; % 域阶数 GF(2^m),q = 2^m m = log2(q); dv = 3; % 变量节点平均度 dc = 6; % 校验节点平均度 rate = 1 - dv / dc; % 设计码率 % ---- 扫描的 SNR 区间与采样配置 ---- EbN0dB = 0 : 0.2 : 5; snr = EbN0dB + 10 * log10(rate) + 10 * log10(m); % Es/N0 换算 numSamples = 1e5; Ia = linspace(0, 0.999, 20); % ---- 先加载多项式表,避免重复蒙特卡洛 ---- [JRPols, CNDPols] = Load_J_Pols('JPols.mat'); % ---- 逐点计算两条曲线 ---- for k = 1 : length(snr) Ie_vnd(k, :) = Calc_VND_Empirical(dv, q, snr(k), Ia, numSamples); Ie_cnd(k, :) = Calc_CND_Empirical(dc, q, Ia, numSamples); end % ---- EXIT 图:CND 曲线横轴翻转后与 VND 画在一起 ---- plot(Ia, Ie_vnd(end, :), 'b-', Ia, 1 - Ie_cnd(end, :), 'r--');snr那行是重点:非二进制符号携带m个比特,Es/N0和Eb/N0之间要乘码率再乘m,漏了m整条曲线在横轴上平移,交点阈值全错。CND曲线画图时横轴取1 - Ia,这是EXIT图的约定,因为校验节点的先验来自变量节点的外信息,方向相反。Ia扫到0.999而不是1,是为了避开J_inv(1)发散。
4.2 参数怎么设:q、度分布、SNR区间和采样点数的选择
参数设置直接影响结果可信度,我一般按下面这套经验值起步,再根据具体码型调。
| 参数 | 建议起步值 | 说明 |
|---|---|---|
| q | 4 或 8 | 先跑小域验证流程,确认无误再上16 |
| dv / dc | 3 / 6 | 对应码率0.5,EXIT图最容易看出交点 |
| EbN0dB | 0 ~ 5 dB,步进0.2 | 步进太大交点精度不够 |
| numSamples | 1e5 | 低于5e4曲线毛刺明显 |
| Ia 扫描点 | 20 ~ 30 | 点太少曲线不平滑,LP设计时要用50以上 |
域阶数q和m要同步改,q=8对应m=3,snr换算里的m写错了会直接偏4.7 dB。度分布dv/dc决定码率,EXIT设计的目标就是在给定码率下找能让两条曲线不相交的度分布,参数区这两行是后面第6章LP设计的输入。采样点数翻倍能把曲线磨平,但CND经验估计的时间也翻倍,先跑一遍确认流程没问题,再调大做正式结果。
4.3 结果落盘与复用:JPols.mat和CND_and_JR_Pols.mat的存取
经验估计慢,所以包里有Store_J_Pols.m、Store_CND_and_JR_Data_To_File.m两个存表函数,把拟合好的多项式系数写到.mat文件里,下次启动直接加载。Load_J_Pols.m和Load_CND_and_JR_Data_From_File.m负责读回来。
% 存表:第一次跑完经验曲线后,把系数写盘 Store_J_Pols('JPols.mat', JRPols); Store_CND_and_JR_Data_To_File('CND_and_JR_Pols.mat', CNDPols, JRPols); % 读表:后面所有实验直接从文件加载 [JRPols, CNDPols] = Load_J_Pols('JPols.mat'); [CNDPols, JRPols] = Load_CND_and_JR_Data_From_File('CND_and_JR_Pols.mat');这里有个惯用原则:经验曲线只在参数没变的时候可信。一旦改了q、dv/dc或SNR范围,必须重新跑经验估计再存表,否则旧系数会给你一条看似光滑但完全错误的曲线。包里的Readme.txt对两个.mat文件的格式有说明,第一次用之前先读一遍。另外压缩包里的www.pudn.com.txt只是来源站的说明页,跟技术流程无关,可以忽略。
5. 避坑指南:非二进制EXIT仿真里最常见的五个翻车现场
这套代码我前前后后跑了一个多月,踩过的坑基本集中在下面五个地方。每条都是血泪经验,按"现象 → 原因 → 解决"写,方便你遇到问题时直接对号入座。
5.1 现象:CND曲线不光滑,全是毛刺
这是个高频翻车点。Calc_CND_Empirical里每个互信息扫描点要做numSamples次dc-1维卷积,本地机器上开了并行才勉强能跑。毛刺的根源是采样数不够——特别是dc较大时,校验约束命中小概率符号的次数很少,经验分布尾部全是噪声。
原因:numSamples低于5e4,或者q比较大但采样数没跟着涨。解决:把numSamples提到2e5以上;急着出图就用Compute_CND_Approximation.m的解析逼近版,经验版本留到最终验证。我再补一句,毛刺也可能是Ia扫描点不均匀导致的,改成linspace(0, 0.999, 30)会比随机散布稳定得多。
5.2 现象:EXIT曲线在高SNR端封顶在0.8左右,上不去
曲线不收敛到log2(q),停在中间某个值。最先怀疑的不应该是算法,而是你自己的数据管线。
原因:最常见的是LLR.m里参考符号没统一。一处用符号0做参考,另一处默认用符号q-1,两段代码算出来的互信息差出固定一块。解决:全局搜一遍LLR(调用点,确认所有参考符号都是0;unLLR的还原逻辑跟LLR的参考符号必须成对。另一个可能原因是发送符号采样不均匀,randi出来的分布偏了,经验互信息被低估。
5.3 现象:GF(16)及以上结果明显偏离仿真
域阶数一高,EXIT预测的解码阈值和实际BP译码仿真对不上,差值能到0.5 dB以上。
原因:高斯近似在高阶域上失效。二进制和高斯假设在小域上还算凑合,q到16、32之后,LLR向量的真实分布跟高斯差太远,理论J函数和Compute_J_Approximation的多项式拟合都救不回来。解决:q >= 16时强制走J_Empirical和Calc_VND_Empirical的经验路线,放弃解析逼近;论文图里同时标注理论曲线和经验曲线,审稿人最吃这套。
5.4 现象:线性规划设计出的度分布码率对不上
用LambdaLinProgDesign跑出来的度分布,代回码率公式发现和目标码率差出一截。
原因:码率公式用边视角度分布算,rate = 1 - dv / dc里dv = (sum(lambda_d / d))^{-1}。很多人直接把lambda_d代进平均度公式,少除了一个d,码率自然错。解决:先验算sum(lambda_d)是否为1,再用dv = 1 / sum(lambda_d ./ d)算平均度,最后核对rate。这个坑我踩了一整天,最后是拿一个已知的规则码度分布做单元测试才定位到的。
5.5 现象:fzero求交点失败,两条线怎么看都不相交
两条曲线明明该相交,Myfzero报错,或者交点落在(0,0)原点。
原因:两条曲线是离散采样后线性插值的,如果Ia网格没包住交点所在区间,插值出来就找不到零点。另外CND曲线横轴翻转后不再单调,fzero在非单调区间上会乱跑。解决:先在粗网格上扫描Ie_vnd - (1 - Ie_cnd)的符号变化,找到交点所在区间,再用Myfzero在这个小区间上精确定位。Myfzero的容差我一般设置到1e-6,太小了在高SNR段收敛不动。
6. 进阶:用线性规划反推度分布,并用EXIT交点验证设计
跑通两条曲线只是第一步,包里的LambdaLinProgDesign.m和RhoLinProgDesign.m才是设计工具。
6.1 把EXIT收敛条件写成LP约束
给定校验节点度分布和目标码率,LambdaLinProgDesign用线性规划反推变量节点度分布。核心思路是把"两条曲线不相交"写成一组线性不等式:对每个互信息x,变量节点的输出曲线必须压在翻转CND曲线之上。这一步的关键在于,每个度d对应的VND输出互信息J(sqrt((d-1)*Jinv(x)^2 + sigma_ch^2))只跟x和d有关,可以预先算成系数矩阵,约束就变成对lambda的线性不等式。
% LambdaLinProgDesign.m 的约束构造核心(简化) Dmax = 15; % 最大变量节点度 x = linspace(0.05, 0.95, 100); % 互信息采样网格 sigmaCh = 0.8; % 由目标 SNR 换算 % 预计算:每个度、每个网格点的 VND 输出互信息 for i = 1 : length(x) for d = 2 : Dmax vnd(i, d - 1) = J(sqrt((d - 1) * (Jinv(x(i)))^2 + sigmaCh^2)); end end % LP:目标是最小化平均变量节点度(等价于最大化码率) f = -1 ./ (2 : Dmax); % 目标系数取负,linprog 求最小 Aeq = ones(1, Dmax - 1); % sum(lambda) = 1 beq = 1; lb = zeros(1, Dmax - 1); % 收敛约束:vnd * lambda >= 1 - CND(1 - x),移项成 A*lambda <= b A = -vnd; b = -(1 - cndFlip(x))'; % cndFlip 由 Calc_CND_Empirical 得到 lambda = linprog(f, A, b, Aeq, beq, lb, []);注意cndFlip(x)要单独调一次Calc_CND_Empirical,别在约束里重复跑蒙特卡洛,否则慢到怀疑人生。d从2起扫是因为度1的变量节点没有校验约束能力,一般直接禁止。跑完lambda之后,用包里的CompleteLambdas.m把缺失的度补全并归一化,得到可直接用的度分布。RhoLinProgDesign的道理一样,只是把约束和变量对调,两者交替迭代几次就能收敛到一组互相匹配的度分布。
6.2 用BP译码验证EXIT预测的阈值
设计完度分布,最后一步是验证。我的验证流程固定两步:先在EXIT图上读交点SNR,再跑一遍完整的非二进制BP译码看瀑布区位置。两者差在0.2 dB以内就算合格。包里Expected_log.m是辅助验证的好工具,它统计每轮迭代消息的期望对数似然增长,如果译码在EXIT预测的阈值上方0.1 dB就开始收敛,说明设计没问题;如果在阈值下方还能收敛,说明EXIT曲线本身偏悲观了,回去查CND经验采样的点数。
这套流程走完,你对"这个度分布为什么能行"就有了闭环证据。从那以后,我每次换q或改度分布,都强制先确认三件事:参考符号统一、采样点数不低于1e5、CND缓存文件版本对得上,再谈设计结果。希望帮到你。
本文还有配套的精品资源,点击获取