Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDPC soft decoding not working #106

Open
ahmad-sho opened this issue Dec 6, 2021 · 9 comments
Open

LDPC soft decoding not working #106

ahmad-sho opened this issue Dec 6, 2021 · 9 comments
Assignees

Comments

@ahmad-sho
Copy link

I'm using commpy to simulate an OFDM transmission/reception. I wanted to add a channel coding block. I used LDPC with a matrix from "David MacKay's Gallager Code resources", in particular, 408.33.844 with N = 408, K=204 (can be found here: http://www.inference.org.uk/mackay/codes/EN/C/408.33.844).
The message is modulated/demodulated in QAM using the same library. The hard decision decoding is working fine. yet when I switch to the soft decisions, it goes into ruins. A sample code is below. Is there something I'm doing wrong? or is it a code problem/limitation? I can't find anything in the documentation that can help.

import numpy as np
import math
import matplotlib.pyplot as plt
import commpy
from commpy import channelcoding as cd

# Parameters:
nOfdm = 5
mQam = 64
nFft = 1024
nGuardBand = 256
nCP = 72
K = 204
N = 408
nDataBits = 204
snr = 25


# Coding and Modulation objects:
ldpc_code_params = cd.get_ldpc_code_params('408.33.844.txt')
Q = commpy.QAMModem(mQam)

# Tx:
bitStream = np.random.randint(2,size = nDataBits)   # generate a random bit stream
codeWords = cd.triang_ldpc_systematic_encode(bitStream, ldpc_code_params, pad=True)
encodedStream = np.reshape(np.array(codeWords),-1)
txSignal = Q.modulate(encodedStream)

# AWGN channel
powerSignal = np.mean(abs(txSignal)**2)
snrLin = 10**(snr/10)
noisePowerSet = powerSignal/snrLin
noise = math.sqrt(noisePowerSet) * (np.random.randn(txSignal.size) + 1j*np.random.randn(txSignal.size))   /  math.sqrt(2)
      
rxSignal = txSignal + noise
plt.figure(0)
plt.plot(np.real(rxSignal),np.imag(rxSignal),'o')
plt.plot(np.real(txSignal),np.imag(txSignal),'x')

# Rx:
demodSignalHard = Q.demodulate(rxSignal,'hard')
demodSignalSoft = Q.demodulate(rxSignal,'soft',noise_var=noisePowerSet)

recCodeWordsSoft = np.reshape(demodSignalSoft,(N,-1))
recCodeWordsHard = np.reshape(demodSignalHard,(N,-1))

finalSignalSoft = np.zeros(1*K,'int')
finalSignalHard = np.zeros(1*K,'int')
for n in range(1):
    zInitialSoft = np.array(cd.ldpc_bp_decode(recCodeWordsSoft[:,n], ldpc_code_params, 'MSA', 10))
    zInitialHard = np.array(cd.ldpc_bp_decode(recCodeWordsHard[:,n], ldpc_code_params, 'MSA', 10))
    finalSignalSoft[n*K:(n+1)*K] = zInitialSoft[0,0:K]
    finalSignalHard[n*K:(n+1)*K] = zInitialHard[1,0:K]

errSoft = np.mean(abs(finalSignalSoft-bitStream))
errHard = np.mean(abs(finalSignalHard-bitStream))

print(f'error Soft = {errSoft}')
print(f'error Hard = {errHard}')
@BastienTr BastienTr self-assigned this Dec 7, 2021
@BastienTr
Copy link
Collaborator

BastienTr commented Dec 7, 2021

What is your expected result? I have just executed the code snippet provided and I get the following results.

Soft error = 0.8970588235294118
Hard error = 11.470588235294118

There are fewer errors with Soft decoding than with Hard decoding so it doesn't seem to be a ruin.

@ahmad-sho
Copy link
Author

What is your expected result? I have just executed the code snippet provided and I get the following results.

Soft error = 0.8970588235294118
Hard error = 11.470588235294118

There are fewer errors with Soft decoding than with Hard decoding so it doesn't seem to be a ruin.

Hi. Apparently, there is something wrong with your execution. The error should be sub-one as it is the mean of the difference between two bitstreams. I executed the code on two different machines (windows and Linux) with Spyder3 and Python3 and the result is 0 for hard decision and ~0.85 for soft decision.

@BastienTr
Copy link
Collaborator

Are you sure that you run the exact same code? For instance, you use finalSignalHard[n*K:(n+1)*K] = zInitialHard[1,0:K] but zInitialHard[1,0:K] is the LLR rather than the bits that are accessed by zInitialHard[0,0:K].

