ft8_lib/ft8/ldpc.cpp

266 lines
7 KiB
C++
Raw Normal View History

//
// LDPC decoder for FT8.
//
// given a 174-bit codeword as an array of log-likelihood of zero,
// return a 174-bit corrected codeword, or zero-length array.
// last 87 bits are the (systematic) plain-text.
// this is an implementation of the sum-product algorithm
// from Sarah Johnson's Iterative Error Correction book.
// codeword[i] = log ( P(x=0) / P(x=1) )
//
// cc -O3 libldpc.c -shared -fPIC -o libldpc.so
//
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "arrays.h"
int ldpc_check(int codeword[]);
// thank you Douglas Bagnall
// https://math.stackexchange.com/a/446411
float fast_tanh(float x)
{
if (x < -4.97f)
{
return -1.0f;
}
if (x > 4.97f)
{
return 1.0f;
}
float x2 = x * x;
float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2)));
float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f));
return a / b;
}
// codeword is 174 log-likelihoods.
// plain is a return value, 174 ints, to be 0 or 1.
// iters is how hard to try.
// ok == 87 means success.
void ldpc_decode(float codeword[], int iters, int plain[], int *ok)
{
float m[87][174]; // ~60 kB
float e[87][174]; // ~60 kB
int best_score = -1;
int best_cw[174];
for (int i = 0; i < 174; i++)
for (int j = 0; j < 87; j++)
m[j][i] = codeword[i];
for (int i = 0; i < 174; i++)
for (int j = 0; j < 87; j++)
e[j][i] = 0.0f;
for (int iter = 0; iter < iters; iter++)
{
for (int j = 0; j < 87; j++)
{
for (int ii1 = 0; ii1 < 7; ii1++)
{
int i1 = Nm[j][ii1] - 1;
if (i1 < 0)
continue;
float a = 1.0f;
for (int ii2 = 0; ii2 < 7; ii2++)
{
int i2 = Nm[j][ii2] - 1;
if (i2 >= 0 && i2 != i1)
{
a *= fast_tanh(m[j][i2] / 2.0f);
}
}
e[j][i1] = log((1 + a) / (1 - a));
}
}
int cw[174];
for (int i = 0; i < 174; i++)
{
float l = codeword[i];
for (int j = 0; j < 3; j++)
l += e[Mn[i][j] - 1][i];
cw[i] = (l <= 0.0f);
}
int score = ldpc_check(cw);
if (score == 87)
{
// Found a perfect answer
#if 0
int cw1[174];
for(int i = 0; i < 174; i++)
cw1[i] = cw[colorder[i]];
for(int i = 0; i < 87; i++)
plain[i] = cw1[174-87+i];
#else
for (int i = 0; i < 174; i++)
plain[i] = cw[colorder[i]];
#endif
*ok = 87;
return;
}
if (score > best_score)
{
for (int i = 0; i < 174; i++)
best_cw[i] = cw[i];
best_score = score;
}
for (int i = 0; i < 174; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
int j1 = Mn[i][ji1] - 1;
float l = codeword[i];
for (int ji2 = 0; ji2 < 3; ji2++)
{
if (ji1 != ji2)
{
int j2 = Mn[i][ji2] - 1;
l += e[j2][i];
}
}
m[j1][i] = l;
}
}
}
// decode didn't work, return something anyway.
#if 0
int cw1[174];
for(int i = 0; i < 174; i++)
cw1[i] = best_cw[colorder[i]];
for(int i = 0; i < 87; i++)
plain[i] = cw1[174-87+i];
#else
for (int i = 0; i < 174; i++)
plain[i] = best_cw[colorder[i]];
#endif
*ok = best_score;
}
//
// does a 174-bit codeword pass the FT8's LDPC parity checks?
// returns the number of parity checks that passed.
// 87 means total success.
//
int ldpc_check(int codeword[])
{
int score = 0;
// Nm[87][7]
for (int j = 0; j < 87; j++)
{
int x = 0;
for (int ii1 = 0; ii1 < 7; ii1++)
{
int i1 = Nm[j][ii1] - 1;
if (i1 >= 0)
{
x ^= codeword[i1];
}
}
if (x == 0)
score++;
}
return score;
}
/*
def bp_decode(codeword, max_iterations = 10):
## 174 codeword bits
## 87 parity checks
mnx = numpy.array(Mn, dtype=numpy.int32)
nmx = numpy.array(Nm, dtype=numpy.int32)
ncw = 3
tov = numpy.zeros( (3, N) )
toc = numpy.zeros( (7, M) )
tanhtoc = numpy.zeros( (7, M) )
zn = numpy.zeros(N)
nclast = 0
ncnt = 0
# initialize messages to checks
for j in range(M):
for i in range(nrw[j]):
toc[i, j] = codeword[nmx[j, i] - 1]
for iteration in range(max_iterations):
# Update bit log likelihood ratios (tov=0 in iteration 0).
#for i in range(N):
# zn[i] = codeword[i] + numpy.sum(tov[:,i])
zn = codeword + numpy.sum(tov, axis = 0)
#print(numpy.sum(tov, axis=0))
# Check to see if we have a codeword (check before we do any iteration).
cw = numpy.zeros(N, dtype=numpy.int32)
cw[zn > 0] = 1
ncheck = 0
for i in range(M):
synd = numpy.sum(cw[ nmx[i, :nrw[i]]-1 ])
if synd % 2 > 0:
ncheck += 1
if ncheck == 0:
# we have a codeword - reorder the columns and return it
codeword = cw[colorder]
#nerr = 0
#for i in range(N):
# if (2*cw[i]-1)*codeword[i] < 0:
# nerr += 1
#print("DECODED!", nerr)
return codeword[M:N]
if iter > 0:
# this code block implements an early stopping criterion
nd = ncheck - nclast
if nd < 0: # of unsatisfied parity checks decreased
ncnt = 0 # reset counter
else:
ncnt += 1
if ncnt >= 5 and iter >= 10 and ncheck >= 15:
nharderror = -1
#return numpy.array([])
nclast = ncheck
# Send messages from bits to check nodes
for j in range(M):
for i in range(nrw[j]):
ibj = nmx[j, i] - 1
toc[i, j] = zn[ibj]
for kk in range(ncw): # subtract off what the bit had received from the check
if mnx[ibj, kk] - 1 == j:
toc[i, j] -= tov[kk, ibj]
# send messages from check nodes to variable nodes
#for i in range(M):
# tanhtoc[:,i] = numpy.tanh(-toc[:,i] / 2)
tanhtoc = numpy.tanh(-toc / 2)
for j in range(N):
for i in range(ncw):
ichk = mnx[j, i] - 1 # Mn(:,j) are the checks that include bit j
Tmn = 1.0
for k in range(nrw[ichk]):
if nmx[ichk, k] - 1 == j: continue
Tmn *= tanhtoc[k, ichk]
y = numpy.arctanh(-Tmn)
#y = platanh(-Tmn)
tov[i, j] = 2*y
return numpy.array([])
*/