Reorganized 'utility' into own package

This commit is contained in:
Andreas Tsouchlos 2022-11-08 00:52:43 +01:00
parent bbd9d9037b
commit 781ae1442d
9 changed files with 64 additions and 98 deletions

View File

@ -3,6 +3,7 @@ import numpy as np
import itertools import itertools
# TODO: Unify the interface regarding [0, 1]^n and [-1, 1]^n
class SoftDecisionDecoder: class SoftDecisionDecoder:
"""This class naively implements a soft decision decoder. The decoder calculates """This class naively implements a soft decision decoder. The decoder calculates
the correlation between the received signal and each codeword and then chooses the the correlation between the received signal and each codeword and then chooses the

View File

@ -1,6 +1,7 @@
import numpy as np import numpy as np
# TODO: Unify the interface regarding [0, 1]^n and [-1, 1]^n
class ProximalDecoder: class ProximalDecoder:
"""Class implementing the Proximal Decoding algorithm. See "Proximal Decoding for LDPC Codes" """Class implementing the Proximal Decoding algorithm. See "Proximal Decoding for LDPC Codes"
by Tadashi Wadayama, and Satoshi Takabe. by Tadashi Wadayama, and Satoshi Takabe.
@ -25,16 +26,8 @@ class ProximalDecoder:
self._gamma = gamma self._gamma = gamma
self._eta = eta self._eta = eta
self._A = [] self._k, self._n = self._H.shape
self._B = [] self._H_ne_0 = H != 0
for row in self._H:
A_k = np.argwhere(row == 1)
self._A.append(A_k[:, 0])
for column in self._H.T:
B_k = np.argwhere(column == 1)
self._B.append(B_k[:, 0])
@staticmethod @staticmethod
def _L_awgn(s: np.array, y: np.array) -> np.array: def _L_awgn(s: np.array, y: np.array) -> np.array:
@ -47,10 +40,8 @@ class ProximalDecoder:
"""Gradient of the code-constraint polynomial. See 2.3, p. 2.""" """Gradient of the code-constraint polynomial. See 2.3, p. 2."""
# Pre-computations # Pre-computations
k, _ = self._H.shape A_prod_matrix = np.tile(x, (self._k, 1))
A_prods = np.prod(A_prod_matrix, axis=1, where=self._H_ne_0)
A_prod_matrix = np.tile(x, (k, 1))
A_prods = np.prod(A_prod_matrix, axis=1, where=self._H > 0)
# Calculate gradient # Calculate gradient
@ -89,8 +80,8 @@ class ProximalDecoder:
and 'w' is noise) and 'w' is noise)
:return: Most probably sent dataword (element of [0, 1]^k) :return: Most probably sent dataword (element of [0, 1]^k)
""" """
s = np.zeros(y.size) s = np.zeros(self._n)
x_hat = np.zeros(y.size) x_hat = np.zeros(self._n)
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)

View File