@ahmad-sho
Copy link
Author

Are you sure that you run the exact same code? For instance, you use finalSignalHard[n*K:(n+1)*K] = zInitialHard[1,0:K] but zInitialHard[1,0:K] is the LLR rather than the bits that are accessed by zInitialHard[0,0:K].

Yes. I just tried a new machine with a fresh installation. As for the indices of the matrices, I noticed that the output is different in the two cases. In the hard case, the first array has only zeros, and the second one has ones and zeros, which happen to be exactly what I sent. in the soft case, the first array has ones and zeros (as expected) and the second array has the output LLRs. Did my example execute on your system?

@yangyuwenyang
Copy link

yangyuwenyang commented Jan 19, 2022

I have try the codes, got the results:
error Soft = 0.8921568627450981 error Hard = 0.0
I agree with ahmad-sho that there must be some bugs in ldpc module.
By the way, another test also demonstrate there may be some bugs in ldpc module.
I will open a new issue to display it. Hope to get help from the authors.
Anyway, the work Commpy is an excellent work! I cann't be too grateful!

@yangyuwenyang
Copy link

Another change:
if we change
zInitialSoft = np.array(cd.ldpc_bp_decode(recCodeWordsSoft[:, n], ldpc_code_params, 'MSA', 10))
into
zInitialSoft = np.array(cd.ldpc_bp_decode(-recCodeWordsSoft[:, n], ldpc_code_params, 'MSA', 10))
then we got
error Soft = 0.11274509803921569 error Hard = 0.0
Seems much better! But still wrong.
the llr deifintion has opposite meaning in softdemodulation p1/p0, however, in ldpc llr=p0/p1

@BastienTr
Copy link
Collaborator

Another change: if we change zInitialSoft = np.array(cd.ldpc_bp_decode(recCodeWordsSoft[:, n], ldpc_code_params, 'MSA', 10)) into zInitialSoft = np.array(cd.ldpc_bp_decode(-recCodeWordsSoft[:, n], ldpc_code_params, 'MSA', 10)) then we got error Soft = 0.11274509803921569 error Hard = 0.0 Seems much better! But still wrong. the llr deifintion has opposite meaning in softdemodulation p1/p0, however, in ldpc llr=p0/p1

You are right! Thanks for pointing this out! This may explain why the LDPC test is passing while the example from this issue shows a bug.

The minus is a quick and efficient workaround but a real med-term solution would be to unify all the LLR definitions across CommPy. I may do it someday if I find some free time and if someone wants to help through a PR, don't hesitate!

I have try the codes, got the results: error Soft = 0.8921568627450981 error Hard = 0.0 I agree with ahmad-sho that there must be some bugs in ldpc module. By the way, another test also demonstrate there may be some bugs in ldpc module. I will open a new issue to display it. Hope to get help from the authors. Anyway, the work Commpy is an excellent work! I cann't be too grateful!

What is this other test?

And you are welcome. It is a real pleasure to see that our work on CommPy is useful to some of you 😄!

@edsonportosilva
Copy link
Contributor

Hi guys,

I also have noticed a few issues with the LDPC encoder/decoder functionalities. However, I suspect the problem is not in the decoder function but in the encoder function instead. The definition of the LLR is indeed swapped, but this is not a critical issue since just a sign change in the input LLRs would fix it. In my case, I did a few tests with DVB.S2 LDPC codes (same ones implemented in MATLAB) + QAM modulation and both the encoder and the soft decoder are working perfectly for the AWGN channel. The problem appears when I try to use other codes with different parity-check matrix structures. The function triang_ldpc_systematic_encode only seems to accept specific kinds of parity-check matrices. It is actually written in the comments:

This function work only for LDPC specified by a triangular parity check matrix.

This requirement is probably due to some optimization to reduce the complexity of the encoder. I have tried to use LDPC codes from the AR4JA family and I also got wrong decoding results with CommPy functions, despite the fact that they worked fine in MATLAB. As far as I could verify, parity-check matrices of AR4JA codes are not triangular, whereas the DVB.S2 are triangular. Thus, I suspect the problem lies in the encoder.

@BastienTr
Copy link
Collaborator

Hi,

I have written the encoder and I can confirm that it only functions for a specific type of parity-check matrices. This is a limitation to simplify the algorithm. I never had the time to implement a more universal version.

The name of the function and the comment clearly mention this limit to avoid any confusion. Could a small test on the input with an error raising in case of invalid matrix be useful?

Furthermore, if any of you wish to write a general encoder, I will be glad to help, review it and accept the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants