Все, что вы хотели узнать об LDPC кодах, но стеснялись спросить (наверное)
- четверг, 27 февраля 2020 г. в 00:28:20
С кодами малой плотности проверок на чётность, которые дальше мы будем именовать коротко LDPC (Low-density parity-check codes), мне удалось познакомиться более или менее близко, работая над семестровым научным проектом в ТУ Ильменау (магистерская программа CSP). Моему научному руководителю направление было интересно в рамках педагогической деятельности (нужно было пополнить базу примеров, а также посмотреть в сторону недвоичных LDPC), а мне из-за того, что эти коды были плюс-минус на слуху на нашей кафедре. Не все удалось рассмотреть в том году, и поэтому исследование плавно перетекло в мое хобби… Так я набрал некоторое количество материала, которым сегодня и хочу поделиться!
Кому может быть интересна данная статья:
В общем, присоединяйтесь!
Внимание:
Предполагается, что читатель знаком с основами помехоустойчивого кодирования. Если тема нова, могу адресовать к своему скромному вкладу в один проект по теме:
LDPC коды — идея довольно старая, впервые они были описаны Робертом Галлагером ещё в 1963 г. в его работе на степень PhD [1]. Однако, из-за своей неоправданной сложности (по тем временам) они не находили применения в технике относительно долгое время.
И только в 1990-х годах эти коды были, так сказать, заново открыты М. Дэви и Д. Маккеем, которые предложили инновационные на тот момент способы построения LDPC кодов с уменьшенной сложностью [2].
Сейчас LDPC коды это:
1) Часть Wi-Fi стандарта (802.11n, 802.11ac, в более ранних стандартах, насколько я знаю, использовались сверточные коды);
2) Часть стандартов группы DVB-x2;
3) Часть стандарта радиочасти сетей мобильной связи 5G (NR — New Radio)[3];
4) Помехоустойчивый код, использующийся во флэш-памяти (ссылка на статью на Хабре от alexzeynikov)
Кроме того, все больше LDPC коды проникают и в спутниковую связь. В свое время, я делал небольшой обзор по малым спутникам CubeSat (посмотреть можно по ссылке) — там тенденция однозначная и обусловлена внедрением стандартов DVB-S2/S2X.
И я думаю, это прекрасная мотивация узнать о данных кодах немного больше.
LDPC коды — это линейные блочные коды, а значит проверочные биты в данной схеме кодирования добавляются в конец информационного сообщения — блоком.
Соответсвенно, процедура кодирования (encoding) — есть ничто иное, как перемножение вектора информационного сообщения длинной на некоторую порождающую матрицу
:
где символ — это умножение по модулю (см. modulo).
Для двоичных кодов это modulo 2, для недвоичных modulo q, исходя из полей Галуа .
Соответственно, и кодовая скорость тоже задается через порождающую матрицу:
Порождающая матрица состоит из двух конкатенированных (соединенных) частей:
где — это, так называемая, четная (parity) часть, а
— единичная (identity) матрица.
Дело в том, что при умножении и сложении по модулю нужно соблюдать правила сопоставления отрицательных и положительных чисел:
На двоичном случае все это незаметно, и поэтому минус иногда пропускают.
Так как мы говорим о линейных блочных кодах, порождающая матрица и должна обеспечивать эту линейность (см. Linear code). То есть, строки порождающей матрицы должны быть линейно независимыми (да, на слух звучит немного парадоксально).
Обратите внимание, identity-часть нужна для того, чтобы оставлять код систематическим: информационное сообщение остается неизменным, а проверочные биты добавляются в конец блоком. При такой схеме, правильно восстановив кодовое слово, можно восстановить и изначальное сообщение, просто убрав проверочные биты. Удобно, не правда ли?
Порождающая матрица напрямую связана с другой важнейшей матрицей, использующейся во время процедуры декодирования: с матрицей проверки на четность (parity-check matrix).
Матрица проверки на четность имеет строк и
столбцов, где
соответствует требуемой длине кодового слова, а
, повторим, соответствует длине сообщения:
Ее основную идею очень удобно объяснять с помощью графа Таннера:
То есть существует два вида узлов: так называемые, узлы переменных (variable nodes), количество которых соответствуют числу столбцов , и узлы проверки (check nodes), соответствующие числу строк (
). Узлы связаны между собой, и связь определяется положением единиц в матрице
.
Картинка справа — это моя собственная мнемоничка моего же производства. Как мне кажется, это самый простой способ уловить суть структуры: если элемент матрицы равен 1, значит связь между узлами есть, если равен 0 — связи нет.
Для того, чтобы считать процедуру декодирования успешной, нужно, чтобы на всех проверочных узлах сформировались определенные значения — как правило, нули (см. декодирование на основе синдромов):
Собственно говоря, эта матрица и определяет последние две буквы в аббревиатуре LDPC (Parity-Check).
Но всё выше описанное — это общие моменты для большинства блочных кодов. Чем же тогда LDPC отличаются от тех же кодов Хэмминга?
В общем-то, тем, что и определяет их как low-density: их матрицы проверки на четность должны быть разряженными (sparce), то есть нулей в них должно быть значительно больше, чем чего-либо другого:
"Low density parity check codes are codes specified by a parity check matrix containing mostly zeros and only small number of ones." [1]
Да, вот так просто.
Например, у того же Галлагера данная матрица была такой:
(3,4)-регулярная матрица проверки на четность длинною 12.
У Маккея и Нила матрица проверки на четность была такой:
(3,4)-регулярная матрица проверки на четность длинною 12.
В стандарте DVB-S2 приняты уже нерегулярные (irregular) матрицы проверки на четность. См.:
Связано это с лучшей помехоустойчивостью нерегулярных схем.
Однако, ничего не замечаете? Правильно: эти матрицы не попадают под стандартную форму из формулы (3), ведь для LDPC кодов мы стремимся сделать проверочные матрицы разреженными. А если матрицы проверки не попадают под стандартную форму, значит не совсем понятно, как для них формировать порождающие матрицы.
Ответ, конечно, есть (и не один). Допустим, такой: изначальную матрицу приводят к стандартной форме через метод Гаусса (Gaussian elimination), из стандартной формы получают порождающую матрицу, а ее используют для кодирования.
Приведем пример из данного учебного материала:
Была такая матрица :
import numpy as np
H = np.array([[1, 1, 0, 1, 1, 0, 0, 1, 0, 0],\
[0, 1, 1, 0, 1, 1, 1, 0 ,0 ,0],\
[0, 0, 0, 1, 0, 0, 0, 1, 1, 1],\
[1, 1, 0, 0, 0, 1, 1, 0, 1, 0],\
[0, 0, 1, 0, 0, 1, 0, 1, 0, 1]])
От нее, путем перемещений и преобразований строк по модулю 2, а также перемещений столбцов, перешли к матрице :
Hstd = np.array([[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],\
[1, 0, 1, 0, 0, 0, 1, 0 ,0 ,0],\
[1, 0, 1, 0, 1, 0, 0, 1, 0, 0],\
[0, 0, 1, 1, 1, 0, 0, 0, 1, 0],\
[1, 1, 0, 0, 1, 0, 0, 0, 0, 1]])
Преобразования со строками с точки зрения линейной алгебры не влияют на кодовое слово, а вот перемещения столбцов нужно запомнить:
idx = [5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
Формируем порождающую матрицу:
M = np.shape(H)[0] # N-K
N = np.shape(H)[1]
K = N - M
G = np.concatenate([np.eye(K), ((-1)*Hstd[:, :K].T %2)], axis=1)
print("Generator matrix:\n"+str(G))
>>> Generator matrix:
[[1. 0. 0. 0. 0. 0. 1. 1. 0. 1.]
[0. 1. 0. 0. 0. 1. 0. 0. 0. 1.]
[0. 0. 1. 0. 0. 1. 1. 1. 1. 0.]
[0. 0. 0. 1. 0. 1. 0. 0. 1. 0.]
[0. 0. 0. 0. 1. 0. 0. 1. 1. 1.]]
Создаем кодовое слово:
c = np.array([1, 0, 1, 0, 1]) @ G %2
print(str(c))
>>> array([1., 0., 1., 0., 1., 1., 0., 1., 0., 0.])
И проверяем синдром:
c[idx] @ H.T %2
>>> array([0, 0, 0, 0, 0])
Магия линейной алгебры сработала!
Завершая раздел, нужно сказать, что такой метод кодирования самый простой для понимания, однако весьма сложный для вычисления в случае больших матриц — порождающая матрица, как правило, перестает быть разряженной. Конечно, на все это есть свои решения, однако, это уже совсем другая история...
По LDPC кодам есть неплохой подбор материалов на Medium:
Однако, лично мне объяснение одного из центральных и самых, наверное, популярных алгоритмов декодирования — алгоритма Belief propagation (aka SPA — Sum-product algorithm) показалось, мягко говоря, слишком формальным (там просто прикреплена научная статья). Душа просит картинок и примеров!
За основу возьмем уже знакомый нам учебный материал:
Итак, во-первых, предположим, что у нас есть некая система связи:
Система связи состоит из:
Передатчик состоит из :
Приемник состоит из:
Договоримся, что под цифровыми модемами будем понимать в первую очередь самые популярные их разновидности: PSK и QAM.
Чем интересны для нас данные типы модуляции? Во-первых, тем, что именно они входят в стандарты современных беспроводных систем (LTE, Wi-Fi, DVB и т.д. ).
А, во-вторых, тем, что они умеют представлять зашумленные значения, полученные из канала связи, в форме, так называемых, мягких значений демодуляции (soft decisions). Или, если выражаться более наукообразно, в форме логарифмированных коэффициентов правдоподобия (LLR — log likelihood ratios):
где обозначает вероятность, а
обозначает некоторое событие.
Мне чаще попадался случай, когда в числителе оставляют вероятность, что бит изначально был нулем, а в знаменателе — что был единицей. Соответственно, если значение LLR на выходе демодулятора отрицательное, мы можем предположить, что скорее всего, бит был нулевым (и наоборот, см. NRZ).
Может, и так — особенной разницы нет, главное, чтобы ваши приемник и передатчик были одинаково осведомлены о выбранной вами схеме наложения (маппинга). Мне кажется, популярность такого нелогичного на первый взгляд NRZ (0 -> +1, 1 -> -1) продиктована в первую очередь распространенностью MatLab в кругах телекоммуникационщиков.
Несложно догадаться, что схема, состоящая только из источника сообщений и модема, весьма чувствительна к сильным шумам, а значит и к ошибкам демодуляции. Благо, что мы включили в нашу схему помехоустойчивый (канальный) кодек. Декодер нам эту ошибку как раз и исправит. А точнее тот алгоритм, который в этот самый декодер зашит.
Итак, Belief propagation.
Потому что алгоритм работает с вероятностями. А точнее, с теми натуральными логарифмами от отношений вероятностей, которые мы указали в формуле (4).
Потому что эти вероятности будут итеративно "пересылаться" от узлов переменных к узлам проверки (сообщение V2C — Variable-to-Check) и наоборот (сообщение C2V — Check-to-Variable).
Под пересылкой сообщений между узлами проверки и переменных понимается то, что LLR будут складываться и перемножаться по определенным формулам.
На этапе инициализации алгоритма LLR соответствуют априорным вероятностям. SPA является одним из алгоритмов максимальной апостериорной вероятности (MAP — maximum a posteriori probability), а значит он стремится максимизировать апостериорную вероятность, полученную после итеративной пересылки между узлами проверок и переменных.
Предлагаю рассмотреть пошагово.
Предупреждение:
Ниже будет представлено некоторое количество математических формул, и иногда они будут довольно непростыми для визуального восприятия. Поэтому если вы не настроены в данный момент на штудирование, предлагаю перейти сразу к пункту "Пример декодирования через SPA на Python (numpy)". Вернетесь к теории, когда будет время и настроение или когда захочется посмотреть, что лежит в основе скриптов (наверное).
Итак, для начала рассмотрим априорные вероятности.
Начальной точкой для нашего алгоритма является матрица значений LLR, повторяющая структуру матрицы . Подберем аналитическое описание:
где является массивом единиц, а
обозначает произведение Адамара (поэлементное умножение). На практике без единичной матрицы можно будет обойтись: заменим скобку на итерационное умножением Адамара вектора LLR со столбцами матрицы контроля четности (нужен будет дополнительный цикл). Если матрицы будут достаточно большими, такой подход может быть более эффективным с точки зрения памяти.
Затем следует, так называемый, горизонтальный шаг: алгоритм требует обработки сообщения (V2C) в области вероятности. Для перехода от LLR к вероятностям воспользуемся отношением между гиперболическим тангенсом и натуральным логарифмом [4, с.32 ]:
Собственно говоря, процедура передачи V2C сообщения — это перемножение ненулевых вероятностей в каждой строке:
где j — это номер определенной строки, i — это номер определенного столбца, — это множество ненулевых значений в j-ой строке, а выражение
означает, что мы исключаем i-ый узел переменных (variable node) из рассмотрения.
То есть на данном этапе нам нужно:
Пункт с исключением узла из рассмотрения можно провести двумя способами: 1) выяснять нужное подмножество до перемножения вероятностей или удалять значение из результата после подсчетов. Я, простоты ради, буду пользоваться вторым методом.
Я думаю, кто-то из вас, возможно, слышал названия и других алгоритмов декодирования LDPC кодов. Например, Min-sum [5], Log-SPA [6] или еще какие-нибудь [7]. Такие алгоритмы еще иногда называются субоптимальными. В чем их отличие? Собственно, в данном пункте: данные алгоритмы стремятся снизить сложность декодирования и для этого используют иные формулы для вычисления горизонтального шага. Как правило, здесь начинается подсчет рисков: каким количеством помехоустойчивости мы готовы пожертвовать ради повышения скорости декодирования.
Итак, мы подходим к концу первой итерации, а значит пора обновить наши априорные вероятности — сделать их апостериорными:
где — это множество элементов, отвечающих ненулевым элементам матрицы проверки на четность в
-ом столбце.
Наложим их на биты через обратный NRZ:
И вычислим синдром по формуле (4). Если вектор нулевой — останавливаем декодирование. Если нет, то переходим к следующему шагу.
На этом этапе нужно пересчитать матрицу :
И далее перейти к вычислению матрицы . И так до тех пор, пока не выполнится пункт 3 (или не кончится количество доступных итераций).
А теперь давайте перейдем к вещам более интересным — к примерам и скриптам!
Возьмем все тот же пример из [4, с.33 ]:
Начинаем декодировать (должна понадобиться одна итерация).
import numpy as np
class SPA:
def __init__(self, H, Imax=1000, trace_on=True):
self.H = H
self.Imax = Imax
self.trace_on = trace_on
self.H_0 = np.shape(H)[0]
self.H_1 = np.shape(H)[1]
self.H_mirr = (self.H + np.ones(np.shape(self.H))) %2
def __nrz(self, l):
for idx, l_j in enumerate(l):
if l_j >= 0:
l[idx] = 0
else:
l[idx] = 1
return l
def __calc_E(self, E, M):
M = np.tanh(M / 2) + self.H_mirr
for j in range(self.H_0):
for i in range(self.H_1):
if self.H[j,i] != 0:
E[j,i] = np.log(( 1 + np.prod(M[j,:]) \
/ M[j,i]) / ( 1 - np.prod(M[j,:]) / M[j,i]) )
return E
def __calc_M(self, M, E, r):
for j in range(self.H_0):
for i in range(self.H_1):
if self.H[j,i] != 0:
M[j,i] = np.sum(E[:, i]) - E[j,i] + r[i]
M = M*H
return M
def decode(self, r):
stop = False
I = 0
M = np.zeros(np.shape(H))
E = np.zeros(np.shape(H))
l = np.zeros(np.shape(r))
print('H:\n'+str(H))
while stop == False and I != self.Imax:
if I == 0:
for j in range(np.shape(H)[0]):
M[j, :] = r*H[j, :]
if self.trace_on == True:
print('M:\n'+str(M))
E = self.__calc_E(E, M)
if self.trace_on == True:
print('E:\n'+str(E))
l = r + np.sum(E, axis=0)
if self.trace_on == True:
print('l:\n'+str(l))
l = self.__nrz(l)
if self.trace_on == True:
print('decoded:\n'+str(l))
s = np.dot(H, l) %2
if np.prod(s == np.zeros(np.size(s))) == 1:
stop = True
else:
I = I + 1
M = self.__calc_M(M, E, r)
return l
l = SPA(H).decode(r)
print('Decoded message:\n'+str(l))
>>> H:
[[1 1 0 1 0 0]
[0 1 1 0 1 0]
[1 0 0 0 1 1]
[0 0 1 1 0 1]]
M:
[[-1.3863 1.3863 -0. 1.3863 -0. -0. ]
[-0. 1.3863 -1.3863 0. -1.3863 -0. ]
[-1.3863 0. -0. 0. -1.3863 -1.3863]
[-0. 0. -1.3863 1.3863 -0. -1.3863]]
E:
[[ 0.75377678 -0.75377678 0. -0.75377678 0. 0. ]
[ 0. 0.75377678 -0.75377678 0. -0.75377678 0. ]
[ 0.75377678 0. 0. 0. 0.75377678 0.75377678]
[ 0. 0. -0.75377678 0.75377678 0. -0.75377678]]
l:
[ 0.12125356 1.3863 -2.89385356 1.3863 -1.3863 -1.3863 ]
Decoded message:
[0. 0. 1. 0. 1. 1.]
Сошлось.
Попробуем другой пример [4, с.36 ]:
Исправить нужно первый и последний биты.
r = np.array([-.5, 2.5, -4., 5., -3.5, 2.5])
l = SPA(H, trace_on=False).decode(r)
print('Decoded message:\n'+str(l))
>>> H:
[[1 1 0 1 0 0]
[0 1 1 0 1 0]
[1 0 0 0 1 1]
[0 0 1 1 0 1]]
Decoded message:
[0. 0. 1. 0. 1. 1.]
Well done!
Ну, что же, теперь мы знаем азы блочного кодирования, в целом, и LDPC кодов, в частности, и даже попробовали сделать что-то, так сказать, своими руками. Можно считать, что стадия перехода от обезьяны к человеку пройдена — теорию усвоили (или хотя бы запасли на будущее).
Давайте использовать готовые решения.
Наверное, первое, что придет вам на ум, — это Communication Toolbox от MathWorks (MatLab). Решение, пожалуй, действительно хорошее, но мне оно не нравится по нескольким причинам:
Поэтому я отправился в путешествие по проектам на GitHub и нашел весьма интересный инструментарий: проект aff3ct, написанный на C++.
Интересно, почему еще никто не создал проект LDPC++ ?..
Прочтем, как проект позиционируют его разработчики:
Симулятор нацелен на высокоскоростное моделирование и широко использует параллельные методы, такие как SIMD, многопоточные и многоузловые модели программирования. Ниже приведен список функций, которые мотивировали создание симулятора:
- воспроизведение самых современных характеристик декодирования,
- исследование различных конфигураций помехоустойчивых кодов (нахождение новых компромиссов),
- аппаратная реализация прототипа (приемники с фиксированной точкой, аппаратные средства в цикле),
- повторное использование проверенных и протестированных модулей (и возможность добавлять свои),
- альтернатива MATLAB (если вы стремитесь сократить время моделирования).
Работает ПО не только для LDPC кодов, но и для других кодеков (например, можно рассмотреть Turbo-коды и полярные коды).
У проекта есть хорошая документация в формате PDF, в формате WEB-страниц, а также онлайн-версия с визуализацией уже полученных результатов (BER/FER Comparator).
Выберем что-нибудь для примера:
Лучше открыть в новой вкладке для полноты эффекта.
Красота!
Можно запустить любой эксперимент и под свой вкус. Для этого нужно будет по инструкции (см. Instalation) установить ПО и запустить его из командной строки с нужными параметрами.
Устанавливать нужно путем сборки из исходников. Поэтому не забывайте про суперпользователей и места нахождения make-файлов.
Например, я запустил на досуге такую модель к командной строке Ubuntu 18.04:
aff3ct -C "LDPC" -K 32400 -N 64800 --enc-type LDPC_DVBS2 --dec-implem SPA --mdm-type QAM --mdm-bps 8 --chn-type RAYLEIGH -m 0.0 -M 15.0 -s 1.0
То есть:
# ----------------------------------------------------
# ---- A FAST FORWARD ERROR CORRECTION TOOLBOX >> ----
# ----------------------------------------------------
# Parameters:
# * Simulation ------------------------------------
# ** Type = BFER
# ** Type of bits = int32
# ** Type of reals = float32
# ** Date (UTC) = 2020-02-19 18:43:51
# ** Git version = v2.3.5
# ** Code type (C) = LDPC
# ** Noise range = 0 -> 15 dB
# ** Noise type (E) = EBN0
# ** Seed = 0
# ** Statistics = off
# ** Debug mode = off
# ** Multi-threading (t) = 2 thread(s)
# ** Coset approach (c) = no
# ** Coded monitoring = no
# ** Bad frames tracking = off
# ** Bad frames replay = off
# ** Bit rate = 0.5 (1/2)
# ** Inter frame level = 1
# * Source ----------------------------------------
# ** Type = RAND
# ** Implementation = STD
# ** Info. bits (K_info) = 32400
# * Codec -----------------------------------------
# ** Type = LDPC
# ** Info. bits (K) = 32400
# ** Codeword size (N_cw) = 64800
# ** Frame size (N) = 64800
# ** Code rate = 0.5 (1/2)
# * Encoder ---------------------------------------
# ** Type = LDPC_DVBS2
# ** Systematic = yes
# * Decoder ---------------------------------------
# ** Type (D) = BP_FLOODING
# ** Implementation = SPA
# ** Systematic = yes
# ** Num. of iterations (i) = 10
# ** Stop criterion (syndrome) = on
# ** Stop criterion depth = 1
# * Modem -----------------------------------------
# ** Type = QAM
# ** Implementation = STD
# ** Bits per symbol = 8
# ** Sigma square = on
# ** Max type = MAX
# * Channel ---------------------------------------
# ** Type = RAYLEIGH
# ** Implementation = STD
# ** Block fading policy = NO
# ** Complex = on
# ** Add users = off
# * Monitor ---------------------------------------
# ** Lazy reduction = off
# ** Frame error count (e) = 100
# ** Compute mutual info = no
# * Terminal --------------------------------------
# ** Show Sigma = off
# ** Enabled = yes
# ** Frequency (ms) = 500
#
В итоге сформировалась такая таблица:
# The simulation is running...
# ---------------------||------------------------------------------------------||---------------------
# Signal Noise Ratio || Bit Error Rate (BER) and Frame Error Rate (FER) || Global throughput
# (SNR) || || and elapsed time
# ---------------------||------------------------------------------------------||---------------------
# ----------|----------||----------|----------|----------|----------|----------||----------|----------
# Es/N0 | Eb/N0 || FRA | BE | FE | BER | FER || SIM_THR | ET/RT
# (dB) | (dB) || | | | | || (Mb/s) | (hhmmss)
# ----------|----------||----------|----------|----------|----------|----------||----------|----------
6.02 | 0.00 || 101 | 1095697 | 101 | 3.35e-01 | 1.00e+00 || 0.005 | 00h11'43
7.02 | 1.00 || 102 | 1054711 | 102 | 3.19e-01 | 1.00e+00 || 0.006 | 00h09'02
8.02 | 2.00 || 102 | 1003146 | 102 | 3.04e-01 | 1.00e+00 || 0.006 | 00h08'58
9.02 | 3.00 || 101 | 936980 | 101 | 2.86e-01 | 1.00e+00 || 0.006 | 00h09'38
10.02 | 4.00 || 102 | 887637 | 102 | 2.69e-01 | 1.00e+00 || 0.005 | 00h11'19
11.02 | 5.00 || 101 | 817568 | 101 | 2.50e-01 | 1.00e+00 || 0.006 | 00h09'25
12.02 | 6.00 || 101 | 749147 | 101 | 2.29e-01 | 1.00e+00 || 0.006 | 00h09'25
13.02 | 7.00 || 102 | 684123 | 102 | 2.07e-01 | 1.00e+00 || 0.006 | 00h09'19
14.02 | 8.00 || 102 | 601807 | 102 | 1.82e-01 | 1.00e+00 || 0.006 | 00h09'19
15.02 | 9.00 || 102 | 507251 | 102 | 1.53e-01 | 1.00e+00 || 0.006 | 00h09'22
16.02 | 10.00 || 101 | 378508 | 101 | 1.16e-01 | 1.00e+00 || 0.006 | 00h09'16
17.02 | 11.00 || 102 | 170607 | 102 | 5.16e-02 | 1.00e+00 || 0.006 | 00h09'19
18.02 | 12.00 || 101 | 14895 | 101 | 4.55e-03 | 1.00e+00 || 0.006 | 00h09'22
19.02 | 13.00 || 107 | 467 | 102 | 1.35e-04 | 9.53e-01 || 0.006 | 00h09'49
20.02 | 14.00 || 812 | 123 | 101 | 4.68e-06 | 1.24e-01 || 0.006 | 01h14'21
21.02 | 15.00 || 14283 | 110 | 100 | 2.38e-07 | 7.00e-03 || 0.006 | 21h13'14
# End of the simulation.
Как вы могли заметить, на последней строке я потерял неимоверное количество времени. Дабы не гнаться за идеалом декодирования со сверхмалыми значениями ошибок (и не повторять моих промахов), выставите нужный вам таймер с помощью флага --sim-stop-time
(не забудьте --sim-crit-nostop
, чтобы моделирование не останавливалось, а переходило к следующей точке отношения сигнал-шум).
Даже такое аскетичное представление данных — как мне кажется, это уже классная возможность.
Можно, конечно, пойти дальше написать какие-нибудь обертки для отрисовки графиков.
У создателей проекта есть и собственные решения по визуализации. Например, PyBER. Суть "тулзы" заключается в том, что с помощью GUI вы можете выбирать сформированные aff3ct txt-файлы, PyBER вам их распарсит и отрисует (полученное можно даже экспортировать, вроде). Лично мне не понравилось представление: графики представлены через plot, а не через более логичный semilogy. Поэтому оставляю опыт использования PyBER на ваше усмотрение.
Допустим, результаты моделирования можно сохранить в txt-файл, а значения отношений битовых ошибок (BER — bit error ratio) можно вытащить уже из него c помощью простой манипуляции с awk:
cat results.txt | awk '{if ($1 !="#") print $11}'
Все это передать, допустим, в Python и нарисовать графики под свой вкус.
Для кодовых скоростей 1/2 и 3/4 (AWGN канал) у меня получились такая картинка:
Без изысков, но в качестве первого шага неплохо, как мне кажется.
Не спорю, сколько я ни пытался объять необъятное, прошлись мы все же только по вершкам. Однако, я все же надеюсь, что данная статья хоть сколько-то снизит порог вхождения человеку, желающему погрузиться в тему LDPC-кодов (наверное).
Конструктивная критика только приветствуется. И спасибо за ваше внимание!
R.G. Gallager Low-Density Parity-Check Codes, IRE Transactions on Information Theory, 1962
D.J.C. MacKay Good Error-Correcting Codes Based on Very Sparse Matrices, IEEE Transactions on Information Theory, VOL.45, NO 2., March 1999
"3GPP RAN1 meeting #87 final report". 3GPP. Retrieved 31 August 2017.
Johnson, S. J. (2006). Introducing low-density parity-check codes. University of Newcastle, Australia, V1
Declercq D., Fossorier M. Decoding algorithms for nonbinary LDPC codes over GF(q) //IEEE transactions on communications. – 2007. – Т. 55. – №. 4. – С. 633-643.
Wymeersch H., Steendam H., Moeneclaey M. Log-domain decoding of LDPC codes over GF (q) //2004 IEEE International Conference on Communications (IEEE Cat. No. 04CH37577). – IEEE, 2004. – Т. 2. – С. 772-776.
Chen J. et al. Reduced-complexity decoding of LDPC codes //IEEE transactions on communications. – 2005. – Т. 53. – №. 8. – С. 1288-1299.
Хотите немного линейной алгебры?
Давайте порассуждаем о том, как можно найти подмножества ненулевых вероятностей?
Для второго способа нужна будет, правда, заранее подготовленная матрица, которая будет повторять структуру матрицы с точностью до наоборот: вместо единиц в ней будут нули, а вместо нулей единицы. Назовем ее "зеркалом" матрицы
:
где — это сложение по модулю (в нашем случае modulo 2).
Соответственно, процедура замены нулей на единицы в матрице — это сложение двух матриц:
После перемножения нужно будет вернуть структуру к исходному виду:
Выглядит забавно, не правда ли? Такой подход очень удобен при небольших матрицах проверки на четность: можно использовать встроенные функции для перемножения элементов в векторах. Однако, я думаю, для больших матриц такой подход может быть неподходящим из-за дополнительного расхода памяти и побочных вычислений.
Вопрос о сложности и простоте декодирования, может быть, не так хорошо просматривается на двоичных кодах, однако на кодах недвоичных встает, так сказать, во весь рост. Пример для GF(4).
Во-первых, вместо вектора LLR, нам придется работать с матрицей LLR (нужно ведь проверять вероятность уже не только двух событий):
Соответственно, пересылаемые сообщения — это уже тензоры, которые придется разбивать на слои и обрабатывать в цикле:
И уже потом выбирать наиболее вероятное значение:
Сложность будет расти пропорционально увеличению q в выражении GF(q). Волей-неволей задумаешься о более быстрых алгоритмах…