@ -4,10 +4,8 @@ import seaborn as sns
import pandas as pd import pandas as pd
from timeit import default_timer as timer from timeit import default_timer as timer
from decoders import proximal from decoders import proximal, naive_soft_decision
from decoders import naive_soft_decision from utility import noise, simulations, encoders
from decoders import channel
from decoders import utility
def main(): def main():
@ -29,7 +27,7 @@ def main():
# Define encoder and decoders # Define encoder and decoders
encoder = channel.Encoder(G) encoder = encoders.Encoder(G)
decoders = {"naive_soft_decision": naive_soft_decision.SoftDecisionDecoder(G, H, R), decoders = {"naive_soft_decision": naive_soft_decision.SoftDecisionDecoder(G, H, R),
"proximal_0_01": proximal.ProximalDecoder(H, R, K=100, gamma=0.01), "proximal_0_01": proximal.ProximalDecoder(H, R, K=100, gamma=0.01),
@ -49,11 +47,11 @@ def main():
for decoder_name in decoders: for decoder_name in decoders:
decoder = decoders[decoder_name] decoder = decoders[decoder_name]
_, BERs_sd = utility.test_decoder(encoder=encoder, _, BERs_sd = simulations.test_decoder(encoder=encoder,
decoder=decoder, decoder=decoder,
d=d, d=d,
SNRs=SNRs, SNRs=SNRs,
N_max=2000) N_max=2000)
data[f"BER_{decoder_name}"] = BERs_sd data[f"BER_{decoder_name}"] = BERs_sd

View File

@ -63,40 +63,6 @@ class GradientTestCase(unittest.TestCase):
self.assertEqual(np.array_equal(grad_h, expected_grad_h), True) self.assertEqual(np.array_equal(grad_h, expected_grad_h), True)
def test_gen_A_B(self):
"""Test the generation of the A and B sets used for the gradient calculation."""
# Hamming(7,4) code
G = np.array([[1, 1, 1, 0, 0, 0, 0],
[1, 0, 0, 1, 1, 0, 0],
[0, 1, 0, 1, 0, 1, 0],
[1, 1, 0, 1, 0, 0, 1]])
H = np.array([[1, 0, 1, 0, 1, 0, 1],
[0, 1, 1, 0, 0, 1, 1],
[0, 0, 0, 1, 1, 1, 1]])
R = np.array([[0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1]])
decoder = proximal.ProximalDecoder(H, R)
expected_A = [np.array([0, 2, 4, 6]),
np.array([1, 2, 5, 6]),
np.array([3, 4, 5, 6])]
expected_B = [np.array([0]),
np.array([1]),
np.array([0, 1]),
np.array([2]),
np.array([0, 2]),
np.array([1, 2]),
np.array([0, 1, 2])]
for A_i, expected_A_i in zip(decoder._A, expected_A):
self.assertEqual(np.array_equal(A_i, expected_A_i), True)
for B_k, expected_B_k in zip(decoder._B, expected_B):
self.assertEqual(np.array_equal(B_k, expected_B_k), True)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -1,6 +1,8 @@
import unittest import unittest
import numpy as np import numpy as np
from decoders import utility
from utility import simulations
from utility import noise
class CountBitErrorsTestCase(unittest.TestCase): class CountBitErrorsTestCase(unittest.TestCase):
@ -15,9 +17,9 @@ class CountBitErrorsTestCase(unittest.TestCase):
d3 = np.array([0, 0, 0, 0]) d3 = np.array([0, 0, 0, 0])
y_hat3 = np.array([1, 1, 1, 1]) y_hat3 = np.array([1, 1, 1, 1])
self.assertEqual(utility.count_bit_errors(d1, y_hat1), 2) self.assertEqual(simulations.count_bit_errors(d1, y_hat1), 2)
self.assertEqual(utility.count_bit_errors(d2, y_hat2), 0) self.assertEqual(simulations.count_bit_errors(d2, y_hat2), 0)
self.assertEqual(utility.count_bit_errors(d3, y_hat3), 4) self.assertEqual(simulations.count_bit_errors(d3, y_hat3), 4)
# TODO: Is this correct? # TODO: Is this correct?
@ -30,11 +32,11 @@ class NoiseAmpFromSNRTestCase(unittest.TestCase):
SNR4 = -20 SNR4 = -20
SNR5 = 60 SNR5 = 60
self.assertEqual(utility._get_noise_amp_from_SNR(SNR1, signal_amp=1), 1) self.assertEqual(noise.get_noise_amp_from_SNR(SNR1, signal_amp=1), 1)
self.assertAlmostEqual(utility._get_noise_amp_from_SNR(SNR2, signal_amp=1), 0.5, places=2) self.assertAlmostEqual(noise.get_noise_amp_from_SNR(SNR2, signal_amp=1), 0.5, places=2)
self.assertEqual(utility._get_noise_amp_from_SNR(SNR3, signal_amp=1), 0.1) self.assertEqual(noise.get_noise_amp_from_SNR(SNR3, signal_amp=1), 0.1)
self.assertEqual(utility._get_noise_amp_from_SNR(SNR4, signal_amp=1), 10) self.assertEqual(noise.get_noise_amp_from_SNR(SNR4, signal_amp=1), 10)
self.assertEqual(utility._get_noise_amp_from_SNR(SNR5, signal_amp=2), 0.002) self.assertEqual(noise.get_noise_amp_from_SNR(SNR5, signal_amp=2), 0.002)
if __name__ == '__main__': if __name__ == '__main__':

1
sw/utility/__init__.py Normal file
View File

@ -0,0 +1 @@
"""This package contains various utilities that can be used in combination with the decoders."""

View File

@ -1,6 +1,7 @@
import numpy as np import numpy as np
# TODO: Unify the interface regarding [0, 1]^n and [-1, 1]^n
# TODO: Should the encoder be responsible for mapping the message from [0, 1]^n to [-1, 1]^n? # TODO: Should the encoder be responsible for mapping the message from [0, 1]^n to [-1, 1]^n?
# (ie. should the encoder perform modulation?) # (ie. should the encoder perform modulation?)
class Encoder: class Encoder:

31
sw/utility/noise.py Normal file
View File

@ -0,0 +1,31 @@
"""Utility functions relating to noise and SNR calculations."""
import numpy as np
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.
:param SNR: Signal-to-Noise-Ratio in dB
:param signal_amp: Signal Amplitude (linear)
:return: Noise Amplitude (linear)
"""
SNR_linear = 10 ** (SNR / 10)
noise_amp = (1 / np.sqrt(SNR_linear)) * signal_amp
return noise_amp
def add_awgn(c: np.array, SNR: float, signal_amp: float = 1) -> np.array:
"""Add Additive White Gaussian Noise to a data vector. As this function adds random noise to
the input, the output changes, even if it is called multiple times with the same input.
:param c: Binary vector representing the data to be transmitted
:param SNR: Signal-to-Noise-Ratio in dB
:param signal_amp: Amplitude of the signal. Used for the noise amplitude calculation
:return: Data vector with added noise
"""
noise_amp = get_noise_amp_from_SNR(SNR, signal_amp=signal_amp)
y = c + np.random.normal(scale=noise_amp, size=c.size)
return y

View File

@ -1,36 +1,11 @@
"""This file contains various utility functions that can be used in combination with the decoders. """This file contains utility functions relating to tests and simulations of the decoders."""
"""
import numpy as np import numpy as np
import typing import typing
from tqdm import tqdm from tqdm import tqdm
from utility import noise
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.
:param SNR: Signal-to-Noise-Ratio in dB
:param signal_amp: Signal Amplitude (linear)
:return: Noise Amplitude (linear)
"""
SNR_linear = 10 ** (SNR / 10)
noise_amp = (1 / np.sqrt(SNR_linear)) * signal_amp
return noise_amp
def add_awgn(c: np.array, SNR: float, signal_amp: float = 1) -> np.array:
"""Add Additive White Gaussian Noise to a data vector. As this function adds random noise to
the input, the output changes, even if it is called multiple times with the same input.
:param c: Binary vector representing the data to be transmitted
:param SNR: Signal-to-Noise-Ratio in dB
:param signal_amp: Amplitude of the signal. Used for the noise amplitude calculation
:return: Data vector with added noise
"""
noise_amp = _get_noise_amp_from_SNR(SNR, signal_amp=signal_amp)
y = c + np.random.normal(scale=noise_amp, size=c.size)
return y
def count_bit_errors(d: np.array, d_hat: np.array) -> int: def count_bit_errors(d: np.array, d_hat: np.array) -> int:
@ -81,7 +56,7 @@ def test_decoder(encoder: typing.Any,
# TODO: Is this a valid simulation? Can we just add AWGN to the codeword, # TODO: Is this a valid simulation? Can we just add AWGN to the codeword,
# ignoring and modulation and (e.g. matched) filtering? # ignoring and modulation and (e.g. matched) filtering?
y = add_awgn(x, SNR, signal_amp=np.sqrt(2)) y = noise.add_awgn(x, SNR, signal_amp=np.sqrt(2))
y_hat = decoder.decode(y) y_hat = decoder.decode(y)