| """State-of-the-art Graph Neural Network (GNN) and Neural Belief Propagation Decoders. |
| |
| This module implements highly optimized, parallelizable decoders for qLDPC codes |
| using PyTorch sparse operations. Graph Neural Networks (GNNs) and Neural BP |
| represent the bleeding edge of qLDPC decoding research (2024/2025). |
| |
| References: |
| - "Decoding Quantum LDPC Codes Using Graph Neural Networks" (2024) |
| - "Machine learning message-passing for the scalable decoding of QLDPC codes" (Nature, 2025) |
| """ |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| import numpy as np |
| from typing import Tuple, Optional, List |
|
|
|
|
| class NeuralBPDecoder(nn.Module): |
| """Neural Belief Propagation (BP) Decoder for qLDPC codes. |
| |
| Implements a differentiable BP-like message passing algorithm where the |
| messages are scaled by learnable weights, improving upon standard Min-Sum |
| or Sum-Product algorithms. |
| |
| Optimized for GPU execution using PyTorch sparse matrix multiplications. |
| """ |
| |
| def __init__(self, num_vars: int, num_checks: int, max_iter: int = 15): |
| super().__init__() |
| self.num_vars = num_vars |
| self.num_checks = num_checks |
| self.max_iter = max_iter |
| |
| |
| self.w_cv = nn.Parameter(torch.ones(1)) |
| self.w_vc = nn.Parameter(torch.ones(1)) |
| |
| |
| self.damping = nn.Parameter(torch.tensor(0.5)) |
|
|
| def forward( |
| self, |
| syndrome: torch.Tensor, |
| parity_matrix: torch.Tensor, |
| channel_llrs: Optional[torch.Tensor] = None |
| ) -> torch.Tensor: |
| """Run Neural BP decoding. |
| |
| Args: |
| syndrome: (batch_size, num_checks) Check node values |
| parity_matrix: (num_checks, num_vars) Sparse or dense parity check matrix |
| channel_llrs: (batch_size, num_vars) Initial log-likelihood ratios. If None, assumes 0. |
| |
| Returns: |
| (batch_size, num_vars) Predicted bit-flip probabilities |
| """ |
| batch_size = syndrome.size(0) |
| device = syndrome.device |
| |
| if channel_llrs is None: |
| channel_llrs = torch.zeros((batch_size, self.num_vars), device=device) |
| |
| |
| if not parity_matrix.is_sparse: |
| parity_matrix = parity_matrix.to_sparse() |
| |
| H_t = parity_matrix.t() |
| |
| |
| v_beliefs = channel_llrs.clone() |
| |
| |
| s_sign = 1.0 - 2.0 * syndrome |
| |
| for _ in range(self.max_iter): |
| |
| |
| v_to_c = self.w_vc * torch.sparse.mm(parity_matrix, v_beliefs.t()).t() |
| |
| |
| |
| c_vals = torch.tanh(v_to_c / 2.0) |
| |
| |
| c_msg = s_sign * c_vals |
| |
| |
| c_to_v = self.w_cv * torch.sparse.mm(H_t, c_msg.t()).t() |
| |
| |
| v_beliefs = self.damping * v_beliefs + (1 - self.damping) * (channel_llrs + c_to_v) |
| |
| |
| probabilities = torch.sigmoid(-v_beliefs) |
| return probabilities |
|
|
|
|
| class GNNLayer(nn.Module): |
| """Single layer of the Message Passing Neural Network for qLDPC.""" |
| |
| def __init__(self, hidden_dim: int): |
| super().__init__() |
| |
| self.msg_net = nn.Sequential( |
| nn.Linear(hidden_dim, hidden_dim), |
| nn.GELU(), |
| nn.Linear(hidden_dim, hidden_dim) |
| ) |
| |
| |
| self.var_update = nn.GRUCell(hidden_dim, hidden_dim) |
| self.chk_update = nn.GRUCell(hidden_dim, hidden_dim) |
| |
| def forward( |
| self, |
| v_feats: torch.Tensor, |
| c_feats: torch.Tensor, |
| H: torch.Tensor, |
| H_t: torch.Tensor |
| ) -> Tuple[torch.Tensor, torch.Tensor]: |
| """ |
| Args: |
| v_feats: Variable node features (batch, num_vars, hidden_dim) |
| c_feats: Check node features (batch, num_checks, hidden_dim) |
| H: Sparse parity matrix (num_checks, num_vars) |
| H_t: Transposed parity matrix (num_vars, num_checks) |
| """ |
| batch_size = v_feats.size(0) |
| hidden_dim = v_feats.size(-1) |
| |
| |
| |
| v_flat = v_feats.reshape(-1, hidden_dim) |
| c_flat = c_feats.reshape(-1, hidden_dim) |
| |
| |
| |
| |
| v_to_c_msgs = [] |
| for i in range(batch_size): |
| msg = torch.sparse.mm(H, v_feats[i]) |
| v_to_c_msgs.append(msg) |
| v_to_c = torch.stack(v_to_c_msgs) |
| v_to_c = self.msg_net(v_to_c) |
| |
| |
| c_flat_new = self.chk_update(v_to_c.reshape(-1, hidden_dim), c_flat) |
| c_feats = c_flat_new.reshape(batch_size, -1, hidden_dim) |
| |
| |
| c_to_v_msgs = [] |
| for i in range(batch_size): |
| msg = torch.sparse.mm(H_t, c_feats[i]) |
| c_to_v_msgs.append(msg) |
| c_to_v = torch.stack(c_to_v_msgs) |
| c_to_v = self.msg_net(c_to_v) |
| |
| |
| v_flat_new = self.var_update(c_to_v.reshape(-1, hidden_dim), v_flat) |
| v_feats = v_flat_new.reshape(batch_size, -1, hidden_dim) |
| |
| return v_feats, c_feats |
|
|
|
|
| class qLDPCGNNDecoder(nn.Module): |
| """Graph Neural Network Decoder for Quantum LDPC Codes. |
| |
| Transforms the Tanner graph of the code into a neural message passing network. |
| SOTA for highly connected qLDPC codes (like hypergraph product codes) where |
| standard MWPM fails. |
| """ |
| |
| def __init__(self, num_vars: int, num_checks: int, hidden_dim: int = 64, num_layers: int = 5): |
| super().__init__() |
| self.num_vars = num_vars |
| self.num_checks = num_checks |
| self.hidden_dim = hidden_dim |
| |
| |
| self.syndrome_embed = nn.Embedding(2, hidden_dim) |
| self.var_embed = nn.Parameter(torch.randn(1, num_vars, hidden_dim)) |
| |
| self.layers = nn.ModuleList([GNNLayer(hidden_dim) for _ in range(num_layers)]) |
| |
| |
| self.out_net = nn.Sequential( |
| nn.Linear(hidden_dim, hidden_dim // 2), |
| nn.GELU(), |
| nn.Linear(hidden_dim // 2, 1) |
| ) |
| |
| def forward(self, syndrome: torch.Tensor, parity_matrix: torch.Tensor) -> torch.Tensor: |
| """Run GNN decoding. |
| |
| Args: |
| syndrome: (batch_size, num_checks) binary syndrome tensor |
| parity_matrix: (num_checks, num_vars) parity matrix |
| |
| Returns: |
| (batch_size, num_vars) logits for each qubit being flipped |
| """ |
| batch_size = syndrome.size(0) |
| |
| if not parity_matrix.is_sparse: |
| parity_matrix = parity_matrix.to_sparse() |
| H_t = parity_matrix.t() |
| |
| |
| |
| c_feats = self.syndrome_embed(syndrome.long()) |
| |
| |
| v_feats = self.var_embed.expand(batch_size, -1, -1) |
| |
| |
| for layer in self.layers: |
| v_feats, c_feats = layer(v_feats, c_feats, parity_matrix, H_t) |
| |
| |
| logits = self.out_net(v_feats).squeeze(-1) |
| |
| return logits |
|
|
|
|