Implemented projection; Added TODOs and fixed docstrings

This commit is contained in:
Andreas Tsouchlos 2022-11-04 22:05:16 +01:00
parent 01bc41b8c5
commit 2c16e2c2a3
5 changed files with 28 additions and 7 deletions

View File

@ -6,24 +6,30 @@ class ProximalDecoder:
"""Class implementing the Proximal Decoding algorithm. See "Proximal Decoding for LDPC Codes" by Tadashi """Class implementing the Proximal Decoding algorithm. See "Proximal Decoding for LDPC Codes" by Tadashi
Wadayama, and Satoshi Takabe. Wadayama, and Satoshi Takabe.
""" """
def __init__(self, H: np.array, K: int = 10, step_size: float = 0.01, gamma: float = 0.05): # TODO: How large should K be?
# TODO: How large should eta be?
# TODO: How large should step_size be?
def __init__(self, H: np.array, K: int = 100, step_size: float = 0.5, gamma: float = 0.05, eta: float = 1.1):
"""Construct a new ProximalDecoder Object. """Construct a new ProximalDecoder Object.
:param H: Parity Check Matrix :param H: Parity Check Matrix
:param K: Max number of iterations to perform when decoding :param K: Max number of iterations to perform when decoding
:param step_size: Step size for the gradient descent process :param step_size: Step size for the gradient descent process
:param gamma: Positive constant. Arises in the approximation of the prior PDF :param gamma: Positive constant. Arises in the approximation of the prior PDF
:param eta: Positive constant slightly larger than one. See 3.2, p. 3
""" """
self._H = H self._H = H
self._K = K self._K = K
self._step_size = step_size self._step_size = step_size
self._gamma = gamma self._gamma = gamma
self._eta = eta
@staticmethod @staticmethod
def _L_awgn(s: np.array, y: np.array) -> np.array: def _L_awgn(s: np.array, y: np.array) -> np.array:
"""Variation of the negative log-likelihood for the special case of AWGN noise. See 4.1, p. 4.""" """Variation of the negative log-likelihood for the special case of AWGN noise. See 4.1, p. 4."""
return s - y return s - y
# TODO: Is this correct?
def _grad_h(self, x: np.array) -> np.array: def _grad_h(self, x: np.array) -> np.array:
"""Gradient of the code-constraint polynomial. See 2.3, p. 2.""" """Gradient of the code-constraint polynomial. See 2.3, p. 2."""
# Calculate first term # Calculate first term
@ -56,6 +62,16 @@ class ProximalDecoder:
return np.array(result) return np.array(result)
# TODO: Is this correct?
def _projection(self, x):
"""Project a vector onto [-eta, eta]^n in order to avoid numerical instability.
Detailed in 3.2, p. 3 (Equation (15)).
:param x:
:return:
"""
return np.clip(x, -self._eta, self._eta)
def _check_parity(self, y_hat: np.array) -> bool: def _check_parity(self, y_hat: np.array) -> bool:
"""Perform a parity check for a given codeword. """Perform a parity check for a given codeword.
@ -77,7 +93,10 @@ class ProximalDecoder:
x_hat = 0 x_hat = 0
for k in range(self._K): for k in range(self._K):
r = s - self._step_size * self._L_awgn(s, y) r = s - self._step_size * self._L_awgn(s, y)
s = r - self._gamma * self._grad_h(r) s = r - self._gamma * self._grad_h(r)
s = self._projection(s) # Equation (15)
x_hat = (np.sign(s) == 1) * 1 x_hat = (np.sign(s) == 1) * 1
if self._check_parity(x_hat): if self._check_parity(x_hat):

View File

@ -7,7 +7,7 @@ from tqdm import tqdm
def _get_noise_amp_from_SNR(SNR: float, signal_amp: float = 1) -> float: def _get_noise_amp_from_SNR(SNR: float, signal_amp: float = 1) -> float:
"""Calculate the amplitude of the noise from an SNR and the signal amplitude """Calculate the amplitude of the noise from an SNR and the signal amplitude.
:param SNR: Signal-to-Noise-Ratio in dB :param SNR: Signal-to-Noise-Ratio in dB
:param signal_amp: Signal Amplitude (linear) :param signal_amp: Signal Amplitude (linear)
@ -49,7 +49,7 @@ def test_decoder(decoder: typing.Any,
-> typing.Tuple[np.array, np.array]: -> typing.Tuple[np.array, np.array]:
"""Calculate the Bit Error Rate (BER) for a given decoder for a number of SNRs. """Calculate the Bit Error Rate (BER) for a given decoder for a number of SNRs.
This function prints it's progress to stdout This function prints its progress to stdout.
:param decoder: Instance of the decoder to be tested :param decoder: Instance of the decoder to be tested
:param c: Codeword whose transmission is to be simulated :param c: Codeword whose transmission is to be simulated
@ -69,6 +69,8 @@ def test_decoder(decoder: typing.Any,
leave=False, leave=False,
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}"): bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}"):
# TODO: Is this a valid simulation? Can we just add AWGN to the codeword, ignoring and modulation and (
# e.g. matched) filtering?
y = add_awgn(c, SNR) y = add_awgn(c, SNR)
y_hat = decoder.decode(y) y_hat = decoder.decode(y)

View File

@ -26,8 +26,8 @@ def main():
print(f"Simulating with c = {c}") print(f"Simulating with c = {c}")
decoder = proximal.ProximalDecoder(H, K=100) decoder = proximal.ProximalDecoder(H, K=100, gamma=0.01)
SNRs, BERs = utility.test_decoder(decoder, c, N=100) SNRs, BERs = utility.test_decoder(decoder, c, SNRs=[1, 3, 20], N=1000)
data = pd.DataFrame({"SNR": SNRs, "BER": BERs}) data = pd.DataFrame({"SNR": SNRs, "BER": BERs})

View File

@ -35,7 +35,7 @@ class CheckParityTestCase(unittest.TestCase):
class GradientTestCase(unittest.TestCase): class GradientTestCase(unittest.TestCase):
"""Test case for the calculation of the gradient of the code-constraint-polynomial""" """Test case for the calculation of the gradient of the code-constraint-polynomial."""
def test_grad_h(self): def test_grad_h(self):
H = np.array([[1, 0, 0], H = np.array([[1, 0, 0],
[0, 1, 0]]) [0, 1, 0]])

View File

@ -21,7 +21,7 @@ class CountBitErrorsTestCase(unittest.TestCase):
class NoiseAmpFromSNRTestCase(unittest.TestCase): class NoiseAmpFromSNRTestCase(unittest.TestCase):
"""Test case for noise amplitude calculation""" """Test case for noise amplitude calculation."""
def test_get_noise_amp_from_SNR(self): def test_get_noise_amp_from_SNR(self):
SNR1 = 0 SNR1 = 0
SNR2 = 6 SNR2 = 6