// qracodes.c // Q-ary RA codes encoding/decoding functions // // (c) 2016 - Nico Palermo, IV3NWV - Microtelecom Srl, Italy // ------------------------------------------------------------------------------ // This file is part of the qracodes project, a Forward Error Control // encoding/decoding package based on Q-ary RA (Repeat and Accumulate) LDPC codes. // // qracodes is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // qracodes is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with qracodes source distribution. // If not, see <http://www.gnu.org/licenses/>. #include <stdio.h> #include <math.h> #include "npfwht.h" #include "pdmath.h" #include "qracodes.h" int qra_encode(const qracode *pcode, int *y, const int *x) { int k,j,kk,jj; int t, chk = 0; const int K = pcode->K; const int M = pcode->M; const int NC= pcode->NC; const int a = pcode->a; const int *acc_input_idx = pcode->acc_input_idx; const int *acc_input_wlog = pcode->acc_input_wlog; const int *gflog = pcode->gflog; const int *gfexp = pcode->gfexp; // copy the systematic symbols to destination memcpy(y,x,K*sizeof(int)); y = y+K; // point to check symbols // compute the code check symbols as a weighted accumulation of a permutated // sequence of the (repeated) systematic input symbols: // chk(k+1) = x(idx(k))*alfa^(logw(k)) + chk(k) // (all operations performed over GF(M)) if (a==1) { // grouping factor = 1 for (k=0;k<NC;k++) { t = x[acc_input_idx[k]]; if (t) { // multiply input by weight[k] and xor it with previous check t = (gflog[t] + acc_input_wlog[k])%(M-1); t = gfexp[t]; chk ^=t; } y[k] = chk; } #ifdef QRA_DEBUG // verify that the encoder accumulator is terminated to 0 // (we designed the code this way so that Iex = 1 when Ia = 1) t = x[acc_input_idx[k]]; if (t) { t = (gflog[t] + acc_input_wlog[k])%(M-1); t = gfexp[t]; // accumulation chk ^=t; } return (chk==0); #else return 1; #endif // QRA_DEBUG } else { // grouping factor > 1 for (k=0;k<NC;k++) { kk = a*k; for (j=0;j<a;j++) { jj = kk+j; // irregular grouping support if (acc_input_idx[jj]<0) continue; t = x[acc_input_idx[jj]]; if (t) { // multiply input by weight[k] and xor it with previous check t = (gflog[t] + acc_input_wlog[jj])%(M-1); t = gfexp[t]; chk ^=t; } } y[k] = chk; } #ifdef QRA_DEBUG // verify that the encoder accumulator is terminated to 0 // (we designed the code this way so that Iex = 1 when Ia = 1) kk = a*k; for (j=0;j<a;j++) { jj = kk+j; if (acc_input_idx[jj]<0) continue; t = x[acc_input_idx[jj]]; if (t) { // multiply input by weight[k] and xor it with previous check t = (gflog[t] + acc_input_wlog[jj])%(M-1); t = gfexp[t]; chk ^=t; } } return (chk==0); #else return 1; #endif // QRA_DEBUG } } static void qra_ioapprox(float *src, float C, int nitems) { // In place approximation of the modified bessel function I0(x*C) // Computes src[k] = Io(src[k]*C) where Io() is the modified Bessel function of first kind and order 0 float v; float vsq; while (nitems--) { v = src[nitems]*C; // rational approximation of log(Io(v)) vsq = v*v; v = vsq*(v+0.039f)/(vsq*.9931f+v*2.6936f+0.5185f); if (v>80.f) // avoid floating point exp() overflows v=80.f; src[nitems] = (float)exp(v); } } float qra_mfskbesselmetric(float *pix, const float *rsq, const int m, const int N, float EsNoMetric) { // Computes the codeword symbols intrinsic probabilities // given the square of the received input amplitudes. // The input vector rqs must be a linear array of size M*N, where M=2^m, // containing the squared amplitudes (rp*rp+rq*rq) of the input samples // First symbol amplitudes should be stored in the first M positions, // second symbol amplitudes stored at positions [M ... 2*M-1], and so on. // Output vector is the intrinsic symbol metric (the probability distribution) // assuming that symbols are transmitted using a M-FSK modulation // and incoherent demodulation. // As the input Es/No is generally unknown (as it cannot be exstimated accurately // when the codeword length is few tens symbols) but an exact metric requires it // we simply fix it to a predefined EsNoMetric value so that the metric is what // expected at that specific value. // The metric computed in this way is optimal only at this predefined Es/No value, // nevertheless it is usually better than a generic parameter-free metric which // makes no assumptions on the input Es/No. // returns the estimated noise standard deviation int k; float rsum = 0.f; float sigmaest, cmetric; const int M = 1<<m; const int nsamples = M*N; // compute total power and modulus of input signal for (k=0;k<nsamples;k++) { rsum = rsum+rsq[k]; pix[k] = (float)sqrt(rsq[k]); } rsum = rsum/nsamples; // average S+N // IMPORTANT NOTE: in computing the noise stdev it is assumed that // in the input amplitudes there's no strong interference! // A more robust estimation can be done evaluating the histogram of the input amplitudes sigmaest = (float)sqrt(rsum/(1.0f+EsNoMetric/M)/2); // estimated noise stdev cmetric = (float)sqrt(2*EsNoMetric)/sigmaest; for (k=0;k<N;k++) { // compute bessel metric for each symbol in the codeword qra_ioapprox(PD_ROWADDR(pix,M,k),cmetric,M); // normalize to a probability distribution pd_norm(PD_ROWADDR(pix,M,k),m); } return sigmaest; } #ifdef QRA_DEBUG void pd_print(int imsg,float *ppd,int size) { int k; printf("imsg=%d\n",imsg); for (k=0;k<size;k++) printf("%7.1e ",ppd[k]); printf("\n"); } #endif #define ADDRMSG(fp, msgidx) PD_ROWADDR(fp,qra_M,msgidx) #define C2VMSG(msgidx) PD_ROWADDR(qra_c2vmsg,qra_M,msgidx) #define V2CMSG(msgidx) PD_ROWADDR(qra_v2cmsg,qra_M,msgidx) #define MSGPERM(logw) PD_ROWADDR(qra_pmat,qra_M,logw) #define QRACODE_MAX_M 256 // Maximum alphabet size handled by qra_extrinsic int qra_extrinsic(const qracode *pcode, float *pex, const float *pix, int maxiter, float *qra_v2cmsg, float *qra_c2vmsg) { const int qra_M = pcode->M; const int qra_m = pcode->m; const int qra_V = pcode->V; const int qra_MAXVDEG = pcode->MAXVDEG; const int *qra_vdeg = pcode->vdeg; const int qra_C = pcode->C; const int qra_MAXCDEG = pcode->MAXCDEG; const int *qra_cdeg = pcode->cdeg; const int *qra_v2cmidx = pcode->v2cmidx; const int *qra_c2vmidx = pcode->c2vmidx; const int *qra_pmat = pcode->gfpmat; const int *qra_msgw = pcode->msgw; // float msgout[qra_M]; // buffer to store temporary results float msgout[QRACODE_MAX_M]; // we use a fixed size in order to avoid mallocs float totex; // total extrinsic information int nit; // current iteration int nv; // current variable int nc; // current check int k,kk; // loop indexes int ndeg; // current node degree int msgbase; // current offset in the table of msg indexes int imsg; // current message index int wmsg; // current message weight int rc = -1; // rc>=0 extrinsic converged to 1 at iteration rc (rc=0..maxiter-1) // rc=-1 no convergence in the given number of iterations // rc=-2 error in the code tables (code checks degrees must be >1) // rc=-3 M is larger than QRACODE_MAX_M if (qra_M>QRACODE_MAX_M) return -3; // message initialization ------------------------------------------------------- // init c->v variable intrinsic msgs pd_init(C2VMSG(0),pix,qra_M*qra_V); // init the v->c messages directed to code factors (k=1..ndeg) with the intrinsic info for (nv=0;nv<qra_V;nv++) { ndeg = qra_vdeg[nv]; // degree of current node msgbase = nv*qra_MAXVDEG; // base to msg index row for the current node // copy intrinsics on v->c for (k=1;k<ndeg;k++) { imsg = qra_v2cmidx[msgbase+k]; pd_init(V2CMSG(imsg),ADDRMSG(pix,nv),qra_M); } } // message passing algorithm iterations ------------------------------ for (nit=0;nit<maxiter;nit++) { // c->v step ----------------------------------------------------- // Computes messages from code checks to code variables. // As the first qra_V checks are associated with intrinsic information // (the code tables have been constructed in this way) // we need to do this step only for code checks in the range [qra_V..qra_C) // The convolutions of probability distributions over the alphabet of a finite field GF(qra_M) // are performed with a fast convolution algorithm over the given field. // // I.e. given the code check x1+x2+x3 = 0 (with x1,x2,x3 in GF(2^m)) // and given Prob(x2) and Prob(x3), we have that: // Prob(x1=X1) = Prob((x2+x3)=X1) = sum((Prob(x2=X2)*Prob(x3=(X1+X2))) for all the X2s in the field // This translates to Prob(x1) = IWHT(WHT(Prob(x2))*WHT(Prob(x3))) // where WHT and IWHT are the direct and inverse Walsh-Hadamard transforms of the argument. // Note that the WHT and the IWHF differs only by a multiplicative coefficent and since in this step // we don't need that the output distribution is normalized we use the relationship // Prob(x1) =(proportional to) WH(WH(Prob(x2))*WH(Prob(x3))) // In general given the check code x1+x2+x3+..+xm = 0 // the output distribution of a variable given the distributions of the other m-1 variables // is the inverse WHT of the product of the WHTs of the distribution of the other m-1 variables // The complexity of this algorithm scales with M*log2(M) instead of the M^2 complexity of // the brute force approach (M=size of the alphabet) for (nc=qra_V;nc<qra_C;nc++) { ndeg = qra_cdeg[nc]; // degree of current node if (ndeg==1) // this should never happen (code factors must have deg>1) return -2; // bad code tables msgbase = nc*qra_MAXCDEG; // base to msg index row for the current node // transforms inputs in the Walsh-Hadamard "frequency" domain // v->c -> fwht(v->c) for (k=0;k<ndeg;k++) { imsg = qra_c2vmidx[msgbase+k]; // msg index np_fwht(qra_m,V2CMSG(imsg),V2CMSG(imsg)); // compute fwht } // compute products and transform them back in the WH "time" domain for (k=0;k<ndeg;k++) { // init output message to uniform distribution pd_init(msgout,pd_uniform(qra_m),qra_M); // c->v = prod(fwht(v->c)) // TODO: we assume that checks degrees are not larger than three but // if they are larger the products can be computed more efficiently for (kk=0;kk<ndeg;kk++) if (kk!=k) { imsg = qra_c2vmidx[msgbase+kk]; pd_imul(msgout,V2CMSG(imsg),qra_m); } // transform product back in the WH "time" domain // Very important trick: // we bias WHT[0] so that the sum of output pd components is always strictly positive // this helps avoiding the effects of underflows in the v->c steps when multipling // small fp numbers msgout[0]+=1E-7f; // TODO: define the bias accordingly to the field size np_fwht(qra_m,msgout,msgout); // inverse weight and output imsg = qra_c2vmidx[msgbase+k]; // current output msg index wmsg = qra_msgw[imsg]; // current msg weight if (wmsg==0) pd_init(C2VMSG(imsg),msgout,qra_M); else // output p(alfa^(-w)*x) pd_bwdperm(C2VMSG(imsg),msgout, MSGPERM(wmsg), qra_M); } // for (k=0;k<ndeg;k++) } // for (nc=qra_V;nc<qra_C;nc++) // v->c step ----------------------------------------------------- for (nv=0;nv<qra_V;nv++) { ndeg = qra_vdeg[nv]; // degree of current node msgbase = nv*qra_MAXVDEG; // base to msg index row for the current node for (k=0;k<ndeg;k++) { // init output message to uniform distribution pd_init(msgout,pd_uniform(qra_m),qra_M); // v->c msg = prod(c->v) // TODO: factor factors to reduce the number of computations for high degree nodes for (kk=0;kk<ndeg;kk++) if (kk!=k) { imsg = qra_v2cmidx[msgbase+kk]; pd_imul(msgout,C2VMSG(imsg),qra_m); } #ifdef QRA_DEBUG // normalize and check if product of messages v->c are null // normalize output to a probability distribution if (pd_norm(msgout,qra_m)<=0) { // dump msgin; printf("warning: v->c pd with invalid norm. nit=%d nv=%d k=%d\n",nit,nv,k); for (kk=0;kk<ndeg;kk++) { imsg = qra_v2cmidx[msgbase+kk]; pd_print(imsg,C2VMSG(imsg),qra_M); } printf("-----------------\n"); } #else // normalize the result to a probability distribution pd_norm(msgout,qra_m); #endif // weight and output imsg = qra_v2cmidx[msgbase+k]; // current output msg index wmsg = qra_msgw[imsg]; // current msg weight if (wmsg==0) { pd_init(V2CMSG(imsg),msgout,qra_M); } else { // output p(alfa^w*x) pd_fwdperm(V2CMSG(imsg),msgout, MSGPERM(wmsg), qra_M); } } // for (k=0;k<ndeg;k++) } // for (nv=0;nv<qra_V;nv++) // check extrinsic information ------------------------------ // We assume that decoding is successful if each of the extrinsic // symbol probability is close to ej, where ej = [0 0 0 1(j-th position) 0 0 0 ] // Therefore, for each symbol k in the codeword we compute max(prob(Xk)) // and we stop the iterations if sum(max(prob(xk)) is close to the codeword length // Note: this is a more restrictive criterium than that of computing the a // posteriori probability of each symbol, making a hard decision and then check // if the codeword syndrome is null. // WARNING: this is tricky and probably works only for the particular class of RA codes // we are coping with (we designed the code weights so that for any input symbol the // sum of its weigths is always 0, thus terminating the accumulator trellis to zero // for every combination of the systematic symbols). // More generally we should instead compute the max a posteriori probabilities // (as a product of the intrinsic and extrinsic information), make a symbol by symbol hard // decision and then check that the syndrome of the result is indeed null. totex = 0; for (nv=0;nv<qra_V;nv++) totex += pd_max(V2CMSG(nv),qra_M); if (totex>(1.*(qra_V)-0.01)) { // the total maximum extrinsic information of each symbol in the codeword // is very close to one. This means that we have reached the (1,1) point in the // code EXIT chart(s) and we have successfully decoded the input. rc = nit; break; // remove the break to evaluate the decoder speed performance as a function of the max iterations number) } } // for (nit=0;nit<maxiter;nit++) // copy extrinsic information to output to do the actual max a posteriori prob decoding pd_init(pex,V2CMSG(0),(qra_M*qra_V)); return rc; } void qra_mapdecode(const qracode *pcode, int *xdec, float *pex, const float *pix) { // Maximum a posteriori probability decoding. // Given the intrinsic information (pix) and extrinsic information (pex) (computed with qra_extrinsic(...)) // compute pmap = pex*pix and decode each (information) symbol of the received codeword // as the symbol which maximizes pmap // Returns: // xdec[k] = decoded (information) symbols k=[0..qra_K-1] // Note: pex is destroyed and overwritten with mapp const int qra_M = pcode->M; const int qra_m = pcode->m; const int qra_K = pcode->K; int k; for (k=0;k<qra_K;k++) { // compute a posteriori prob pd_imul(PD_ROWADDR(pex,qra_M,k),PD_ROWADDR(pix,qra_M,k),qra_m); xdec[k]=pd_argmax(NULL, PD_ROWADDR(pex,qra_M,k), qra_M); } }