极化码——第一个被证明能到达香农极限的编码,我用了最简单的C语言实现

张开发
2026/4/10 20:38:04 15 分钟阅读

分享文章

极化码——第一个被证明能到达香农极限的编码,我用了最简单的C语言实现
作者绳匠_ZZ0从Turbo到LDPC我终于走到了极化码——5G控制信道的选择理论上的王者前言编码理论的终极拼图2009年土耳其裔科学家Erdal Arıkan发表了一篇论文提出了一种全新的信道编码方法——极化码Polar Code。它的革命性在于它是第一个在数学上被严格证明可以达到香农极限的编码而且编码和译码复杂度都很低O(N log N)。相比Turbo码和LDPC码的“逼近”极化码是“达到”。这一突破让它被选为5G NR标准中控制信道的编码方案eMBB场景。虽然数据信道仍用LDPC但极化码的地位无可替代。在这篇文章中我将带你从零理解极化码的核心思想——信道极化并用C语言实现一个简单的极化编码和连续消除SC译码器。因为是初学者友好我会避开复杂的线性代数只保留最直观的代码。一、极化码的直觉把好信道和坏信道分开1.1 信道极化现象假设你有一堆相同的信道比如N个二进制对称信道每个容量为I。通过一个线性变换极化变换这些信道会“极化”成两类好信道容量趋近于1几乎无噪声坏信道容量趋近于0完全噪声而且好信道的比例正好等于原信道的容量I。因此我们可以在好信道上发送信息比特在坏信道上发送固定比特冻结比特。接收端根据冻结比特的信息就能恢复出原始信息。通俗版把一堆硬币同时抛经过某种神奇的操作有些硬币变成了金块永远正面有些变成了废铁永远反面。你只在金块上刻字废铁不管。这样即使有很多废铁你也能读出金块上的字。1.2 核心变换2x2核矩阵极化码的基本构建块是一个2x2矩阵G2[1011]G2​[11​01​]这是二元域上的矩阵。将两个比特(u1, u2)经过G2变换得到(x1, x2)x1 u1 ⊕ u2 (模2加)x2 u2重复应用这个变换类似FFT的蝶形结构就可以对N2^n个信道进行极化。二、极化码的基本概念2.1 参数定义码长N2的幂次如8、16、1024等。信息位集合A容量接近1的信道索引用于传输信息比特。冻结位集合A^c容量接近0的信道索引填充已知值通常0。码率R |A| / N。2.2 编码过程给定信息比特u_A和冻结比特u_{A^c}0计算xu⋅GNxu⋅GN​其中G_N是G_2的N阶克罗内克幂。这可以通过蝶形结构快速实现。2.3 译码过程连续消除SCSC译码按顺序逐比特判决从第1位到第N位利用前面已判决的比特和接收序列y计算当前比特的后验概率如果是信息位则判决0/1如果是冻结位则直接设为已知值。整个译码复杂度为O(N log N)。三、C语言实现极简极化码编码与SC译码为了让代码清晰易读我们固定码长N8信息位位置通过事先计算好的极化顺序确定。实际中需要计算每个子信道的可靠性如巴氏参数或高斯近似但为了简化我们手动指定一组信息位。3.1 定义参数c #include stdio.h #include stdlib.h #include string.h #include math.h #include time.h #define N 8 // 码长2的幂 #define LOG2N 3 // log2(N) #define K 4 // 信息位个数 // 信息位的位置按照极化顺序从0开始索引 // 对于N8信道容量排序从高到低通常为7,6,5,3,4,2,1,0 // 我们取前4个作为信息位 const int info_pos[K] {7, 6, 5, 3}; // 冻结位标志: frozen[i]1表示信息位0表示冻结位 int frozen[N]; // 构建蝶形变换的索引用于编码 int butterfly_indices[LOG2N][N/2][2];3.2 初始化蝶形结构极化码的编码可以通过递归蝶形实现。我们预先计算每一层需要异或的索引对。c void init_butterfly() { int step 1; for (int layer 0; layer LOG2N; layer) { int block_size 1 (layer 1); int half block_size / 2; for (int i 0; i N; i block_size) { for (int j 0; j half; j) { butterfly_indices[layer][i/2 j][0] i j; butterfly_indices[layer][i/2 j][1] i j half; } } } }3.3 编码函数使用蝶形结构进行编码从最后一层向前迭代每层对配对的两个比特进行异或操作类似FFT。c void polar_encode(int *info_bits, int *codeword) { // 先构造u向量信息位填入info_bits冻结位填0 int u[N] {0}; int info_idx 0; for (int i 0; i N; i) { if (frozen[i]) { u[i] info_bits[info_idx]; } else { u[i] 0; // 冻结位固定为0 } } // 复制u到x然后执行蝶形变换 int x[N]; memcpy(x, u, sizeof(u)); // 从最后一层到第一层实际是正向变换但极化编码公式是 x u * G_N // 我们按照递归定义实现 for (int layer 0; layer LOG2N; layer) { int step 1 layer; for (int i 0; i N; i 2*step) { for (int j 0; j step; j) { int a x[i j]; int b x[i j step]; x[i j] a ^ b; x[i j step] b; } } } memcpy(codeword, x, sizeof(x)); }3.4 SC译码器简化版SC译码需要计算每个比特的似然比LLR。我们模拟BPSK调制和AWGN信道接收端得到软信息。为了简化我们直接使用接收的比特硬判决并假设信道是二进制对称信道BSC。对于真实AWGN需要计算LLR。下面是一个针对BSC的SC译码器演示逻辑c // 递归函数计算每个比特的LLR或判决 // 为了简单这里我们实现一个基于对数似然比的简化版但只展示结构 // 完整SC译码涉及递归计算f/g函数代码较长。我们提供核心框架。 // f函数: LLR合并 double f(double la, double lb) { return 2 * atanh(tanh(la/2) * tanh(lb/2)); } // g函数: LLR合并依赖于先判决的比特u double g(double la, double lb, int u) { return (u 0) ? (la lb) : (lb - la); } // 递归译码函数 void sc_decode_recursive(double *llr, int *decoded, int start, int length, int *cur_idx) { if (length 1) { // 叶子节点判决 if (frozen[start]) { // 信息位根据LLR符号判决 decoded[*cur_idx] (llr[start] 0) ? 1 : 0; (*cur_idx); } else { // 冻结位设为0 decoded[*cur_idx] 0; (*cur_idx); } return; } int half length / 2; // 计算左子节点的LLRf操作 double llr_left[half]; for (int i 0; i half; i) { llr_left[i] f(llr[start i], llr[start half i]); } // 递归译码左半部分 sc_decode_recursive(llr_left, decoded, start, half, cur_idx); // 根据左半部分的译码结果计算右子节点的LLRg操作 double llr_right[half]; for (int i 0; i half; i) { // 注意这里需要用到已译出的左子节点比特 int u_hat decoded[start/2 i]; // 索引需要调整这里示意 llr_right[i] g(llr[start i], llr[start half i], u_hat); } // 递归译码右半部分 sc_decode_recursive(llr_right, decoded, start half, half, cur_idx); } // SC译码主函数 void polar_decode_sc(double *recv_llr, int *decoded_bits) { int cur_idx 0; sc_decode_recursive(recv_llr, decoded_bits, 0, N, cur_idx); }注意上面的递归函数中索引传递有细节需要根据树结构确定decoded数组的索引。完整实现需要小心处理。为了确保代码可运行我们提供一个非递归、针对N8的简化SC译码直接硬判决不计算LLR。这仅用于教学实际性能很差。c // 超简化版SC译码直接使用接收比特硬判决通过递归复制 void polar_decode_simple(int *received_bits, int *decoded_bits) { // 对于小N8我们可以模拟递归过程但这里直接根据接收比特和信息位位置手动计算 // 不SC译码需要利用冻结位信息。 // 为了不让代码过于复杂我们采用查表法对N8所有可能接收向量有限但2^8256种。 // 但那样不够通用。我们给出一个示范框架不追求完整。 // 实际工程中SC译码的代码量约100-200行这里仅展示思想。 printf(SC译码器实现较复杂请参考完整代码库。\n); }鉴于篇幅完整的SC译码器实现需要更多代码。我会在GitHub仓库中提供可运行的完整版本。这里重点展示极化码的核心思想和编码实现。3.5 主函数测试c int main() { // 初始化冻结位标志 memset(frozen, 0, sizeof(frozen)); for (int i 0; i K; i) { frozen[info_pos[i]] 1; } init_butterfly(); // 原始信息比特 int info_bits[K] {1, 0, 1, 1}; int codeword[N]; polar_encode(info_bits, codeword); printf(极化码编码结果: ); for (int i 0; i N; i) printf(%d, codeword[i]); printf(\n); // 模拟BPSK调制和AWGN信道得到接收LLR这里简化直接模拟无错 double llr[N]; for (int i 0; i N; i) { // 假设无噪声LLR为∞或-∞实际用大数 llr[i] (codeword[i] 0) ? 100.0 : -100.0; } int decoded_info[K]; polar_decode_sc(llr, decoded_info); // 需要实现完整SC译码 printf(译码后信息位: ); for (int i 0; i K; i) printf(%d, decoded_info[i]); printf(\n); return 0; }四、性能对比极化码 vs Turbo/LDPC编码理论极限码长译码复杂度5G应用Turbo码逼近中等O(N)4G数据信道LDPC码逼近长O(N log N)5G数据信道极化码达到2的幂O(N log N)5G控制信道在短码长如几百比特下极化码的性能优于LDPC和Turbo这正是控制信道的典型场景。因此5G选择了极化码。五、我的学习心得极化码的数学很美信道极化现象是自然规律不是人为设计。SC译码虽然简单但性能不够好SCL连续消除列表译码加上CRC辅助才能接近理论性能。实现极化码比LDPC容易因为结构规整像FFT一样。5G标准中的极化码使用了CA-SCL译码列表大小通常为8性能接近最大似然。六、下一步至此我已经学完了现代信道编码的三座大山Turbo码、LDPC码、极化码。下一步我计划把这三者整合到一个信道编码工具箱中或者转向MIMO检测和OFDM。你对哪个更感兴趣

更多文章