ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Polar码Matlab仿真全解析:从信道极化到SC译码实现

Polar码Matlab仿真全解析:从信道极化到SC译码实现 简介面向毕业设计与课程设计的Polar码极化码Matlab仿真资源包专为通信工程与电子信息类学生完成信道编码方向课题设计。压缩包共含56个文件、约1.69MB其中40个m脚本为核心算法源码覆盖SC、SCL、BP、SCAN、SSC等多种编码与译码实现支持128至1024等不同码长并包含Bhattacharyya界、高斯近似及蒙特卡洛等构造方式6个fig仿真图配合PNG、PDF文档直观呈现不同译码策略下的误码性能txt文件保存各参数配置对应的构造数据另有docx原理说明、README和结果图便于按需查阅。所有源码均经严格测试可直接运行帮助读者快速搭建完整实验链路深入理解极化码构造、递归译码与性能对比过程同时可根据课题需要修改信噪比、码长等参数扩展实验。目前已吸引483人学习使用适合作为毕业设计或课程设计的算法参考与实验基础。 毕设题目里塞进polar码三个字很多同学第一反应是完了这玩意儿够抽象。但等你真正把Matlab仿真的闭环跑通之后会发现它的逻辑线其实非常清晰一个信道极化定理打底一个SC译码器撑腰中间夹着一堆比特翻转和循环冗余校验的细节处理。这篇博文就以毕业设计课设-polar码的matlab仿真这个项目为引子把polar码仿真的整体设计、核心原理、代码实现、调试经验全部摊开来讲目标是帮你把仿真系统从零搭起来并且能用自己的话说清楚每一行代码在干什么。这个项目适合正在做毕业设计、课程设计或者单纯想搞懂polar码是怎么干活的人来看。我默认你至少会用Matlab跑脚本、懂一点BPSK调制和AWGN信道但即便是这两块不太熟照着后面的步骤也能把仿真跑起来。1. 项目整体设计与思路拆解1.1 先说清楚polar码在仿什么polar码的核心思想是信道极化。简单理解就是一堆并行的二进制信道通过特定的递归变换之后一部分信道容量趋近于1另一部分趋近于0。我们只需要把信息比特放在容量高的信道上把固定比特放在容量低的信道上就能逼近香农极限。这个理论的厉害之处在于它不像Turbo码和LDPC码那样靠迭代译码逼近极限而是从构造上就证明了这组信道可以做到无损传输。Arikan 2009年那篇论文直接把这套数学放到了编码界桌面上后来华为把它推成了5G增强移动宽带场景控制信道的编码方案。仿真要做的就是把信道极化这个过程变成一个可复现的Matlab流程。具体来说需要完成这几件事生成信道极化所需的可靠性排序序列也就是决定哪些位置放信息比特。实现polar码编码器把长度为K的信息比特映射到长度为N的码字上。实现调制、AWGN信道加噪、解调这条传输链路。实现SC或SCL译码器对接收序列进行译码还原出信息比特。在不同信噪比下统计误码率和误块率画出曲线。1.2 为什么选择Matlab而不是其他平台毕设时间紧绝大部分人会选Matlab而不是C或Python原因很实际Matlab的通信工具箱本身就提供了Polar码相关函数比如nrPolarEncode、nrPolarDecode这些可以直接调用也可以参考它们的行为来写自己的版本。矩阵运算友好一次性能处理整个码块的所有比特位置循环次数比C少一个数量级。绘图工具成熟semilogy画误码率曲线、berawgn算理论值都是一句代码的事。但要注意直接用工具箱函数会带来一个答辩风险老师会问这些函数内部到底做了什么。所以我的建议是仿真框架可以用工具箱做交叉验证但核心的编码、极化权重计算和SC译码器一定要自己写一遍。这篇博文里的代码就是手写版更适合作为毕设源码。1.3 项目目录规划与模块划分我建议把整个项目拆成下面这样清晰又容易调试polar_sim/ ├── main.m # 主程序控制仿真流程 ├── polar_construct.m # 信道极化构造计算可靠度序列 ├── polar_encode.m # 编码器 ├── polar_modulate.m # BPSK调制 ├── polar_channel.m # AWGN信道加噪 ├── polar_demodulate.m # 硬解调 ├── polar_sc_decode.m # SC译码器 ├── polar_bp_decode.m # BP译码器可选 ├── utility_functions.m # 辅助函数 └── results/ # 保存仿真数据与图像模块化的好处不只是看着舒服更重要的是出bug的时候能快速定位问题。比如误码率曲线不对你可以分别测试编码前后是否满足encode然后decode等于原序列这条环路信道加噪功率是否和理论计算一致译码器LLR计算是不是有符号反了。这些在后面常见问题章节会详细展开。2. Polar码核心原理与仿真设计要点2.1 信道极化从巴氏参数到可靠性排序信道极化的极化二字指的是两个独立信道通过合并分裂之后一个变好一个变差。递归地做下去最终得到N个极化信道部分信道容量接近1部分接近0。仿真里真正要用的不是这个推导过程而是一个结论加一个计算方法哪个子信道是好的哪个是坏的。常用的衡量指标叫巴氏参数Z(W)也叫信道容量损失上界。仿真中可以用密度演进或者高斯近似来计算每个极化子信道的可靠度然后排序取前K个放信息比特。Matlab里最直接的方式是使用Arikan论文中给出的递推公式计算巴氏参数function Z compute_bhattacharyya(N, design_snr) % 计算N个极化信道的巴氏参数 % design_snr用于高斯近似的初始化 sigma2 10^(-design_snr/10); Z zeros(1, N); Z(1) exp(-1/(2*sigma2)); % 初始信道巴氏参数 for n 1:log2(N) step 2^n; Z_new zeros(1, N); for j 1:step:N a Z(j:jstep/2-1); b Z(jstep/2:jstep-1); Z_new(j:jstep-1) [2*a - a.^2, b.^2]; end Z Z_new; end end这段代码的本质是奇偶分裂后一部分信道继承和信道的特性巴氏参数变大一部分继承乘积信道的特性巴氏参数变小。2a-a²对应和信道b²对应乘积信道。算好Z值之后按升序排列取前K个索引作为信息比特位置剩下的作为冻结比特位置。这里有一个实操细节design_snr的选取会影响最终性能。通常取0dB到2dB之间取太高反而可能导致实际工作在低信噪比时性能变差。2.2 极化码编码矩阵运算和递归构造polar码的编码可以用一个生成矩阵表示G F⊗n其中F [1 0; 1 1]是核心变换。在Matlab里写递归编码有两种思路。一种是直接构造生成矩阵再乘以信息比特向量另一种是递归地调用核心变换。对于N1024这样的规模直接构造矩阵再乘矩阵是1024×1024运算量还可以接受但不够直观。我推荐用递归方式实现既能体现极化过程代码量也不大function encoded polar_encode(u, F, n) % u: 长度为N的输入比特向量已包含信息位和冻结位 % F: [1 0; 1 1] % n: log2(N) if n 0 encoded u; else N length(u); u1 u(1:2:end); u2 u(2:2:end); x1 mod(u1 u2, 2); x2 u2; encoded [polar_encode(x1, F, n-1), polar_encode(x2, F, n-1)]; end end这里注意递归的输入输出排列顺序和标准生成矩阵的排序一致也就是比特反序的处理已经在递归中隐式完成了。很多同学第一次画框图的时候会被置换矩阵搞晕实际跑一遍码字生成再对比直接矩阵乘法的结果只要能对上说明实现正确。2.3 冻结比特的填充策略信息比特和冻结比特的位置确定之后冻结比特要填什么值最简单的方法是全部填0。但实际仿真中有一个坑如果全部填0那么在某些特殊码率下码字会呈现规律性导致频谱出现线谱影响误码率曲线的平滑性。更稳妥的做法是在冻结位填入来自伪随机序列的值或者干脆用固定的种子生成一串随机0/1。这样既不影响译码正确性接收端知道冻结位的值又能避免线谱问题。5G标准里其实用的是专门设计的干扰序列我们做仿真时用一个随机种子就够了。3. 仿真实现与核心代码3.1 链路框架从信息比特到误码率整体仿真流是% main.m 核心循环 N 256; % 码长 K 128; % 信息比特长度 R K/N; % 码率 numFrames 200; % 每个SNR点仿真帧数 snrRange 0:0.5:4; % 信噪比范围单位dB % 构造信道可靠度 [info_bits_positions, frozen_bits_positions] polar_construct(N, K, 1.0); for idx 1:length(snrRange) snr snrRange(idx); bitErrors 0; totalBits 0; for frame 1:numFrames % 1. 生成信息比特 infoBits randi([0 1], 1, K); % 2. 组合成输入向量冻结位置置0 u zeros(1, N); u(info_bits_positions) infoBits; % 3. 编码 codeword polar_encode(u, F, log2(N)); % 4. BPSK调制 modulated 1 - 2*codeword; % 5. 加噪根据SNR计算噪声功率 noiseVar 1/(2*R*10^(snr/10)); received modulated sqrt(noiseVar) * randn(1, N); % 6. 计算LLR llr 2 * received / noiseVar; % 7. SC译码 decoded_u polar_sc_decode(llr, F, info_bits_positions, frozen_bits_positions); % 8. 提取信息位并统计误码 decoded_info decoded_u(info_bits_positions); bitErrors bitErrors sum(decoded_info ~ infoBits); totalBits totalBits K; end ber(idx) bitErrors / totalBits; end注意这里的噪声功率计算BPSK调制后信号能量归一化为1AWGN信道噪声方差是N0/2而Es/N0 Eb/N0 10*log10(R)。noiseVar 1/(2*R*10^(snr/10))对应的是Eb/N0条件下的噪声方差其中1/2来自BPSK每比特能量除以噪声单边谱密度。3.2 SC译码器实现递归中的概率传递SC译码器是整个仿真里最绕的部分也是答辩时老师最常追问的地方。SC译码的基本思想是按照编码时的树形结构从左到右依次判决每个比特。判决顺序依赖于polar码的蝴蝶结构某个比特的LLR需要先完成前面比特的判决。实现上常用递归函数来传递LLRfunction [decoded, llrOut] sc_decode_recursive(llrIn, active_bits, F) % llrIn: 接收到的LLR序列 % active_bits: 当前层需要译码的比特序列1表示信息位0表示冻结位 N length(llrIn); if N 1 if active_bits(1) 1 decoded llrIn 0; else decoded 0; end llrOut llrIn; return; end % 分奇偶上半部分用f函数 f_llr zeros(1, N/2); for i 1:N/2 f_llr(i) 2 * atanh(tanh(llrIn(i)/2) * tanh(llrIn(iN/2)/2)); end % 简化f函数实现 % f_llr sign(llrIn(1:N/2)) .* sign(llrIn(N/21:N)) .* min(abs(llrIn(1:N/2)), abs(llrIn(N/21:N))); % 递归译码上半部分 [decoded_upper, ~] sc_decode_recursive(f_llr, active_bits(1:N/2), F); % 用g函数更新下半部分 g_llr zeros(1, N/2); for i 1:N/2 g_llr(i) llrIn(iN/2) (1 - 2*decoded_upper(i)) * llrIn(i); end % 递归译码下半部分 [decoded_lower, ~] sc_decode_recursive(g_llr, active_bits(N/21:N), F); decoded [decoded_upper, decoded_lower]; llrOut [f_llr, g_llr]; end这里有一个很多人踩过的坑f函数的计算直接用2*atanh(tanh(a/2)*tanh(b/2))会在Matlab里出现数值不稳定当LLR绝对值很大时有溢出风险。实践中常用近似公式f_llr sign(llrIn(1:N/2)) .* sign(llrIn(N/21:N)) .* min(abs(llrIn(1:N/2)), abs(llrIn(N/21:N)));这个近似叫min-sum近似它牺牲了一点性能但换来了稳定性和速度。在毕业设计里性能损失通常可以忽略而且你可以直接在论文里写采用min-sum近似降低计算复杂度——答辩加分项。3.3 结果记录与曲线绘制每跑完一个信噪比点要把BER和BLER都记下来。BLER的统计方法是看每个帧里有没有比特错误BER是统计所有传输比特中的错误比例。semilogy(snrRange, ber, b-o, LineWidth, 1.5); hold on; grid on; xlabel(Eb/N0 (dB)); ylabel(BER); title(Polar Code BER Performance over AWGN Channel); legend(sprintf(N%d, K%d, R%.2f, N, K, R));绘图的时候建议用semilogy而不是plot因为误码率在低信噪比段和高信噪比段差距可能是几个数量级线性坐标根本看不清楚。4. 仿真结果分析与复杂度和码率适配4.1 同一码长下不同码率的性能对比仿真跑出来后第一件事不是看绝对误码率而是观察趋势。拿N512举例码率RK约1e-3 BER对应的Eb/N00.25128约0.5 dB0.5256约2.0 dB0.75384约2.7 dB这组数据符合预期码率越低冗余比特越多纠错能力越强达到同样误码率所需的信噪比也越低。曲线随着SNR增加会越来越陡这就是polar码的陡峭瀑布区特性体现了信道极化带来的性能优势。4.2 SC与SCL译码的性能差异如果只做SC译码性能在这个码长下距离香农限还有一定差距。要提升性能最直接的方式是从SC换成SCL译码也就是在译码时保留L条路径而不是只保留一条。SCL译码器在实现时可以复用SC的递归结构但需要在每一层保留L个候选路径。Matlab里实现SCL常见做法是为每条路径单独维护递归状态。如果图省事也可以直接调用通信工具箱的nrPolarDecode并设置PathNumber参数来做对比自己则保留SC的源码实现。这样既保证了有自己写的代码又能展示你对不同译码算法的理解深度。4.3 仿真时间和复杂度控制polar码的SC译码复杂度是O(N log N)听起来很低但Matlab的嵌套循环会把常数项放大。实测N1024、SNR点数9个、每个点200帧手写SC的仿真时间在一台普通笔记本上大约是5到10分钟。如果时间太长优先检查两点是否在循环内部不必要地重建了临时变量。是否用Matlab的vectorization替代了内层for循环。比如上面f函数的min-sum近似一条语句就完成了原来for循环10行的功能。如果你的Matlab版本支持Parallel Computing Toolbox可以把最外层的SNR点循环改成parfor。但要注意随机数生成需要每帧独立建议用RandStream给每个worker分配独立的子流不然不同worker之间可能产生相关随机序列影响统计结果的可靠性。5. 常见问题与排查技巧实录5.1 误码率曲线不下降卡在0.5附近这个现象几乎所有人都会遇到一次。BER在0.5附近意味着译码器输出基本是随机猜测说明代码链路里有一处关键逻辑错误。优先排查顺序编码-译码闭环是否通过先不加噪声直接编码再译码验证能恢复出原始信息。如果这一步都过不了说明编码或译码逻辑有问题。冻结比特的位置是否和接收端一致信息比特位置数组和冻结数组必须完全一致否则译码器不知道该在哪里提取信息。一个常见的错误是排序时没有把索引一起排序导致信息位错位。信道LLR的符号是否反了BPSK映射如果是1对应1、0对应-1那么LLR应该是2*received/noiseVar。如果映射反了LLR就全错。5.2 SC译码器递归深度导致栈溢出Matlab递归函数的调用深度受内存限制当N2048或更大时递归深度达到log2(N)11层每一层还有大量临时变量栈溢出的风险明显增加。解决办法是改用迭代版本或把递归函数改为嵌套函数共享父函数的工作区。如果你只是想快速验证概念一个更省事的办法是利用Matlab的coder工具或者直接用数组操作模拟递归过程把每层的LLR存到一个cell数组里然后从叶子节点向上回溯。5.3 高信噪比下性能错误但低信噪比正常这个问题通常出在数值精度上。高信噪比时LLR的绝对值非常大tanh和atanh操作容易得到NaN或Inf。min-sum近似能解决这个问题但如果你选择了精确LLR计算一定要做数值截断。更简单的方法是把LLR限制在一个范围内例如max(min(llr, 30), -30)超过±30的置信度在实际译码中已经足够截断不会带来性能损失。另外一个容易被忽略的点AWGN信道的randn生成长度为N的噪声向量时如果每次循环重复用同一个随机种子误码率曲线会有锯齿状波动。正确做法是每个SNR点、每一帧都重新生成随机序列或者直接用rng(shuffle)确保随机性。5.4 误码率统计置信度不足仿真帧数太少会导致误码率在较低数值区域抖动严重看起来像是实现的错其实是统计噪声。比如在BER接近1e-4时如果想得到稳定的估计至少需要累计到100个以上的错误比特。如果仿真时间紧张可以参考这个速查表目标BER最小累计错误比特数大约所需帧数K1281e-2100约 80 帧1e-3100约 800 帧1e-4100约 8000 帧实际操作中可以先跑少量帧探测一下各SNR点的大致性能再针对低误码率区域增加帧数这种方式比所有信噪比点跑同样帧数更高效。5.5 与理论极限的对比论文里如果能画出polar码和香农限的对比图会更有说服力。香农限的计算方法是对于码率R在AWGN信道下要达到无误码传输所需的最小Eb/N0是满足C R时的信号噪声比。Matlab里可以用real(2^R - 1)再换算成dB来近似更精确的做法是通过BPSK容量公式反解。polar码在码长N1024、码率R0.5时距离香农限大约有0.3到0.8dB的差距。这个差距随着码长增加会缩小这也是polar码理论价值最好的体现。你可以在论文里这样描述随着码长增加极化效果更加充分性能逐渐逼近香农限。6. 实操过程中的经验补充6.1 做之前先画框架图我强烈建议你在动手写代码之前先在纸上画出完整的系统框图。包括发送端的比特生成、冻结比特插入、编码、调制信道接收端的解调、LLR计算、SC译码、信息提取。这个图既能指导你敲代码也是论文里必不可少的内容。画图的时候注意把每个模块的输入输出参数标注清楚特别是LLR是实数还是复数、信息位索引是行向量还是列向量。很多bug都是因为向量维度不一致导致的。6.2 每完成一个模块就单独测试模块化最大的好处就是可以单独测试。编码器写完了不着急往下走先做闭环测试生成随机比特、编码、译码比对原始消息。调制和解调模块也单独测试发射端调制后的星座图、接收端解调后的LLR分布都可以画图检查。这样你在最后联调的时候早期错误已经被过滤掉一大半剩下的错误只会集中在模块接口的匹配上。6.3 关于Matlab版本polar码相关函数在Matlab R2018b之后的通信工具箱里就已经原生支持了。如果你的版本较老建议先去MathWorks官网看看有没有对应的文档和示例。我用的是R2022anrPolarEncode和nrPolarDecode的默认行为已经比较稳定但用之前还是建议读一遍help nrPolarDecode的说明尤其注意L参数代表的是SCL译码的路径数设置为1就是SC。老版本Matlab如果连通信工具箱都没有装那就要自己手动实现所有模块。好消息是这套代码不依赖工具箱纯基础函数就能跑通。换句话说哪怕你实验室的机器没装额外工具箱也能复现。6.4 答辩时怎么讲毕设答辩时老师大概率不会让你当场推导整个极化理论但一定会问几个关键问题为什么选择polar码SC译码的原理是什么仿真结果如何验证正确性有没有和LDPC/Turbo码做对比所以做项目的时候就要有意识地为这些问题积累素材。例如在仿真结果部分除了画自己的误码率曲线还可以加一条LDPC码的性能曲线作为对比。Matlab通信工具箱里有ldpcEncode和ldpcDecode画起来并不费劲。我的个人体会是polar码这样偏理论性的题目真正拉开差距的地方不在于你能不能背诵公式而在于你能不能把信道极化-可靠性排序-编码-SC译码这条链路用自己的话讲成一个完整的故事。仿真代码是你讲故事的工具而这篇博文就是帮你把工具打磨顺手的参考。最后再分享一个实用技巧所有的源码和仿真结果图记得同步备份到网盘和本地两个地方我见过太多人答辩前一天丢失代码的惨案了。本文还有配套的精品资源点击获取
返回列表