From 8687174a85275e677fd0703da24e639a8b11cea2 Mon Sep 17 00:00:00 2001
From: Veeresh Taranalli <veeresht@gmail.com>
Date: Wed, 19 Aug 2020 10:03:22 -0700
Subject: [PATCH 01/54] Updated Coveralls badge link to use master branch.

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 1c34b76..f8e21d6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 
 
 [![Build Status](https://secure.travis-ci.org/veeresht/CommPy.svg?branch=master)](https://secure.travis-ci.org/veeresht/CommPy)
-[![Coverage](https://coveralls.io/repos/veeresht/CommPy/badge.svg)](https://coveralls.io/r/veeresht/CommPy)
+[![Coverage](https://coveralls.io/repos/veeresht/CommPy/badge.svg?branch=master)](https://coveralls.io/r/veeresht/CommPy)
 [![PyPi](https://badge.fury.io/py/scikit-commpy.svg)](https://badge.fury.io/py/scikit-commpy)
 [![Docs](https://readthedocs.org/projects/commpy/badge/?version=latest)](http://commpy.readthedocs.io/en/latest/?badge=latest)
 

From a6ce54b724eedc026c4799710039a4a52afe01d4 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt+gitkraken@gmail.com>
Date: Thu, 6 Aug 2020 19:13:04 +0100
Subject: [PATCH 02/54] Collect more metrics in the simulation

- Chunk Errors (CEs): number of chunks that here  received with errors
- Number Chunks (NCs): Number of chunks Tx
---
 commpy/links.py | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/commpy/links.py b/commpy/links.py
index a124956..5e973b5 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -146,6 +146,7 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
             self.decoder = lambda msg: msg
         else:
             self.decoder = decoder
+        self.full_simulation_results = None
 
     def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
         """
@@ -180,6 +181,8 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
 
         # Initialization
         BERs = np.zeros_like(SNRs, dtype=float)
+        CEs = np.zeros_like(SNRs, dtype=float)  # Chunk Errors
+        NCs = np.zeros_like(SNRs, dtype=float)  # Number of Chunks
         # Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
         if send_chunk is None:
             send_chunk = err_min
@@ -192,9 +195,11 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
         # Computations
         for id_SNR in range(len(SNRs)):
             self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
-            bit_send = 0
-            bit_err = 0
-            while bit_send < send_max and bit_err < err_min:
+            total_bit_send = 0
+            total_bit_err = 0
+            total_chunk_loss = 0
+            total_chunk_count = 0
+            while total_bit_send < send_max and total_bit_err < err_min:
                 # Propagate some bits
                 msg = np.random.choice((0, 1), send_chunk)
                 symbs = self.modulate(msg)
@@ -216,13 +221,19 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                     decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
                                                 self.constellation, self.channel.noise_std ** 2,
                                                 received_msg, self.channel.nb_tx * self.num_bits_symbol)
-                    bit_err += np.bitwise_xor(msg, decoded_bits[:len(msg)]).sum()
+                    bit_err = (msg != decoded_bits[:len(msg)]).sum()
                 else:
-                    bit_err += np.bitwise_xor(msg, self.decoder(received_msg)[:len(msg)]).sum()
-                bit_send += send_chunk
-            BERs[id_SNR] = bit_err / bit_send
-            if bit_err < err_min:
+                    bit_err = (msg != self.decoder(received_msg)[:len(msg)]).sum()
+                total_bit_err += bit_err
+                total_chunk_count += 1
+                total_chunk_loss += 1 if bit_err > 0 else 0
+                total_bit_send += send_chunk
+            BERs[id_SNR] = total_bit_err / total_bit_send
+            CEs[id_SNR] = total_chunk_loss
+            NCs[id_SNR] = total_chunk_count
+            if total_bit_err < err_min:
                 break
+        self.full_simulation_results = BERs, CEs, NCs
         return BERs
 
 

From c74c6eb1ebe987b1175c85ba9c14de908b7b0402 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt+gitkraken@gmail.com>
Date: Mon, 17 Aug 2020 17:29:27 +0100
Subject: [PATCH 03/54] Add chunk aggregation simulation

Chunk aggregation (or frame aggregation if you're simulating sending frames) aggregates N chunks and sends them as a single chunk of bits.

While from the sending perspective this is exactly the same as sending a chunk N times bigger, from the receiving perspective it is not.
Different chunks can be recover or discarded and account as a loss or received.

To make it clear to control the simulation, send_max parameter was renamed to tx_max, making it functionally similar, but easier to use and understand.
---
 commpy/examples/conv_encode_decode.py |  4 +-
 commpy/links.py                       | 62 +++++++++++++++++----------
 2 files changed, 41 insertions(+), 25 deletions(-)

diff --git a/commpy/examples/conv_encode_decode.py b/commpy/examples/conv_encode_decode.py
index 920c109..ca424ab 100644
--- a/commpy/examples/conv_encode_decode.py
+++ b/commpy/examples/conv_encode_decode.py
@@ -137,8 +137,8 @@ def decoder_soft(msg):
                           decoder_soft, code_rate)
 
 # Test
-BERs_hard = model_hard.link_performance(SNRs, 10000, 600, 5000, code_rate)
-BERs_soft = model_soft.link_performance(SNRs, 10000, 600, 5000, code_rate)
+BERs_hard = model_hard.link_performance(SNRs, 2, 600, 5000, code_rate)
+BERs_soft = model_soft.link_performance(SNRs, 2, 600, 5000, code_rate)
 plt.semilogy(SNRs, BERs_hard, 'o-', SNRs, BERs_soft, 'o-')
 plt.grid()
 plt.xlabel('Signal to Noise Ration (dB)')
diff --git a/commpy/links.py b/commpy/links.py
index 5e973b5..c0c043a 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -59,7 +59,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
            Estimated Bit Error Ratio corresponding to each SNRs
     """
 
-    return link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate)
+    return link_model.link_performance(SNRs, send_max/send_chunk, err_min, send_chunk, code_rate)
 
 
 class LinkModel:
@@ -148,7 +148,8 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
             self.decoder = decoder
         self.full_simulation_results = None
 
-    def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
+    def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate=1,
+                         number_chunks_per_send=1, stop_on_surpass_error=True):
         """
         Estimate the BER performance of a link model with Monte Carlo simulation.
 
@@ -158,8 +159,8 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
                where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
 
-        send_max : int
-                   Maximum number of bits send for each SNR.
+        tx_max : int
+                 Maximum number of transmissions for each SNR.
 
         err_min : int
                   link_performance send bits until it reach err_min errors (see also send_max).
@@ -173,6 +174,13 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                     Rate of the used code.
                     *Default*: 1 i.e. no code.
 
+        number_chunks_per_send : int
+                                 Number of chunks per transmission
+
+        stop_on_surpass_error : bool
+                                Controls if during simulation of a SNR it should break and move to the next SNR when
+                                the bit error is above the err_min parameter
+
         Returns
         -------
         BERs : 1d ndarray
@@ -181,8 +189,9 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
 
         # Initialization
         BERs = np.zeros_like(SNRs, dtype=float)
-        CEs = np.zeros_like(SNRs, dtype=float)  # Chunk Errors
-        NCs = np.zeros_like(SNRs, dtype=float)  # Number of Chunks
+        BEs = np.zeros((len(SNRs), tx_max), dtype=int)  # Bit errors per tx
+        CEs = np.zeros((len(SNRs), tx_max), dtype=int)  # Chunk Errors per tx
+        NCs = np.zeros((len(SNRs), tx_max), dtype=int)  # Number of Chunks per tx
         # Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
         if send_chunk is None:
             send_chunk = err_min
@@ -195,13 +204,15 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
         # Computations
         for id_SNR in range(len(SNRs)):
             self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
-            total_bit_send = 0
-            total_bit_err = 0
-            total_chunk_loss = 0
-            total_chunk_count = 0
-            while total_bit_send < send_max and total_bit_err < err_min:
+            total_tx_send = 0
+            bit_err = np.zeros(tx_max, dtype=int)
+            chunk_loss = np.zeros(tx_max, dtype=int)
+            chunk_count = np.zeros(tx_max, dtype=int)
+            for id_tx in range(tx_max):
+                if stop_on_surpass_error and bit_err.sum() > err_min:
+                    break
                 # Propagate some bits
-                msg = np.random.choice((0, 1), send_chunk)
+                msg = np.random.choice((0, 1), send_chunk * number_chunks_per_send)
                 symbs = self.modulate(msg)
                 channel_output = self.channel.propagate(symbs)
 
@@ -221,19 +232,24 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                     decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
                                                 self.constellation, self.channel.noise_std ** 2,
                                                 received_msg, self.channel.nb_tx * self.num_bits_symbol)
-                    bit_err = (msg != decoded_bits[:len(msg)]).sum()
                 else:
-                    bit_err = (msg != self.decoder(received_msg)[:len(msg)]).sum()
-                total_bit_err += bit_err
-                total_chunk_count += 1
-                total_chunk_loss += 1 if bit_err > 0 else 0
-                total_bit_send += send_chunk
-            BERs[id_SNR] = total_bit_err / total_bit_send
-            CEs[id_SNR] = total_chunk_loss
-            NCs[id_SNR] = total_chunk_count
-            if total_bit_err < err_min:
+                    decoded_bits = self.decoder(received_msg)
+                # calculate number of error frames
+                for i in range(number_chunks_per_send):
+                    errors = np.bitwise_xor(msg[send_chunk * i:send_chunk * (i + 1)],
+                                          decoded_bits[send_chunk * i:send_chunk * (i + 1)]).sum()
+                    bit_err[id_tx] += errors
+                    chunk_loss[id_tx] += 1 if errors > 0 else 0
+
+                chunk_count[id_tx] += number_chunks_per_send
+                total_tx_send += 1
+            BERs[id_SNR] = bit_err.sum() / (total_tx_send*send_chunk)
+            BEs[id_SNR] = bit_err
+            CEs[id_SNR] = np.where(bit_err > 0, 1, 0)
+            NCs[id_SNR] = chunk_count
+            if BEs[id_SNR].sum() < err_min:
                 break
-        self.full_simulation_results = BERs, CEs, NCs
+        self.full_simulation_results = BERs, BEs, CEs, NCs
         return BERs
 
 

From be67f52ed5a027a613ad7e1da6e9baba6fa929a4 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt+gitkraken@gmail.com>
Date: Mon, 17 Aug 2020 19:26:56 +0100
Subject: [PATCH 04/54] Fix link_performance call without send_chunk

---
 commpy/links.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/commpy/links.py b/commpy/links.py
index c0c043a..cc834b4 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -58,8 +58,9 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
     BERs : 1d ndarray
            Estimated Bit Error Ratio corresponding to each SNRs
     """
-
-    return link_model.link_performance(SNRs, send_max/send_chunk, err_min, send_chunk, code_rate)
+    if not send_chunk:
+        send_chunk = err_min
+    return link_model.link_performance(SNRs, math.ceil(send_max/send_chunk), err_min, send_chunk, code_rate)
 
 
 class LinkModel:

From fa698d3fa287e4531f59d41fac3542fd1662a2e3 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt+gitkraken@gmail.com>
Date: Wed, 19 Aug 2020 10:22:04 +0100
Subject: [PATCH 05/54] Add base WiFi 802.11

---
 commpy/tests/test_wifi80211.py |  51 +++++++++++++
 commpy/wifi80211.py            | 126 +++++++++++++++++++++++++++++++++
 2 files changed, 177 insertions(+)
 create mode 100644 commpy/tests/test_wifi80211.py
 create mode 100644 commpy/wifi80211.py

diff --git a/commpy/tests/test_wifi80211.py b/commpy/tests/test_wifi80211.py
new file mode 100644
index 0000000..92e00d1
--- /dev/null
+++ b/commpy/tests/test_wifi80211.py
@@ -0,0 +1,51 @@
+# Authors: CommPy contributors
+# License: BSD 3-Clause
+
+from __future__ import division  # Python 2 compatibility
+
+from numpy import arange, sqrt, log10
+from numpy.random import seed
+from numpy.testing import run_module_suite, assert_allclose, dec
+from scipy.special import erfc
+
+from commpy.channels import MIMOFlatChannel, SISOFlatChannel
+from commpy.links import LinkModel
+from commpy.modulation import QAMModem, kbest
+from commpy.wifi80211 import Wifi80211
+
+
+@dec.slow
+def test_wifi80211():
+    wifi80211 = Wifi80211(1)
+    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10**4, 600)
+    # desired = (0.48, 0.45, 0.44, 0.148, 0.0135)  # From reference
+    # for i, val in enumerate(desired):
+    #     print((BERs[i]-val)/val)
+    # assert_allclose(BERs, desired, rtol=0.3,
+    #                 err_msg='Wrong performance for SISO QPSK and AWGN channel')
+
+    # Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
+    wifi80211 = Wifi80211(3)
+    RayleighChannel = MIMOFlatChannel(4, 4)
+    RayleighChannel.uncorr_rayleigh_fading(complex)
+    modem = wifi80211.get_modem()
+
+    def receiver(y, h, constellation, noise_var):
+        return modem.demodulate(kbest(y, h, constellation, 16), 'hard')
+
+    BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5)+10*log10(modem.num_bits_symbol), 10**4, 600, receiver=receiver)
+    # for i, val in enumerate(BERs):
+    #     print(val)
+    # model = LinkModel(QAM16.modulate, RayleighChannel, receiver,
+    #                   QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es)
+    # SNRs = arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol)
+
+    # BERs = link_performance(model, SNRs, 600e4, 600)
+    # desired = (2e-1, 1e-1, 3e-2, 2e-3, 4e-5)  # From reference
+    # assert_allclose(BERs, desired, rtol=1.25,
+    #                 err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
+
+
+if __name__ == "__main__":
+    seed(17121996)
+    run_module_suite()
diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
new file mode 100644
index 0000000..b9712d8
--- /dev/null
+++ b/commpy/wifi80211.py
@@ -0,0 +1,126 @@
+import math
+
+import numpy as np
+
+import commpy.channelcoding.convcode as cc
+import commpy.links as lk
+import commpy.modulation as mod
+from commpy.channels import _FlatChannel
+
+
+# =============================================================================
+# Convolutional Code
+# =============================================================================
+
+
+class Wifi80211:
+    # Build memory and generator matrix
+    # Number of delay elements in the convolutional encoder
+    # "The encoder uses a 6-stage shift register."
+    # (https://pdfs.semanticscholar.org/c63b/71e43dc23b17ca57267f3b769224c64d5e33.pdf p.19)
+    memory = np.array(6, ndmin=1)
+    generator_matrix = np.array((133, 171), ndmin=2)  # from 802.11 standard, page 2295
+
+    def get_modem(self):
+        qpsks = [
+            2,
+            4,
+            4,
+            16,
+            64,
+            64,
+            64,
+            256,
+            256
+        ]
+        if self.mcs == 0:
+            # BPSK
+            return mod.PSKModem(2)
+        else:
+            # Modem : QPSK
+            return mod.QAMModem(qpsks[self.mcs])
+
+    @staticmethod
+    def get_puncture_matrix(numerator, denominator):
+        if numerator == 1 and denominator == 2:
+            return None
+        # from the standard 802.11 2016
+        if numerator == 2 and denominator == 3:
+            # page 2297
+            return [1, 1, 1, 0]
+        if numerator == 3 and denominator == 4:
+            # page 2297
+            return [1, 1, 1, 0, 0, 1]
+        if numerator == 5 and denominator == 6:
+            # page 2378
+            return [1, 1, 1, 0, 0, 1, 1, 0, 0, 1]
+        return None
+
+    def get_coding(self):
+        coding = [
+            (1, 2),
+            (1, 2),
+            (3, 4),
+            (1, 2),
+            (3, 4),
+            (2, 3),
+            (3, 4),
+            (5, 6),
+            (3, 4),
+            (5, 6),
+        ]
+        return coding[self.mcs]
+
+    @staticmethod
+    def get_trellis():
+        return cc.Trellis(Wifi80211.memory, Wifi80211.generator_matrix)
+
+    def __init__(self, mcs):
+        self.mcs = mcs
+        self.modem = None
+
+    def link_performance(self, channels: _FlatChannel, SNRs, tx_max, err_min, send_chunk=None,
+                         frame_aggregation=1, receiver=None, stop_on_surpass_error=True):
+        trellis1 = Wifi80211.get_trellis()
+        coding = self.get_coding()
+        modem = self.get_modem()
+
+        def modulate(bits):
+            res = cc.conv_encode(bits, trellis1, 'cont')
+            puncture_matrix = Wifi80211.get_puncture_matrix(coding[0], coding[1])
+            res_p = res
+            if puncture_matrix:
+                res_p = cc.puncturing(res, puncture_matrix)
+
+            return modem.modulate(res_p)
+
+        # Receiver function (no process required as there are no fading)
+        def _receiver(y, h, constellation, noise_var):
+            return modem.demodulate(y, 'soft', noise_var)
+
+        if not receiver:
+            receiver = _receiver
+
+        # Decoder function
+        def decoder_soft(msg):
+            msg_d = msg
+            puncture_matrix = Wifi80211.get_puncture_matrix(coding[0], coding[1])
+            if puncture_matrix:
+                try:
+                    msg_d = cc.depuncturing(msg, puncture_matrix, math.ceil(len(msg) * coding[0] / coding[1] * 2))
+                except IndexError as e:
+                    print(e)
+                    print("Decoded message size %d" % (math.ceil(len(msg) * coding[0] / coding[1] * 2)))
+                    print("Encoded message size %d" % len(msg))
+                    print("Coding %d/%d" % (coding[0], coding[1]))
+            return cc.viterbi_decode(msg_d, trellis1, decoding_type='soft')
+
+        self.model = lk.LinkModel(modulate, channels, receiver,
+                                  modem.num_bits_symbol, modem.constellation, modem.Es,
+                                  decoder_soft, coding[0] / coding[1])
+        return self.model.link_performance(SNRs, tx_max,
+                                           err_min=err_min, send_chunk=send_chunk,
+                                           code_rate=coding[0] / coding[1],
+                                           number_chunks_per_send=frame_aggregation,
+                                           stop_on_surpass_error=stop_on_surpass_error
+                                           )

From 9fcd266febac9a581de7607411fffdf48bb7554e Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Wed, 19 Aug 2020 18:15:47 +0100
Subject: [PATCH 06/54] Fix tests on wifi802.11

---
 commpy/tests/test_wifi80211.py | 44 ++++++++++++++++------------------
 1 file changed, 20 insertions(+), 24 deletions(-)

diff --git a/commpy/tests/test_wifi80211.py b/commpy/tests/test_wifi80211.py
index 92e00d1..c5fcca0 100644
--- a/commpy/tests/test_wifi80211.py
+++ b/commpy/tests/test_wifi80211.py
@@ -3,27 +3,30 @@
 
 from __future__ import division  # Python 2 compatibility
 
-from numpy import arange, sqrt, log10
+from numpy import arange, log10
 from numpy.random import seed
-from numpy.testing import run_module_suite, assert_allclose, dec
-from scipy.special import erfc
+from numpy.testing import run_module_suite, dec, assert_allclose
 
 from commpy.channels import MIMOFlatChannel, SISOFlatChannel
-from commpy.links import LinkModel
-from commpy.modulation import QAMModem, kbest
+from commpy.modulation import kbest
 from commpy.wifi80211 import Wifi80211
 
 
 @dec.slow
-def test_wifi80211():
+def test_wifi80211_siso_channel():
+    seed(17121996)
     wifi80211 = Wifi80211(1)
-    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10**4, 600)
-    # desired = (0.48, 0.45, 0.44, 0.148, 0.0135)  # From reference
-    # for i, val in enumerate(desired):
-    #     print((BERs[i]-val)/val)
-    # assert_allclose(BERs, desired, rtol=0.3,
-    #                 err_msg='Wrong performance for SISO QPSK and AWGN channel')
+    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)
+    desired = (0.489, 0.503, 0.446, 0.31, 0.015)  # From previous tests
+    for i, val in enumerate(desired):
+        print((BERs[i] - val) / val)
+    assert_allclose(BERs, desired, rtol=0.3,
+                    err_msg='Wrong performance for SISO QPSK and AWGN channel')
+
 
+@dec.slow
+def test_wifi80211_mimo_channel():
+    seed(17121996)
     # Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
     wifi80211 = Wifi80211(3)
     RayleighChannel = MIMOFlatChannel(4, 4)
@@ -33,19 +36,12 @@ def test_wifi80211():
     def receiver(y, h, constellation, noise_var):
         return modem.demodulate(kbest(y, h, constellation, 16), 'hard')
 
-    BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5)+10*log10(modem.num_bits_symbol), 10**4, 600, receiver=receiver)
-    # for i, val in enumerate(BERs):
-    #     print(val)
-    # model = LinkModel(QAM16.modulate, RayleighChannel, receiver,
-    #                   QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es)
-    # SNRs = arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol)
-
-    # BERs = link_performance(model, SNRs, 600e4, 600)
-    # desired = (2e-1, 1e-1, 3e-2, 2e-3, 4e-5)  # From reference
-    # assert_allclose(BERs, desired, rtol=1.25,
-    #                 err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
+    BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5) + 10 * log10(modem.num_bits_symbol), 10 ** 4,
+                                      600, receiver=receiver)
+    desired = (0.535, 0.508, 0.521, 0.554, 0.475)  # From previous test
+    assert_allclose(BERs, desired, rtol=1.25,
+                    err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
 
 
 if __name__ == "__main__":
-    seed(17121996)
     run_module_suite()

From b172121ce16af55d3c7959374de57243c3a3884a Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Wed, 19 Aug 2020 18:31:56 +0100
Subject: [PATCH 07/54] Improve types

---
 commpy/channelcoding/convcode.py |  8 +++++---
 commpy/links.py                  | 12 ++++++------
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/commpy/channelcoding/convcode.py b/commpy/channelcoding/convcode.py
index dc530f7..291740f 100644
--- a/commpy/channelcoding/convcode.py
+++ b/commpy/channelcoding/convcode.py
@@ -747,7 +747,8 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
 
     return decoded_bits[:L]
 
-def puncturing(message, punct_vec):
+
+def puncturing(message: list, punct_vec: list):
     """
     Applying of the punctured procedure.
     Parameters
@@ -771,7 +772,8 @@ def puncturing(message, punct_vec):
             shift = shift + 1
     return np.array(punctured)
 
-def depuncturing(punctured, punct_vec, shouldbe):
+
+def depuncturing(punctured: list, punct_vec: list, shouldbe: int):
     """
     Applying of the inserting zeros procedure.
     Parameters
@@ -797,5 +799,5 @@ def depuncturing(punctured, punct_vec, shouldbe):
         else:
             shift2 = shift2 + 1
         if idx%N == 0:
-            shift = shift + 1;
+            shift = shift + 1
     return depunctured
diff --git a/commpy/links.py b/commpy/links.py
index cc834b4..447ff71 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -60,7 +60,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
     """
     if not send_chunk:
         send_chunk = err_min
-    return link_model.link_performance(SNRs, math.ceil(send_max/send_chunk), err_min, send_chunk, code_rate)
+    return link_model.link_performance(SNRs, math.ceil(send_max / send_chunk), err_min, send_chunk, code_rate)
 
 
 class LinkModel:
@@ -134,7 +134,7 @@ class LinkModel:
         *Default* is 1.
     """
 
-    def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1):
+    def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1.):
         self.modulate = modulate
         self.channel = channel
         self.receive = receive
@@ -149,7 +149,7 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
             self.decoder = decoder
         self.full_simulation_results = None
 
-    def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate=1,
+    def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
                          number_chunks_per_send=1, stop_on_surpass_error=True):
         """
         Estimate the BER performance of a link model with Monte Carlo simulation.
@@ -171,7 +171,7 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate=1,
                       so it should be large enough regarding the code type.
                       *Default*: send_chunck = err_min
 
-        code_rate : float in (0,1]
+        code_rate : float in (0,1)
                     Rate of the used code.
                     *Default*: 1 i.e. no code.
 
@@ -238,13 +238,13 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate=1,
                 # calculate number of error frames
                 for i in range(number_chunks_per_send):
                     errors = np.bitwise_xor(msg[send_chunk * i:send_chunk * (i + 1)],
-                                          decoded_bits[send_chunk * i:send_chunk * (i + 1)]).sum()
+                                            decoded_bits[send_chunk * i:send_chunk * (i + 1)]).sum()
                     bit_err[id_tx] += errors
                     chunk_loss[id_tx] += 1 if errors > 0 else 0
 
                 chunk_count[id_tx] += number_chunks_per_send
                 total_tx_send += 1
-            BERs[id_SNR] = bit_err.sum() / (total_tx_send*send_chunk)
+            BERs[id_SNR] = bit_err.sum() / (total_tx_send * send_chunk)
             BEs[id_SNR] = bit_err
             CEs[id_SNR] = np.where(bit_err > 0, 1, 0)
             NCs[id_SNR] = chunk_count

From 67f407a7b194da63ed1d7c5f3f2618af00537ada Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Wed, 19 Aug 2020 18:32:11 +0100
Subject: [PATCH 08/54] Document class for Wifi 802.11

---
 commpy/wifi80211.py | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index b9712d8..41e4a59 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -14,6 +14,18 @@
 
 
 class Wifi80211:
+    """
+    This class aims to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac))
+
+    First the chunk is coded according to the generator matrix from the standard, having a rate of 1/2.
+    Then, depending on the Modulation Coding Scheme (MCS) used, puncturing is applied to achieve other coding rates.
+    For more details of which MCS map to which modulation and each coding the standard is *the* recommended place,
+    but for a lighter and faster source to check  https://mcsindex.com is a good place.
+    Finally the bits are then mapped to the modulation scheme in conformity to the MCS (BPSK, QPSK, 16-QAM, 64-QAM, 256-QAM).
+
+    On the receiving the inverse operations are perform, with depuncture when MCS needs it.
+
+    """
     # Build memory and generator matrix
     # Number of delay elements in the convolutional encoder
     # "The encoder uses a 6-stage shift register."

From 5c8c8585b19a9031cacad6a21fa4990560933e02 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Thu, 20 Aug 2020 11:14:52 +0100
Subject: [PATCH 09/54] Add example of usage of the simulation of 802.11

---
 .../examples/wifi80211_conv_encode_decode.py  | 34 +++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 commpy/examples/wifi80211_conv_encode_decode.py

diff --git a/commpy/examples/wifi80211_conv_encode_decode.py b/commpy/examples/wifi80211_conv_encode_decode.py
new file mode 100644
index 0000000..5806cc9
--- /dev/null
+++ b/commpy/examples/wifi80211_conv_encode_decode.py
@@ -0,0 +1,34 @@
+# Authors: CommPy contributors
+# License: BSD 3-Clause
+
+import math
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+import commpy.channels as chan
+# ==================================================================================================
+# Complete example using Commpy Wifi 802.11 physical parameters
+# ==================================================================================================
+from commpy.wifi80211 import Wifi80211
+
+# AWGN channel
+channels = chan.SISOFlatChannel(None, (1 + 0j, 0j))
+
+w2 = Wifi80211(mcs=2)
+w3 = Wifi80211(mcs=3)
+
+# SNR range to test
+SNRs2 = np.arange(0, 6) + 10 * math.log10(w2.get_modem().num_bits_symbol)
+SNRs3 = np.arange(0, 6) + 10 * math.log10(w3.get_modem().num_bits_symbol)
+
+BERs_mcs2 = w2.link_performance(channels, SNRs2, 10, 10, 600, stop_on_surpass_error=False)
+BERs_mcs3 = w3.link_performance(channels, SNRs3, 10, 10, 600, stop_on_surpass_error=False)
+
+# Test
+plt.semilogy(SNRs2, BERs_mcs2, 'o-', SNRs3, BERs_mcs3, 'o-')
+plt.grid()
+plt.xlabel('Signal to Noise Ration (dB)')
+plt.ylabel('Bit Error Rate')
+plt.legend(('MCS 2', 'MCS 3'))
+plt.show()

From e98ed9608b88316a002bc1131882e0a1d6062539 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Fri, 21 Aug 2020 15:43:32 +0100
Subject: [PATCH 10/54] Fix type hints in puncturing and depuncturing

Using np.ndarray where the documentation states they are those types, making it consistent.
---
 commpy/channelcoding/convcode.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/commpy/channelcoding/convcode.py b/commpy/channelcoding/convcode.py
index 291740f..ee3ba08 100644
--- a/commpy/channelcoding/convcode.py
+++ b/commpy/channelcoding/convcode.py
@@ -748,7 +748,7 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
     return decoded_bits[:L]
 
 
-def puncturing(message: list, punct_vec: list):
+def puncturing(message: np.ndarray, punct_vec: np.ndarray) -> np.ndarray:
     """
     Applying of the punctured procedure.
     Parameters
@@ -773,7 +773,7 @@ def puncturing(message: list, punct_vec: list):
     return np.array(punctured)
 
 
-def depuncturing(punctured: list, punct_vec: list, shouldbe: int):
+def depuncturing(punctured: np.ndarray, punct_vec: np.ndarray, shouldbe: int) -> np.ndarray:
     """
     Applying of the inserting zeros procedure.
     Parameters

From 5096d4cf13fd6884e0b83b6a977e7bced16a3acf Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Fri, 21 Aug 2020 15:44:46 +0100
Subject: [PATCH 11/54] Fix indication of code_rate

code_rate needs to be in the range 0 to 1, excluding the zero itself.
---
 commpy/links.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/links.py b/commpy/links.py
index 447ff71..e2939b3 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -171,7 +171,7 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: fl
                       so it should be large enough regarding the code type.
                       *Default*: send_chunck = err_min
 
-        code_rate : float in (0,1)
+        code_rate : float in (0,1]
                     Rate of the used code.
                     *Default*: 1 i.e. no code.
 

From e3bb4f742c595ea521f706d5633758bce700d563 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Fri, 21 Aug 2020 16:50:54 +0100
Subject: [PATCH 12/54] Add back old link_performance

The new proposed link_performance was renamed link_performance_full_metrics and change to return all the calculated metrics.
The change from one method to the other should be quite simple, and same results are produced with more details.
---
 commpy/examples/conv_encode_decode.py |  4 +-
 commpy/links.py                       | 89 +++++++++++++++++++++++++--
 commpy/tests/test_links.py            | 10 ++-
 3 files changed, 95 insertions(+), 8 deletions(-)

diff --git a/commpy/examples/conv_encode_decode.py b/commpy/examples/conv_encode_decode.py
index ca424ab..920c109 100644
--- a/commpy/examples/conv_encode_decode.py
+++ b/commpy/examples/conv_encode_decode.py
@@ -137,8 +137,8 @@ def decoder_soft(msg):
                           decoder_soft, code_rate)
 
 # Test
-BERs_hard = model_hard.link_performance(SNRs, 2, 600, 5000, code_rate)
-BERs_soft = model_soft.link_performance(SNRs, 2, 600, 5000, code_rate)
+BERs_hard = model_hard.link_performance(SNRs, 10000, 600, 5000, code_rate)
+BERs_soft = model_soft.link_performance(SNRs, 10000, 600, 5000, code_rate)
 plt.semilogy(SNRs, BERs_hard, 'o-', SNRs, BERs_soft, 'o-')
 plt.grid()
 plt.xlabel('Signal to Noise Ration (dB)')
diff --git a/commpy/links.py b/commpy/links.py
index e2939b3..a86af36 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -60,7 +60,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
     """
     if not send_chunk:
         send_chunk = err_min
-    return link_model.link_performance(SNRs, math.ceil(send_max / send_chunk), err_min, send_chunk, code_rate)
+    return link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate)
 
 
 class LinkModel:
@@ -149,8 +149,8 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
             self.decoder = decoder
         self.full_simulation_results = None
 
-    def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
-                         number_chunks_per_send=1, stop_on_surpass_error=True):
+    def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
+                                      number_chunks_per_send=1, stop_on_surpass_error=True):
         """
         Estimate the BER performance of a link model with Monte Carlo simulation.
 
@@ -184,8 +184,15 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: fl
 
         Returns
         -------
-        BERs : 1d ndarray
-               Estimated Bit Error Ratio corresponding to each SNRs
+        List[BERs, BEs, CEs, NCs]
+           BERs : 1d ndarray
+                  Estimated Bit Error Ratio corresponding to each SNRs
+           BEs : 2d ndarray
+                 Number of Estimated Bits with Error per transmission corresponding to each SNRs
+           CEs : 2d ndarray
+                 Number of Estimated Chunks with Errors per transmission corresponding to each SNRs
+           NCs : 2d ndarray
+                 Number of Chunks transmitted per transmission corresponding to each SNRs
         """
 
         # Initialization
@@ -251,6 +258,78 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: fl
             if BEs[id_SNR].sum() < err_min:
                 break
         self.full_simulation_results = BERs, BEs, CEs, NCs
+        return BERs, BEs, CEs, NCs
+
+    def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
+        """
+        Estimate the BER performance of a link model with Monte Carlo simulation.
+        Parameters
+        ----------
+        SNRs : 1D arraylike
+               Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
+               where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
+        send_max : int
+                   Maximum number of bits send for each SNR.
+        err_min : int
+                  link_performance send bits until it reach err_min errors (see also send_max).
+        send_chunk : int
+                      Number of bits to be send at each iteration. This is also the frame length of the decoder if available
+                      so it should be large enough regarding the code type.
+                      *Default*: send_chunck = err_min
+        code_rate : float in (0,1]
+                    Rate of the used code.
+                    *Default*: 1 i.e. no code.
+        Returns
+        -------
+        BERs : 1d ndarray
+               Estimated Bit Error Ratio corresponding to each SNRs
+        """
+
+        # Initialization
+        BERs = np.zeros_like(SNRs, dtype=float)
+        # Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
+        if send_chunk is None:
+            send_chunk = err_min
+        divider = self.num_bits_symbol * self.channel.nb_tx
+        send_chunk = max(divider, send_chunk // divider * divider)
+
+        receive_size = self.channel.nb_tx * self.num_bits_symbol
+        full_args_decoder = len(getfullargspec(self.decoder).args) > 1
+
+        # Computations
+        for id_SNR in range(len(SNRs)):
+            self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
+            bit_send = 0
+            bit_err = 0
+            while bit_send < send_max and bit_err < err_min:
+                # Propagate some bits
+                msg = np.random.choice((0, 1), send_chunk)
+                symbs = self.modulate(msg)
+                channel_output = self.channel.propagate(symbs)
+
+                # Deals with MIMO channel
+                if isinstance(self.channel, MIMOFlatChannel):
+                    nb_symb_vector = len(channel_output)
+                    received_msg = np.empty(int(math.ceil(len(msg) / self.rate)), dtype=np.int8)
+                    for i in range(nb_symb_vector):
+                        received_msg[receive_size * i:receive_size * (i + 1)] = \
+                            self.receive(channel_output[i], self.channel.channel_gains[i],
+                                         self.constellation, self.channel.noise_std ** 2)
+                else:
+                    received_msg = self.receive(channel_output, self.channel.channel_gains,
+                                                self.constellation, self.channel.noise_std ** 2)
+                # Count errors
+                if full_args_decoder:
+                    decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
+                                                self.constellation, self.channel.noise_std ** 2,
+                                                received_msg, self.channel.nb_tx * self.num_bits_symbol)
+                    bit_err += np.bitwise_xor(msg, decoded_bits[:len(msg)]).sum()
+                else:
+                    bit_err += np.bitwise_xor(msg, self.decoder(received_msg)[:len(msg)]).sum()
+                bit_send += send_chunk
+            BERs[id_SNR] = bit_err / bit_send
+            if bit_err < err_min:
+                break
         return BERs
 
 
diff --git a/commpy/tests/test_links.py b/commpy/tests/test_links.py
index 9a4ae6b..70812be 100644
--- a/commpy/tests/test_links.py
+++ b/commpy/tests/test_links.py
@@ -23,13 +23,17 @@ def test_link_performance():
 
     def receiver(y, h, constellation, noise_var):
         return QPSK.demodulate(y, 'hard')
+
     model = LinkModel(QPSK.modulate, SISOFlatChannel(fading_param=(1 + 0j, 0)), receiver,
                       QPSK.num_bits_symbol, QPSK.constellation, QPSK.Es)
 
     BERs = link_performance(model, range(0, 9, 2), 600e4, 600)
-    desired = erfc(sqrt(10**(arange(0, 9, 2) / 10) / 2)) / 2
+    desired = erfc(sqrt(10 ** (arange(0, 9, 2) / 10) / 2)) / 2
     assert_allclose(BERs, desired, rtol=0.25,
                     err_msg='Wrong performance for SISO QPSK and AWGN channel')
+    full_metrics = model.link_performance_full_metrics(range(0, 9, 2), 1000, 600)
+    assert_allclose(full_metrics[0], desired, rtol=0.25,
+                    err_msg='Wrong performance for SISO QPSK and AWGN channel')
 
     # Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
     QAM16 = QAMModem(16)
@@ -38,6 +42,7 @@ def receiver(y, h, constellation, noise_var):
 
     def receiver(y, h, constellation, noise_var):
         return QAM16.demodulate(kbest(y, h, constellation, 16), 'hard')
+
     model = LinkModel(QAM16.modulate, RayleighChannel, receiver,
                       QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es)
     SNRs = arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol)
@@ -46,6 +51,9 @@ def receiver(y, h, constellation, noise_var):
     desired = (2e-1, 1e-1, 3e-2, 2e-3, 4e-5)  # From reference
     assert_allclose(BERs, desired, rtol=1.25,
                     err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
+    full_metrics = model.link_performance_full_metrics(SNRs, 1000, 600)
+    assert_allclose(full_metrics[0], desired, rtol=1.25,
+                    err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
 
 
 if __name__ == "__main__":

From 48eaab8ac3ce826ea2121e3db25ee45b1dc0f24b Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Fri, 21 Aug 2020 16:51:25 +0100
Subject: [PATCH 13/54] Fix visibility of methods and add documentation to
 public ones in wifi80211

---
 commpy/wifi80211.py | 86 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 69 insertions(+), 17 deletions(-)

diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index 41e4a59..d97f531 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -1,4 +1,5 @@
 import math
+from typing import List
 
 import numpy as np
 
@@ -33,7 +34,10 @@ class Wifi80211:
     memory = np.array(6, ndmin=1)
     generator_matrix = np.array((133, 171), ndmin=2)  # from 802.11 standard, page 2295
 
-    def get_modem(self):
+    def get_modem(self) -> mod.Modem:
+        """
+        Gets the modem that is going to be used for this particular WiFi simulation according to the MCS
+        """
         qpsks = [
             2,
             4,
@@ -53,7 +57,7 @@ def get_modem(self):
             return mod.QAMModem(qpsks[self.mcs])
 
     @staticmethod
-    def get_puncture_matrix(numerator, denominator):
+    def _get_puncture_matrix(numerator: int, denominator: int) -> List:
         if numerator == 1 and denominator == 2:
             return None
         # from the standard 802.11 2016
@@ -68,7 +72,7 @@ def get_puncture_matrix(numerator, denominator):
             return [1, 1, 1, 0, 0, 1, 1, 0, 0, 1]
         return None
 
-    def get_coding(self):
+    def _get_coding(self):
         coding = [
             (1, 2),
             (1, 2),
@@ -84,22 +88,70 @@ def get_coding(self):
         return coding[self.mcs]
 
     @staticmethod
-    def get_trellis():
+    def _get_trellis():
         return cc.Trellis(Wifi80211.memory, Wifi80211.generator_matrix)
 
-    def __init__(self, mcs):
+    def __init__(self, mcs: int):
+        """
+        Build WiFi 802.11 simulation class
+        Parameters
+        ----------
+        mcs : int
+              The Modulation Coding Scheme (MCS) to simulate.
+              A list of MCS and which coding and modulations they correspond to
+        """
         self.mcs = mcs
         self.modem = None
 
-    def link_performance(self, channels: _FlatChannel, SNRs, tx_max, err_min, send_chunk=None,
+    def link_performance(self, channel: _FlatChannel, SNRs, tx_max, err_min, send_chunk=None,
                          frame_aggregation=1, receiver=None, stop_on_surpass_error=True):
-        trellis1 = Wifi80211.get_trellis()
-        coding = self.get_coding()
+        """
+        Estimate the BER performance of a link model with Monte Carlo simulation as in commpy.links.link_performance
+
+        Parameters
+        ----------
+        channel : _FlatChannel
+                  The channel to be used for the simulation
+
+        SNRs : 1D arraylike
+               Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
+               where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
+
+        tx_max : int
+                 Maximum number of transmissions for each SNR.
+
+        err_min : int
+                  link_performance send bits until it reach err_min errors (see also send_max).
+
+        send_chunk : int
+                     Number of bits to be send at each frame. This is also the frame length of the decoder if available
+                     so it should be large enough regarding the code type.
+                     *Default*: send_chunck = err_min
+
+        frame_aggregation : int
+                            Number of frames aggregated per transmission (each frame with size send_chunk)
+
+        receiver  : function
+                    Specify a custom receiver function to be used in the simulation.
+                    This is particular useful for MIMO simulations.
+
+        stop_on_surpass_error : bool
+                                Controls if during simulation of a SNR it should break and move to the next SNR when
+                                the bit error is above the err_min parameter
+
+        Returns
+        -------
+        BERs : 1d ndarray
+               Estimated Bit Error Ratio corresponding to each SNRs
+        """
+
+        trellis1 = Wifi80211._get_trellis()
+        coding = self._get_coding()
         modem = self.get_modem()
 
         def modulate(bits):
             res = cc.conv_encode(bits, trellis1, 'cont')
-            puncture_matrix = Wifi80211.get_puncture_matrix(coding[0], coding[1])
+            puncture_matrix = Wifi80211._get_puncture_matrix(coding[0], coding[1])
             res_p = res
             if puncture_matrix:
                 res_p = cc.puncturing(res, puncture_matrix)
@@ -116,7 +168,7 @@ def _receiver(y, h, constellation, noise_var):
         # Decoder function
         def decoder_soft(msg):
             msg_d = msg
-            puncture_matrix = Wifi80211.get_puncture_matrix(coding[0], coding[1])
+            puncture_matrix = Wifi80211._get_puncture_matrix(coding[0], coding[1])
             if puncture_matrix:
                 try:
                     msg_d = cc.depuncturing(msg, puncture_matrix, math.ceil(len(msg) * coding[0] / coding[1] * 2))
@@ -127,12 +179,12 @@ def decoder_soft(msg):
                     print("Coding %d/%d" % (coding[0], coding[1]))
             return cc.viterbi_decode(msg_d, trellis1, decoding_type='soft')
 
-        self.model = lk.LinkModel(modulate, channels, receiver,
+        self.model = lk.LinkModel(modulate, channel, receiver,
                                   modem.num_bits_symbol, modem.constellation, modem.Es,
                                   decoder_soft, coding[0] / coding[1])
-        return self.model.link_performance(SNRs, tx_max,
-                                           err_min=err_min, send_chunk=send_chunk,
-                                           code_rate=coding[0] / coding[1],
-                                           number_chunks_per_send=frame_aggregation,
-                                           stop_on_surpass_error=stop_on_surpass_error
-                                           )
+        return self.model.link_performance_full_metrics(SNRs, tx_max,
+                                                        err_min=err_min, send_chunk=send_chunk,
+                                                        code_rate=coding[0] / coding[1],
+                                                        number_chunks_per_send=frame_aggregation,
+                                                        stop_on_surpass_error=stop_on_surpass_error
+                                                        )

From 51c80af2fa439cc3df1d7580b1d5f1fa0575f3bd Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Fri, 21 Aug 2020 17:06:34 +0100
Subject: [PATCH 14/54] Fix tests of wifi80211 to take in account the full
 results

---
 commpy/tests/test_wifi80211.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/commpy/tests/test_wifi80211.py b/commpy/tests/test_wifi80211.py
index c5fcca0..83bb40a 100644
--- a/commpy/tests/test_wifi80211.py
+++ b/commpy/tests/test_wifi80211.py
@@ -16,10 +16,10 @@
 def test_wifi80211_siso_channel():
     seed(17121996)
     wifi80211 = Wifi80211(1)
-    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)
+    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)[0]
     desired = (0.489, 0.503, 0.446, 0.31, 0.015)  # From previous tests
-    for i, val in enumerate(desired):
-        print((BERs[i] - val) / val)
+    # for i, val in enumerate(desired):
+    #     print((BERs[i] - val) / val)
     assert_allclose(BERs, desired, rtol=0.3,
                     err_msg='Wrong performance for SISO QPSK and AWGN channel')
 
@@ -37,7 +37,7 @@ def receiver(y, h, constellation, noise_var):
         return modem.demodulate(kbest(y, h, constellation, 16), 'hard')
 
     BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5) + 10 * log10(modem.num_bits_symbol), 10 ** 4,
-                                      600, receiver=receiver)
+                                      600, receiver=receiver)[0]
     desired = (0.535, 0.508, 0.521, 0.554, 0.475)  # From previous test
     assert_allclose(BERs, desired, rtol=1.25,
                     err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')

From d298ddd4a358ac2ef1e28cf826baede551cfcbee Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Sun, 23 Aug 2020 13:46:06 +0200
Subject: [PATCH 15/54] Update README.md, THANKS.txt and add toctree in
 wifi80211.py

---
 README.md           |  3 +++
 THANKS.txt          |  1 +
 commpy/wifi80211.py | 14 ++++++++++++++
 3 files changed, 18 insertions(+)

diff --git a/README.md b/README.md
index f8e21d6..7370878 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,9 @@ Available Features
 - Binary Symmetric Channel (BSC)
 - Binary AWGN Channel (BAWGNC)
 
+[Wifi 802.11 Simulation Class](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
+- A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac))
+
 [Filters](https://github.com/veeresht/CommPy/blob/master/commpy/filters.py)
 -------
 - Rectangular
diff --git a/THANKS.txt b/THANKS.txt
index c9ec33d..6427e2e 100644
--- a/THANKS.txt
+++ b/THANKS.txt
@@ -2,6 +2,7 @@ Please add names as needed so that we can keep up with all the contributors.
 
 Veeresh Taranalli for initial creation and contribution of CommPy.
 Bastien Trotobas for adding some features, bugfixes, maintenance.
+@eSoare for several enhancements including WiFi 802.11 class and code speedups.
 Vladimir Fadeev for bugfixes, addition of convolutional code puncturing and readme explanation of codings.
 Youness Akourim for adding features and fixing some bugs.
 Rey Tucker for python3 compatibility fixes.
diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index d97f531..f7cb9e4 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -1,3 +1,17 @@
+# Authors: CommPy contributors
+# License: BSD 3-Clause
+
+"""
+============================================
+Wifi 802.11 simulation (:mod:`commpy.wifi80211`)
+============================================
+
+.. autosummary::
+   :toctree: generated/
+
+   Wifi80211    -- Class to simulate the transmissions and receiving parameters of physical layer 802.11
+"""
+
 import math
 from typing import List
 

From eccc88e087ca08a059e772359d688df246dada4b Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Mon, 24 Aug 2020 12:16:29 +0100
Subject: [PATCH 16/54] Add modulation and corresponding coding list to
 documentation

---
 commpy/wifi80211.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index f7cb9e4..1fac50e 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -112,7 +112,17 @@ def __init__(self, mcs: int):
         ----------
         mcs : int
               The Modulation Coding Scheme (MCS) to simulate.
-              A list of MCS and which coding and modulations they correspond to
+              A list of MCS and which coding and modulations they correspond to is bellow:
+                 - 0 : BPSK 1/2
+                 - 1 : QPSK 1/2
+                 - 2 : QPSK 3/4
+                 - 3 : 16-QAM 1/2
+                 - 4 : 16-QAM 3/4
+                 - 5 : 64-QAM 2/3
+                 - 6 : 64-QAM 3/4
+                 - 7 : 64-QAM 5/6
+                 - 8 : 256-QAM 3/4
+                 - 9 : 256-QAM 5/6
         """
         self.mcs = mcs
         self.modem = None

From 78c6e25bae9a37ab2975bd6414ecc93fba72e185 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Mon, 24 Aug 2020 12:18:43 +0100
Subject: [PATCH 17/54] Fix typo in THANKS and add real name

---
 THANKS.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/THANKS.txt b/THANKS.txt
index 6427e2e..68c5492 100644
--- a/THANKS.txt
+++ b/THANKS.txt
@@ -2,7 +2,7 @@ Please add names as needed so that we can keep up with all the contributors.
 
 Veeresh Taranalli for initial creation and contribution of CommPy.
 Bastien Trotobas for adding some features, bugfixes, maintenance.
-@eSoare for several enhancements including WiFi 802.11 class and code speedups.
+Eduardo Soares (@eSoares) for several enhancements including WiFi 802.11 class and code speedups.
 Vladimir Fadeev for bugfixes, addition of convolutional code puncturing and readme explanation of codings.
 Youness Akourim for adding features and fixing some bugs.
 Rey Tucker for python3 compatibility fixes.

From 1d7419b5ec38026b5b2a1e959b03f917a9985627 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Tue, 1 Sep 2020 18:14:49 +0100
Subject: [PATCH 18/54] Fix wrong modulation for mcs 1 and 2

---
 commpy/tests/test_wifi80211.py |  2 +-
 commpy/wifi80211.py            | 13 +++++++------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/commpy/tests/test_wifi80211.py b/commpy/tests/test_wifi80211.py
index 83bb40a..88a6095 100644
--- a/commpy/tests/test_wifi80211.py
+++ b/commpy/tests/test_wifi80211.py
@@ -17,7 +17,7 @@ def test_wifi80211_siso_channel():
     seed(17121996)
     wifi80211 = Wifi80211(1)
     BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)[0]
-    desired = (0.489, 0.503, 0.446, 0.31, 0.015)  # From previous tests
+    desired = (0.548, 0.508, 0.59, 0.81, 0.18)  # From previous tests
     # for i, val in enumerate(desired):
     #     print((BERs[i] - val) / val)
     assert_allclose(BERs, desired, rtol=0.3,
diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index 1fac50e..f26f655 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -52,7 +52,7 @@ def get_modem(self) -> mod.Modem:
         """
         Gets the modem that is going to be used for this particular WiFi simulation according to the MCS
         """
-        qpsks = [
+        bits_per_symbol = [
             2,
             4,
             4,
@@ -63,12 +63,13 @@ def get_modem(self) -> mod.Modem:
             256,
             256
         ]
-        if self.mcs == 0:
-            # BPSK
-            return mod.PSKModem(2)
+        if self.mcs <= 2:
+            # BPSK for mcs 0
+            # QPSK for mcs 1 a 2
+            return mod.PSKModem(bits_per_symbol[self.mcs])
         else:
-            # Modem : QPSK
-            return mod.QAMModem(qpsks[self.mcs])
+            # Modem : QAMModem
+            return mod.QAMModem(bits_per_symbol[self.mcs])
 
     @staticmethod
     def _get_puncture_matrix(numerator: int, denominator: int) -> List:

From a48ce157b64a49c8edc1ad9d58b3c7b6197c1099 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Thu, 17 Sep 2020 16:35:13 +0100
Subject: [PATCH 19/54] Add missing MCS 4 param

---
 commpy/wifi80211.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/commpy/wifi80211.py b/commpy/wifi80211.py
index f26f655..58ee435 100644
--- a/commpy/wifi80211.py
+++ b/commpy/wifi80211.py
@@ -57,6 +57,7 @@ def get_modem(self) -> mod.Modem:
             4,
             4,
             16,
+            16,
             64,
             64,
             64,

From c5919c2cf94ab541c7bbe3a18ef892657c9f2dd2 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Tue, 6 Oct 2020 12:10:04 +0100
Subject: [PATCH 20/54] Adjust send chunk to avoid rounding errors

The send chunk needs to take in to account the coding rate, in order
to after coding the result be divisible by the number of bits of the
modulation.
---
 commpy/channels.py |  2 +-
 commpy/links.py    | 41 +++++++++++++++++++++++++----------------
 2 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/commpy/channels.py b/commpy/channels.py
index 37866af..416a901 100644
--- a/commpy/channels.py
+++ b/commpy/channels.py
@@ -54,7 +54,7 @@ def generate_noises(self, dims):
         else:
             self.noises = standard_normal(dims) * self.noise_std
 
-    def set_SNR_dB(self, SNR_dB, code_rate=1, Es=1):
+    def set_SNR_dB(self, SNR_dB, code_rate: float = 1., Es=1):
 
         """
         Sets the the noise standard deviation based on SNR expressed in dB.
diff --git a/commpy/links.py b/commpy/links.py
index a86af36..dfe24fe 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -17,6 +17,7 @@
 
 import math
 from inspect import getfullargspec
+from fractions import Fraction
 
 import numpy as np
 
@@ -49,7 +50,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
                   so it should be large enough regarding the code type.
                   *Default*: send_chunck = err_min
 
-    code_rate : float in (0,1]
+    code_rate : float or Fraction in (0,1]
                 Rate of the used code.
                 *Default*: 1 i.e. no code.
 
@@ -97,9 +98,9 @@ class LinkModel:
                 binary array.
               *Default* is no process.
 
-    rate : float
-        Code rate.
-        *Default* is 1.
+    rate : float or Fraction in (0,1]
+           Rate of the used code.
+           *Default*: 1 i.e. no code.
 
     Attributes
     ----------
@@ -134,13 +135,15 @@ class LinkModel:
         *Default* is 1.
     """
 
-    def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1.):
+    def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=Fraction(1, 1)):
         self.modulate = modulate
         self.channel = channel
         self.receive = receive
         self.num_bits_symbol = num_bits_symbol
         self.constellation = constellation
         self.Es = Es
+        if type(rate) is float:
+            rate = Fraction(rate).limit_denominator(100)
         self.rate = rate
 
         if decoder is None:
@@ -149,7 +152,7 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
             self.decoder = decoder
         self.full_simulation_results = None
 
-    def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
+    def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: Fraction = Fraction(1, 1),
                                       number_chunks_per_send=1, stop_on_surpass_error=True):
         """
         Estimate the BER performance of a link model with Monte Carlo simulation.
@@ -171,7 +174,7 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
                       so it should be large enough regarding the code type.
                       *Default*: send_chunck = err_min
 
-        code_rate : float in (0,1]
+        code_rate : Fraction in (0,1]
                     Rate of the used code.
                     *Default*: 1 i.e. no code.
 
@@ -200,18 +203,21 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
         BEs = np.zeros((len(SNRs), tx_max), dtype=int)  # Bit errors per tx
         CEs = np.zeros((len(SNRs), tx_max), dtype=int)  # Chunk Errors per tx
         NCs = np.zeros((len(SNRs), tx_max), dtype=int)  # Number of Chunks per tx
-        # Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
+        # Set chunk size and round it to be a multiple of num_bits_symbol* nb_tx to avoid padding taking in to account the coding rate
         if send_chunk is None:
             send_chunk = err_min
-        divider = self.num_bits_symbol * self.channel.nb_tx
-        send_chunk = max(divider, send_chunk // divider * divider)
+        if type(code_rate) is float:
+            code_rate = Fraction(code_rate).limit_denominator(100)
+        self.rate = code_rate
+        divider = (Fraction(1, self.num_bits_symbol * self.channel.nb_tx) * 1 / code_rate).denominator
+        send_chunk = max(divider, (send_chunk * number_chunks_per_send) // divider * divider)
 
         receive_size = self.channel.nb_tx * self.num_bits_symbol
         full_args_decoder = len(getfullargspec(self.decoder).args) > 1
 
         # Computations
         for id_SNR in range(len(SNRs)):
-            self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
+            self.channel.set_SNR_dB(SNRs[id_SNR], float(code_rate), self.Es)
             total_tx_send = 0
             bit_err = np.zeros(tx_max, dtype=int)
             chunk_loss = np.zeros(tx_max, dtype=int)
@@ -227,7 +233,7 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
                 # Deals with MIMO channel
                 if isinstance(self.channel, MIMOFlatChannel):
                     nb_symb_vector = len(channel_output)
-                    received_msg = np.empty(int(math.ceil(len(msg) / self.rate)), dtype=np.int8)
+                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))), dtype=np.int8)
                     for i in range(nb_symb_vector):
                         received_msg[receive_size * i:receive_size * (i + 1)] = \
                             self.receive(channel_output[i], self.channel.channel_gains[i],
@@ -276,7 +282,7 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                       Number of bits to be send at each iteration. This is also the frame length of the decoder if available
                       so it should be large enough regarding the code type.
                       *Default*: send_chunck = err_min
-        code_rate : float in (0,1]
+        code_rate : float or Fraction in (0,1]
                     Rate of the used code.
                     *Default*: 1 i.e. no code.
         Returns
@@ -290,7 +296,10 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
         # Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
         if send_chunk is None:
             send_chunk = err_min
-        divider = self.num_bits_symbol * self.channel.nb_tx
+        if type(code_rate) is float:
+            code_rate = Fraction(code_rate).limit_denominator(100)
+        self.rate = code_rate
+        divider = (Fraction(1, self.num_bits_symbol * self.channel.nb_tx) * 1 / code_rate).denominator
         send_chunk = max(divider, send_chunk // divider * divider)
 
         receive_size = self.channel.nb_tx * self.num_bits_symbol
@@ -298,7 +307,7 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
 
         # Computations
         for id_SNR in range(len(SNRs)):
-            self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
+            self.channel.set_SNR_dB(SNRs[id_SNR], float(code_rate), self.Es)
             bit_send = 0
             bit_err = 0
             while bit_send < send_max and bit_err < err_min:
@@ -310,7 +319,7 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                 # Deals with MIMO channel
                 if isinstance(self.channel, MIMOFlatChannel):
                     nb_symb_vector = len(channel_output)
-                    received_msg = np.empty(int(math.ceil(len(msg) / self.rate)), dtype=np.int8)
+                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))), dtype=np.int8)
                     for i in range(nb_symb_vector):
                         received_msg[receive_size * i:receive_size * (i + 1)] = \
                             self.receive(channel_output[i], self.channel.channel_gains[i],

From abe571e955bb440b813f7d3744193ca37cd0dd20 Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Tue, 6 Oct 2020 12:11:56 +0100
Subject: [PATCH 21/54] Adjust trellis to work in smaller send chunks

Smaller send chunks can cause the trellis to not go through all the
depth. Causing the bits to not be decoded and ending in errors.
---
 commpy/channelcoding/convcode.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/commpy/channelcoding/convcode.py b/commpy/channelcoding/convcode.py
index ee3ba08..d1dcf16 100644
--- a/commpy/channelcoding/convcode.py
+++ b/commpy/channelcoding/convcode.py
@@ -695,12 +695,13 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
     rate = k/n
     total_memory = trellis.total_memory
 
-    if tb_depth is None:
-        tb_depth = 5*total_memory
-
     # Number of message bits after decoding
     L = int(len(coded_bits)*rate)
 
+    if tb_depth is None:
+        tb_depth = min(5 * total_memory, L)
+
+
     path_metrics = np.full((trellis.number_states, 2), np.inf)
     path_metrics[0][0] = 0
     paths = np.empty((trellis.number_states, tb_depth), 'int')

From 3bc2faf2062c1784cd6dc712df67aaed5a0da1ee Mon Sep 17 00:00:00 2001
From: Eduardo Soares <eduardosoares.pt@gmail.com>
Date: Tue, 6 Oct 2020 16:01:13 +0100
Subject: [PATCH 22/54] Remove chunks per send when finding the right send
 chunk

It would make send_chunk bigger than needed, and if the send_chunk is divisible, so is the send_chunk*number_chunks_per_send.
---
 commpy/links.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/links.py b/commpy/links.py
index dfe24fe..ed289ee 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -210,7 +210,7 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
             code_rate = Fraction(code_rate).limit_denominator(100)
         self.rate = code_rate
         divider = (Fraction(1, self.num_bits_symbol * self.channel.nb_tx) * 1 / code_rate).denominator
-        send_chunk = max(divider, (send_chunk * number_chunks_per_send) // divider * divider)
+        send_chunk = max(divider, send_chunk // divider * divider)
 
         receive_size = self.channel.nb_tx * self.num_bits_symbol
         full_args_decoder = len(getfullargspec(self.decoder).args) > 1

From 5f9f8eccb186c05d2d307101aeefde447ac1fa5a Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Fri, 9 Oct 2020 13:44:18 +0200
Subject: [PATCH 23/54] Setup file for v0.6

---
 setup.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/setup.py b/setup.py
index 080e8c0..c8bc9ba 100644
--- a/setup.py
+++ b/setup.py
@@ -11,12 +11,12 @@
 MAINTAINER_EMAIL = 'veeresht@gmail.com'
 URL = 'http://veeresht.github.com/CommPy'
 LICENSE = 'BSD 3-Clause'
-VERSION = '0.5.0'
+VERSION = '0.6.0'
 
 #This is a list of files to install, and where
 #(relative to the 'root' dir, where setup.py is)
 #You could be more specific.
-files = ["channelcoding/*, channelcoding/tests/*, tests/*"]
+files = ["channelcoding/*, channelcoding/tests/*, tests/*, channelcoding/designs/ldpc/gallager/*, channelcoding/designs/ldpc/wimax/*"]
 
 setup(
     name=DISTNAME,

From ee289ae5ea1c26d87a901bfc0e2b6d21e0da43e2 Mon Sep 17 00:00:00 2001
From: Bastien Trotobas <34445644+BastienTr@users.noreply.github.com>
Date: Mon, 12 Oct 2020 22:23:34 +0200
Subject: [PATCH 24/54] [Bug fix] Bad typing in links.py introduced in bcc36ddc
 (#88)

[Bug fix] Bad typing in links.py introduced in bcc36ddc
---
 commpy/links.py            | 16 +++----
 commpy/tests/test_links.py | 92 +++++++++++++++++++++++++++-----------
 2 files changed, 75 insertions(+), 33 deletions(-)

diff --git a/commpy/links.py b/commpy/links.py
index ed289ee..356f32d 100644
--- a/commpy/links.py
+++ b/commpy/links.py
@@ -16,8 +16,8 @@
 from __future__ import division  # Python 2 compatibility
 
 import math
-from inspect import getfullargspec
 from fractions import Fraction
+from inspect import getfullargspec
 
 import numpy as np
 
@@ -95,7 +95,7 @@ class LinkModel:
          *Default* Es=1.
 
     decoder : function with prototype decoder(array) or decoder(y, H, constellation, noise_var, array) that return a
-                binary array.
+                binary ndarray.
               *Default* is no process.
 
     rate : float or Fraction in (0,1]
@@ -127,7 +127,7 @@ class LinkModel:
     Es : float
          Average energy per symbols.
 
-    decoder : function with prototype decoder(binary array) that return a binary array.
+    decoder : function with prototype decoder(binary array) that return a binary ndarray.
               *Default* is no process.
 
     rate : float
@@ -233,7 +233,7 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
                 # Deals with MIMO channel
                 if isinstance(self.channel, MIMOFlatChannel):
                     nb_symb_vector = len(channel_output)
-                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))), dtype=np.int8)
+                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))))
                     for i in range(nb_symb_vector):
                         received_msg[receive_size * i:receive_size * (i + 1)] = \
                             self.receive(channel_output[i], self.channel.channel_gains[i],
@@ -251,7 +251,7 @@ def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None,
                 # calculate number of error frames
                 for i in range(number_chunks_per_send):
                     errors = np.bitwise_xor(msg[send_chunk * i:send_chunk * (i + 1)],
-                                            decoded_bits[send_chunk * i:send_chunk * (i + 1)]).sum()
+                                            decoded_bits[send_chunk * i:send_chunk * (i + 1)].astype(int)).sum()
                     bit_err[id_tx] += errors
                     chunk_loss[id_tx] += 1 if errors > 0 else 0
 
@@ -319,7 +319,7 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                 # Deals with MIMO channel
                 if isinstance(self.channel, MIMOFlatChannel):
                     nb_symb_vector = len(channel_output)
-                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))), dtype=np.int8)
+                    received_msg = np.empty(int(math.ceil(len(msg) / float(self.rate))))
                     for i in range(nb_symb_vector):
                         received_msg[receive_size * i:receive_size * (i + 1)] = \
                             self.receive(channel_output[i], self.channel.channel_gains[i],
@@ -332,9 +332,9 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
                     decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
                                                 self.constellation, self.channel.noise_std ** 2,
                                                 received_msg, self.channel.nb_tx * self.num_bits_symbol)
-                    bit_err += np.bitwise_xor(msg, decoded_bits[:len(msg)]).sum()
+                    bit_err += np.bitwise_xor(msg, decoded_bits[:len(msg)].astype(int)).sum()
                 else:
-                    bit_err += np.bitwise_xor(msg, self.decoder(received_msg)[:len(msg)]).sum()
+                    bit_err += np.bitwise_xor(msg, self.decoder(received_msg)[:len(msg)].astype(int)).sum()
                 bit_send += send_chunk
             BERs[id_SNR] = bit_err / bit_send
             if bit_err < err_min:
diff --git a/commpy/tests/test_links.py b/commpy/tests/test_links.py
index 70812be..5a8c548 100644
--- a/commpy/tests/test_links.py
+++ b/commpy/tests/test_links.py
@@ -8,34 +8,41 @@
 from numpy.testing import run_module_suite, assert_allclose, dec
 from scipy.special import erfc
 
+from commpy.channelcoding.ldpc import get_ldpc_code_params, triang_ldpc_systematic_encode, ldpc_bp_decode
 from commpy.channels import MIMOFlatChannel, SISOFlatChannel
 from commpy.links import link_performance, LinkModel
-from commpy.modulation import QAMModem, kbest
+from commpy.modulation import QAMModem, kbest, best_first_detector
 
 
 @dec.slow
 def test_link_performance():
     # Set seed
-    seed(17121996)
+    seed(8071996)
+    ######################################
+    # Build models & desired solutions
+    ######################################
+    models = []
+    desired_bers = []
+    snr_range = []
+    labels = []
+    rtols = []
+    code_rates = []
 
-    # Apply link_performance to SISO QPSK and AWGN channel
+    # SISO QPSK and AWGN channel
     QPSK = QAMModem(4)
 
     def receiver(y, h, constellation, noise_var):
         return QPSK.demodulate(y, 'hard')
 
-    model = LinkModel(QPSK.modulate, SISOFlatChannel(fading_param=(1 + 0j, 0)), receiver,
-                      QPSK.num_bits_symbol, QPSK.constellation, QPSK.Es)
+    models.append(LinkModel(QPSK.modulate, SISOFlatChannel(fading_param=(1 + 0j, 0)), receiver,
+                            QPSK.num_bits_symbol, QPSK.constellation, QPSK.Es))
+    snr_range.append(arange(0, 9, 2))
+    desired_bers.append(erfc(sqrt(10 ** (snr_range[-1] / 10) / 2)) / 2)
+    labels.append('SISO QPSK and AWGN channel')
+    rtols.append(.25)
+    code_rates.append(1)
 
-    BERs = link_performance(model, range(0, 9, 2), 600e4, 600)
-    desired = erfc(sqrt(10 ** (arange(0, 9, 2) / 10) / 2)) / 2
-    assert_allclose(BERs, desired, rtol=0.25,
-                    err_msg='Wrong performance for SISO QPSK and AWGN channel')
-    full_metrics = model.link_performance_full_metrics(range(0, 9, 2), 1000, 600)
-    assert_allclose(full_metrics[0], desired, rtol=0.25,
-                    err_msg='Wrong performance for SISO QPSK and AWGN channel')
-
-    # Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
+    # MIMO 16QAM, 4x4 Rayleigh channel and hard-output K-Best
     QAM16 = QAMModem(16)
     RayleighChannel = MIMOFlatChannel(4, 4)
     RayleighChannel.uncorr_rayleigh_fading(complex)
@@ -43,17 +50,52 @@ def receiver(y, h, constellation, noise_var):
     def receiver(y, h, constellation, noise_var):
         return QAM16.demodulate(kbest(y, h, constellation, 16), 'hard')
 
-    model = LinkModel(QAM16.modulate, RayleighChannel, receiver,
-                      QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es)
-    SNRs = arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol)
-
-    BERs = link_performance(model, SNRs, 600e4, 600)
-    desired = (2e-1, 1e-1, 3e-2, 2e-3, 4e-5)  # From reference
-    assert_allclose(BERs, desired, rtol=1.25,
-                    err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
-    full_metrics = model.link_performance_full_metrics(SNRs, 1000, 600)
-    assert_allclose(full_metrics[0], desired, rtol=1.25,
-                    err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
+    models.append(LinkModel(QAM16.modulate, RayleighChannel, receiver,
+                            QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es))
+    snr_range.append(arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol))
+    desired_bers.append((2e-1, 1e-1, 3e-2, 2e-3, 4e-5))  # From reference
+    labels.append('MIMO 16QAM, 4x4 Rayleigh channel and hard-output K-Best')
+    rtols.append(1.25)
+    code_rates.append(1)
+
+    # MIMO 16QAM, 4x4 Rayleigh channel and soft-output best-first
+    QAM16 = QAMModem(16)
+    RayleighChannel = MIMOFlatChannel(4, 4)
+    RayleighChannel.uncorr_rayleigh_fading(complex)
+    ldpc_params = get_ldpc_code_params('commpy/channelcoding/designs/ldpc/wimax/1440.720.txt', True)
+
+    def modulate(bits):
+        return QAM16.modulate(triang_ldpc_systematic_encode(bits, ldpc_params, False).reshape(-1, order='F'))
+
+    def decoder(llrs):
+        return ldpc_bp_decode(llrs, ldpc_params, 'MSA', 15)[0][:720].reshape(-1, order='F')
+
+    def demode(symbs):
+        return QAM16.demodulate(symbs, 'hard')
+
+    def receiver(y, h, constellation, noise_var):
+        return best_first_detector(y, h, constellation, (1, 3, 5), noise_var, demode, 500)
+
+    models.append(LinkModel(modulate, RayleighChannel, receiver,
+                            QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es,
+                            decoder, 0.5))
+    snr_range.append(arange(17, 20, 1))
+    desired_bers.append((1.7e-1, 1e-1, 2.5e-3))  # From reference
+    labels.append('MIMO 16QAM, 4x4 Rayleigh channel and soft-output best-first')
+    rtols.append(2)
+    code_rates.append(.5)
+
+    ######################################
+    # Make tests
+    ######################################
+
+    for test in range(len(models)):
+        BERs = link_performance(models[test], snr_range[test], 5e5, 200, 720, models[test].rate)
+        assert_allclose(BERs, desired_bers[test], rtol=rtols[test],
+                        err_msg='Wrong performance for ' + labels[test])
+        full_metrics = models[test].link_performance_full_metrics(snr_range[test], 2500, 200, 720, models[test].rate)
+        assert_allclose(full_metrics[0], desired_bers[test], rtol=rtols[test],
+                        err_msg='Wrong performance for ' + labels[test])
 
 
 if __name__ == "__main__":

From c1690794f57717bdbd3abb70c0b47a1216082e2e Mon Sep 17 00:00:00 2001
From: Bastien Trotobas <34445644+BastienTr@users.noreply.github.com>
Date: Tue, 3 Nov 2020 10:42:00 +0100
Subject: [PATCH 25/54] [minor] Update Readme

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7370878..2ca4fbd 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Available Features
 - Binary Symmetric Channel (BSC)
 - Binary AWGN Channel (BAWGNC)
 
-[Wifi 802.11 Simulation Class](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
+[**Wifi 802.11 Simulation Class**](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
 - A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac))
 
 [Filters](https://github.com/veeresht/CommPy/blob/master/commpy/filters.py)

From bd47de10449efc81c286e229ca80cc484d6f1e08 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Mon, 9 Nov 2020 11:07:36 +0100
Subject: [PATCH 26/54] Bug fix in ldpc_bp_decode

The sparse-matrix structure could change during the decoding process leading to unexpected behaviour. That's why we only work on the `data` field preserving the sparse structure.
---
 commpy/channelcoding/ldpc.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/commpy/channelcoding/ldpc.py b/commpy/channelcoding/ldpc.py
index f843f68..6913223 100644
--- a/commpy/channelcoding/ldpc.py
+++ b/commpy/channelcoding/ldpc.py
@@ -241,8 +241,8 @@ def ldpc_bp_decode(llr_vec, ldpc_code_params, decoder_algorithm, n_iters):
 
             # Variable Node Update
             msg_sum = np.array(message_matrix.sum(0)).squeeze()
-            message_matrix *= -1
-            message_matrix += parity_check_matrix.multiply(msg_sum + llr_vec[i_start:i_stop])
+            message_matrix.data *= -1
+            message_matrix.data += parity_check_matrix.multiply(msg_sum + llr_vec[i_start:i_stop]).data
 
             out_llrs = msg_sum + llr_vec[i_start:i_stop]
             np.signbit(out_llrs, out=dec_word[i_start:i_stop])

From 52da77d49854568c07e10fed94bbff31bb4f4f05 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 09:11:33 +0100
Subject: [PATCH 27/54] fix incorrect CAZAC sequence

---
 commpy/sequences.py | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/commpy/sequences.py b/commpy/sequences.py
index ba1a97b..67c5bf4 100644
--- a/commpy/sequences.py
+++ b/commpy/sequences.py
@@ -72,23 +72,35 @@ def pnsequence(pn_order, pn_seed, pn_mask, seq_length):
 
     return pnseq
 
-def zcsequence(u, seq_length):
+def zcsequence(u, seq_length, q=0):
     """
     Generate a Zadoff-Chu (ZC) sequence.
 
     Parameters
     ----------
     u : int
-        Root index of the the ZC sequence.
+        Root index of the the ZC sequence: u>0.
 
     seq_length : int
-        Length of the sequence to be generated. Usually a prime number.
+        Length of the sequence to be generated. Usually a prime number:
+        u<seq_length, greatest-common-denominator(u,seq_length)=1.
+
+    q : int
+        Cyclic shift of the sequence (default 0).
 
     Returns
     -------
     zcseq : 1D ndarray of complex floats
         ZC sequence generated.
     """
-    zcseq = exp((-1j * pi * u * arange(seq_length) * (arange(seq_length)+1)) / seq_length)
+    assert u>0
+    assert u<seq_length
+    assert np.gcd(u,seq_length)==1
+    for el in [u,seq_length,q]:
+        assert float(el).is_integer()
+
+    cf = seq_length%2
+    n = arange(seq_length)
+    zcseq = exp( -1j * pi * u * n * (n+cf+2.*q) / seq_length)
 
     return zcseq

From 246acba65ddbcdf7c9e42d65e7f1571608d4cfa0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 09:16:37 +0100
Subject: [PATCH 28/54] import numpy as np

---
 commpy/sequences.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/commpy/sequences.py b/commpy/sequences.py
index 67c5bf4..7612e1e 100644
--- a/commpy/sequences.py
+++ b/commpy/sequences.py
@@ -15,6 +15,7 @@
 """
 __all__ = ['pnsequence', 'zcsequence']
 
+import numpy as np
 from numpy import empty, exp, pi, arange, int8, fromiter, sum
 
 def pnsequence(pn_order, pn_seed, pn_mask, seq_length):
@@ -100,7 +101,7 @@ def zcsequence(u, seq_length, q=0):
         assert float(el).is_integer()
 
     cf = seq_length%2
-    n = arange(seq_length)
-    zcseq = exp( -1j * pi * u * n * (n+cf+2.*q) / seq_length)
+    n = np.arange(seq_length)
+    zcseq = np.exp( -1j * np.pi * u * n * (n+cf+2.*q) / seq_length)
 
     return zcseq

From e2f2605200ab977e8266b38ebf492b73381c18cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 10:51:28 +0100
Subject: [PATCH 29/54] change matrix [[0o171, 0o133]] to [[5, 7]] and deal
 with padded decodded messages

---
 commpy/channelcoding/README.md | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/commpy/channelcoding/README.md b/commpy/channelcoding/README.md
index 57eb338..8964a0d 100644
--- a/commpy/channelcoding/README.md
+++ b/commpy/channelcoding/README.md
@@ -78,11 +78,12 @@ The [following](https://en.wikipedia.org/wiki/File:Conv_code_177_133.png) convol
 Convolutional encoder parameters:
 
 ```python
+generator_matrix = np.array([[5, 7]]) # generator branches
+trellis = cc.Trellis(np.array([M]), generator_matrix) # Trellis structure
+
 rate = 1/2 # code rate
 L = 7 # constraint length
 m = np.array([L-1]) # number of delay elements
-generator_matrix = np.array([[0o171, 0o133]]) # generator branches
-trellis = cc.Trellis(M, generator_matrix) # Trellis structure
 ```
 
 Viterbi decoder parameters:
@@ -106,9 +107,9 @@ noiseVar = 10**(-snrdB/10) # noise variance (power)
 
 N_c = 10 # number of trials
 
-BER_soft = np.empty((N_c,))
-BER_hard = np.empty((N_c,))
-BER_uncoded = np.empty((N_c,))
+BER_soft = -9e99*np.ones(N_c)
+BER_hard = -9e99*np.ones(N_c)
+BER_uncoded = -9e99*np.ones(N_c)
 
 for cntr in range(N_c):
     
@@ -136,18 +137,17 @@ for cntr in range(N_c):
     decoded_soft = cc.viterbi_decode(demodulated_soft, trellis, tb_depth, decoding_type='unquantized') # decoding (soft decision)
     decoded_hard = cc.viterbi_decode(demodulated_hard, trellis, tb_depth, decoding_type='hard') # decoding (hard decision)
 
+    NumErr, BER_soft[cntr] = BER_calc(message_bits, decoded_soft[:message_bits.size]) # bit-error ratio (soft decision)
+    NumErr, BER_hard[cntr] = BER_calc(message_bits, decoded_hard[:message_bits.size]) # bit-error ratio (hard decision)
+    NumErr, BER_uncoded[cntr] = BER_calc(message_bits, demodulated_uncoded[:message_bits.size]) # bit-error ratio (uncoded case)
 
-    NumErr, BER_soft[cntr] = BER_calc(message_bits, decoded_soft[:-(L-1)]) # bit-error ratio (soft decision)
-    NumErr, BER_hard[cntr] = BER_calc(message_bits, decoded_hard[:-(L-1)]) # bit-error ratio (hard decision)
-    NumErr, BER_uncoded[cntr] = BER_calc(message_bits, demodulated_uncoded) # bit-error ratio (uncoded case)
-
-mean_BER_soft = np.mean(BER_soft) # averaged bit-error ratio (soft decision)
-mean_BER_hard = np.mean(BER_hard) # averaged bit-error ratio (hard decision)
-mean_BER_uncoded = np.mean(BER_uncoded) # averaged bit-error ratio (uncoded case)
+mean_BER_soft = BER_soft.mean() # averaged bit-error ratio (soft decision)
+mean_BER_hard = BER_hard.mean() # averaged bit-error ratio (hard decision)
+mean_BER_uncoded = BER_uncoded.mean() # averaged bit-error ratio (uncoded case)
 
-print("Soft decision:\n"+str(mean_BER_soft)+"\n")
-print("Hard decision:\n"+str(mean_BER_hard)+"\n")
-print("Uncoded message:\n"+str(mean_BER_uncoded)+"\n")
+print("Soft decision:\n{}\n".format(mean_BER_soft))
+print("Hard decision:\n{}\n".format(mean_BER_hard))
+print("Uncoded message:\n{}\n".format(mean_BER_uncoded))
 ```
 
 Outputs:

From 65bfc19ac0f31adde87ce273eaaf84031c357d23 Mon Sep 17 00:00:00 2001
From: Bastien Trotobas <34445644+BastienTr@users.noreply.github.com>
Date: Tue, 10 Nov 2020 10:52:03 +0100
Subject: [PATCH 30/54] [minor] Useless commit to test Travis

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 2ca4fbd..4047d59 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Available Features
 - Binary AWGN Channel (BAWGNC)
 
 [**Wifi 802.11 Simulation Class**](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
-- A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac))
+- A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac)).
 
 [Filters](https://github.com/veeresht/CommPy/blob/master/commpy/filters.py)
 -------

From 47971d6132f083f2fdcd5f9443eaa957dcad29fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 11:05:52 +0100
Subject: [PATCH 31/54] update output message

---
 commpy/channelcoding/README.md | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/commpy/channelcoding/README.md b/commpy/channelcoding/README.md
index 8964a0d..8f3b661 100644
--- a/commpy/channelcoding/README.md
+++ b/commpy/channelcoding/README.md
@@ -153,14 +153,14 @@ print("Uncoded message:\n{}\n".format(mean_BER_uncoded))
 Outputs:
 
 ```python
->>> Soft decision:
->>> 0.0
->>>
->>> Hard decision:
->>> 3.0000000000000004e-05
->>>
->>> Uncoded message:
->>> 0.008782
+Soft decision:
+2.8000000000000003e-05
+
+Hard decision:
+0.0007809999999999999
+
+Uncoded message:
+0.009064
 ```
 
 ### Reference

From 156b078757dbfb0f6b637e496c034d9011e0ad93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 11:51:13 +0100
Subject: [PATCH 32/54] Trigger notification


From ac51ec65a6767c903ab0f2bbae420054e69d2c08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 15:47:06 +0100
Subject: [PATCH 33/54] raise value error and add unittest

---
 commpy/sequences.py            | 13 +++++++++----
 commpy/tests/test_sequences.py | 35 +++++++++++++++++++++++++++++++---
 2 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/commpy/sequences.py b/commpy/sequences.py
index 7612e1e..2862290 100644
--- a/commpy/sequences.py
+++ b/commpy/sequences.py
@@ -94,11 +94,16 @@ def zcsequence(u, seq_length, q=0):
     zcseq : 1D ndarray of complex floats
         ZC sequence generated.
     """
-    assert u>0
-    assert u<seq_length
-    assert np.gcd(u,seq_length)==1
+
     for el in [u,seq_length,q]:
-        assert float(el).is_integer()
+        if not float(el).is_integer():
+            raise ValueError('{} is not an integer'.format(el))
+    if u<=0:
+        raise ValueError('u is not stricly positive')
+    if u>=seq_length:
+        raise ValueError('u is not stricly smaller than seq_length')
+    if np.gcd(u,seq_length)!=1:
+        raise ValueError('the greatest common denominator of u and seq_length is not 1')
 
     cf = seq_length%2
     n = np.arange(seq_length)
diff --git a/commpy/tests/test_sequences.py b/commpy/tests/test_sequences.py
index f48f332..51a5bcb 100644
--- a/commpy/tests/test_sequences.py
+++ b/commpy/tests/test_sequences.py
@@ -1,16 +1,17 @@
 # Authors: CommPy contributors
 # License: BSD 3-Clause
 
+import numpy as np
 from numpy import array
-from numpy.testing import run_module_suite, assert_raises, assert_equal
-
-from commpy.sequences import pnsequence
+from numpy.testing import run_module_suite, assert_raises, assert_equal, assert_almost_equal
 
+from commpy.sequences import pnsequence, zcsequence
 
 def test_pnsequence():
     # Test the raises of errors
     with assert_raises(ValueError):
         pnsequence(4, '001', '1101', 2**4 - 1)
+    with assert_raises(ValueError):
         pnsequence(4, '0011', '110', 2 ** 4 - 1)
 
     # Test output with
@@ -19,6 +20,34 @@ def test_pnsequence():
     assert_equal(pnsequence(4, (0, 0, 1, 1), array((1, 1, 0, 1)), 7), array((1, 1, 0, 0, 1, 0, 1), int),
                  err_msg='Pseudo-noise sequence is not the one expected.')
 
+def test_zcsequence():
+    # Test the raises of errors
+    with assert_raises(ValueError):
+        zcsequence(u=-1, seq_length=20, q=0)
+    with assert_raises(ValueError):
+        zcsequence(u=20, seq_length=0, q=0)
+    with assert_raises(ValueError):
+        zcsequence(u=3, seq_length=18, q=0)
+    with assert_raises(ValueError):
+        zcsequence(u=3.1, seq_length=11, q=0)
+    with assert_raises(ValueError):
+        zcsequence(u=3, seq_length=11.1, q=0)
+    with assert_raises(ValueError):
+        zcsequence(u=3, seq_length=11, q=0.1)
+
+    # Test output with
+    assert_almost_equal(zcsequence(u=1, seq_length=2, q=0), array([1.000000e+00+0.j, 6.123234e-17-1.j]),
+        err_msg='CAZAC sequence is not the expected one.')
+
+    # Test if output cross-correlation is valid
+    seqCAZAC = zcsequence(u=3, seq_length=20, q=0)
+    x = np.fft.fft(seqCAZAC) / np.sqrt(seqCAZAC.size)
+    h = (np.fft.ifft(np.conj(x) * x)*np.sqrt(seqCAZAC.size)).T
+    corr = np.absolute(h)**2/h.size
+    assert_almost_equal(corr[0], 1.,
+        err_msg='CAZAC sequence auto-correlation is not valid, first term is not 1')
+    assert_almost_equal(corr[1:], np.zeros(corr.size-1),
+        err_msg='CAZAC sequence auto-correlation is not valid, all terms except first are not 0')
 
 if __name__ == "__main__":
     run_module_suite()

From 1fc1334a0561a273a4e26d11069f81afed643212 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?H=C3=A9lion=20du=20Mas=20des=20Bourboux?=
 <helion.dumasdesbourboux@thalesgroup.com>
Date: Tue, 10 Nov 2020 15:51:09 +0100
Subject: [PATCH 34/54] initialize arrays to zeros

---
 commpy/channelcoding/README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/commpy/channelcoding/README.md b/commpy/channelcoding/README.md
index 8f3b661..14fd9f4 100644
--- a/commpy/channelcoding/README.md
+++ b/commpy/channelcoding/README.md
@@ -107,9 +107,9 @@ noiseVar = 10**(-snrdB/10) # noise variance (power)
 
 N_c = 10 # number of trials
 
-BER_soft = -9e99*np.ones(N_c)
-BER_hard = -9e99*np.ones(N_c)
-BER_uncoded = -9e99*np.ones(N_c)
+BER_soft = np.zeros(N_c)
+BER_hard = np.zeros(N_c)
+BER_uncoded = np.zeros(N_c)
 
 for cntr in range(N_c):
     

From 47433d60b82774854dc92396384504416b9e5890 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Mon, 4 Jan 2021 10:39:23 +0100
Subject: [PATCH 35/54] Fix #96 : fractions.gcd is deprecated

---
 commpy/channelcoding/gfields.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/commpy/channelcoding/gfields.py b/commpy/channelcoding/gfields.py
index 49cc680..6bd0e1d 100644
--- a/commpy/channelcoding/gfields.py
+++ b/commpy/channelcoding/gfields.py
@@ -3,7 +3,7 @@
 
 """ Galois Fields """
 
-from fractions import gcd
+from math import gcd
 
 from numpy import array, zeros, arange, convolve, ndarray, concatenate
 
@@ -52,7 +52,7 @@ def __init__(self, x, m):
         if type(x) is int and x >= 0 and x < pow(2, m):
             self.elements = array([x])
         elif type(x) is ndarray and len(x) >= 1:
-            self.elements = x
+            self.elements = x.astype(int)
 
     # Overloading addition operator for Galois Field
     def __add__(self, x):

From 4013d3107ab37479e9905f4dcfc5a389030d7ce3 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Tue, 5 Jan 2021 10:22:23 +0100
Subject: [PATCH 36/54] Small README changes

---
 README.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 1c34b76..fe6164d 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 
 
 [![Build Status](https://secure.travis-ci.org/veeresht/CommPy.svg?branch=master)](https://secure.travis-ci.org/veeresht/CommPy)
-[![Coverage](https://coveralls.io/repos/veeresht/CommPy/badge.svg)](https://coveralls.io/r/veeresht/CommPy)
+[![Coverage](https://coveralls.io/repos/veeresht/CommPy/badge.svg?branch=master)](https://coveralls.io/r/veeresht/CommPy)
 [![PyPi](https://badge.fury.io/py/scikit-commpy.svg)](https://badge.fury.io/py/scikit-commpy)
 [![Docs](https://readthedocs.org/projects/commpy/badge/?version=latest)](http://commpy.readthedocs.io/en/latest/?badge=latest)
 
@@ -37,6 +37,9 @@ Available Features
 - Binary Symmetric Channel (BSC)
 - Binary AWGN Channel (BAWGNC)
 
+[Wifi 802.11 Simulation Class](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
+- A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac)).
+
 [Filters](https://github.com/veeresht/CommPy/blob/master/commpy/filters.py)
 -------
 - Rectangular

From 5dba9a496835313fec7076407daddcbc702dfa09 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Tue, 5 Jan 2021 10:38:31 +0100
Subject: [PATCH 37/54] [minor] Change setup.py

Change to follow the official doc: https://packaging.python.org/tutorials/packaging-projects/
---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index c8bc9ba..e07c722 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 # Authors: CommPy contributors
 # License: BSD 3-Clause
 
-from distutils.core import setup
+from setuptools import setup
 
 # Taken from scikit-learn setup.py
 DISTNAME = 'scikit-commpy'

From aaedc5f99fd075209ae21781e0d0f415c356a28e Mon Sep 17 00:00:00 2001
From: "Liu, Andrew Z" <liu.andrew@vast-inc.com>
Date: Thu, 18 Feb 2021 11:04:44 -0500
Subject: [PATCH 38/54] Bug fix for ldpc_bp_decode

`out_llrs` array was being overwritten by the output LLR values of a single
block. This has been fixed by writing the output LLRs of each block to their
corresponding position within the `out_llrs` array
---
 commpy/channelcoding/ldpc.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/commpy/channelcoding/ldpc.py b/commpy/channelcoding/ldpc.py
index 6913223..b3005e2 100644
--- a/commpy/channelcoding/ldpc.py
+++ b/commpy/channelcoding/ldpc.py
@@ -244,8 +244,8 @@ def ldpc_bp_decode(llr_vec, ldpc_code_params, decoder_algorithm, n_iters):
             message_matrix.data *= -1
             message_matrix.data += parity_check_matrix.multiply(msg_sum + llr_vec[i_start:i_stop]).data
 
-            out_llrs = msg_sum + llr_vec[i_start:i_stop]
-            np.signbit(out_llrs, out=dec_word[i_start:i_stop])
+            out_llrs[i_start:i_stop] = msg_sum + llr_vec[i_start:i_stop]
+            np.signbit(out_llrs[i_start:i_stop], out=dec_word[i_start:i_stop])
 
     # Reformat outputs
     n_blocks = llr_vec.size // ldpc_code_params['n_vnodes']

From 795dbb73cc85928ff32f0927beb434111d88b91c Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Fri, 19 Feb 2021 18:15:29 +0100
Subject: [PATCH 39/54] Add Gray code support for Modems

Close veeresht/CommPy#100 and veeresht/CommPy#60
---
 commpy/modulation.py | 31 ++++++++++++++++---------------
 requirements.txt     |  1 +
 2 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/commpy/modulation.py b/commpy/modulation.py
index 5389daf..409dbe0 100644
--- a/commpy/modulation.py
+++ b/commpy/modulation.py
@@ -21,14 +21,14 @@
 
 """
 from bisect import insort
-from itertools import product
 
 import matplotlib.pyplot as plt
-from numpy import arange, array, zeros, pi, cos, sin, sqrt, log2, argmin, \
+from numpy import arange, array, zeros, pi, sqrt, log2, argmin, \
     hstack, repeat, tile, dot, shape, concatenate, exp, \
-    log, vectorize, empty, eye, kron, inf, full, abs, newaxis, minimum, clip
+    log, vectorize, empty, eye, kron, inf, full, abs, newaxis, minimum, clip, fromiter
 from numpy.fft import fft, ifft
 from numpy.linalg import qr, norm
+from sympy.combinatorics.graycode import GrayCode
 
 from commpy.utilities import bitarray2dec, dec2bitarray, signal_power
 
@@ -65,10 +65,16 @@ class Modem:
                         If the constellation is changed to an array-like with length that is not a power of 2.
         """
 
-    def __init__(self, constellation):
+    def __init__(self, constellation, reorder_as_gray=True):
         """ Creates a custom Modem object. """
 
-        self.constellation = constellation
+        if reorder_as_gray:
+            m = log2(len(constellation))
+            gray_code_sequence = GrayCode(m).generate_gray()
+            gray_code_sequence_array = fromiter((int(g, 2) for g in gray_code_sequence), int, len(constellation))
+            self.constellation = constellation[gray_code_sequence_array.argsort()]
+        else:
+            self.constellation = constellation
 
     def modulate(self, input_bits):
         """ Modulate (map) an array of bits to constellation symbols.
@@ -197,10 +203,7 @@ class PSKModem(Modem):
     def __init__(self, m):
         """ Creates a Phase Shift Keying (PSK) Modem object. """
 
-        def _constellation_symbol(i):
-            return cos(2 * pi * (i - 1) / m) + sin(2 * pi * (i - 1) / m) * (0 + 1j)
-
-        self.constellation = list(map(_constellation_symbol, arange(m)))
+        super().__init__(exp(1j * arange(0, 2 * pi, 2 * pi / m)))
 
 
 class QAMModem(Modem):
@@ -241,12 +244,10 @@ def __init__(self, m):
 
         """
 
-        def _constellation_symbol(i):
-            return (2 * i[0] - 1) + (2 * i[1] - 1) * (1j)
-
-        mapping_array = arange(1, sqrt(m) + 1) - (sqrt(m) / 2)
-        self.constellation = list(map(_constellation_symbol,
-                                      list(product(mapping_array, repeat=2))))
+        num_symb_pam = int(sqrt(m))
+        pam = arange(-num_symb_pam + 1, num_symb_pam, 2)
+        constellation = tile(hstack((pam, pam[::-1])), num_symb_pam // 2) * 1j + pam.repeat(num_symb_pam)
+        super().__init__(constellation)
 
 
 def ofdm_tx(x, nfft, nsc, cp_length):
diff --git a/requirements.txt b/requirements.txt
index 4991962..731c11c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@ numpy>=1.9.2
 scipy>=0.15.0
 matplotlib>=1.4.3
 nose>=1.3.4
+sympy>=1.7.1

From 57cea99b2f6f79d56c4dd36d07397323a55a1a48 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Fri, 19 Feb 2021 18:29:27 +0100
Subject: [PATCH 40/54] Add ValueErrors when the symbols is not acceptable.

---
 commpy/modulation.py | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/commpy/modulation.py b/commpy/modulation.py
index 409dbe0..b4a4f5c 100644
--- a/commpy/modulation.py
+++ b/commpy/modulation.py
@@ -203,6 +203,10 @@ class PSKModem(Modem):
     def __init__(self, m):
         """ Creates a Phase Shift Keying (PSK) Modem object. """
 
+        num_bits_symbol = log2(m)
+        if num_bits_symbol != int(num_bits_symbol):
+            raise ValueError('Constellation length must be a power of 2.')
+
         super().__init__(exp(1j * arange(0, 2 * pi, 2 * pi / m)))
 
 
@@ -232,6 +236,7 @@ class QAMModem(Modem):
         ------
         ValueError
                         If the constellation is changed to an array-like with length that is not a power of 2.
+                        If the parameter m would lead to an non-square QAM during initialization.
     """
 
     def __init__(self, m):
@@ -240,13 +245,20 @@ def __init__(self, m):
         Parameters
         ----------
         m : int
-            Size of the QAM constellation.
+            Size of the QAM constellation. Must lead to a square QAM (ie sqrt(m) is an integer).
 
+        Raises
+        ------
+        ValueError
+                        If m would lead to an non-square QAM.
         """
 
-        num_symb_pam = int(sqrt(m))
+        num_symb_pam = sqrt(m)
+        if num_symb_pam != int(num_symb_pam):
+            raise ValueError('m must lead to a square QAM.')
+
         pam = arange(-num_symb_pam + 1, num_symb_pam, 2)
-        constellation = tile(hstack((pam, pam[::-1])), num_symb_pam // 2) * 1j + pam.repeat(num_symb_pam)
+        constellation = tile(hstack((pam, pam[::-1])), int(num_symb_pam) // 2) * 1j + pam.repeat(num_symb_pam)
         super().__init__(constellation)
 
 

From ddbc6c4684f9a57770b754e2cde5db148e801010 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Fri, 19 Feb 2021 20:09:51 +0100
Subject: [PATCH 41/54] Add sympy to travis.yml

---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 4afe9c3..a989699 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,7 +23,7 @@ install:
     - conda info -a
 
     # Replace dep1 dep2 ... with your dependencies
-    - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib nose
+    - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy matplotlib nose sympy
     - source activate test-environment
     #- conda install --yes -c dan_blanchard python-coveralls nose-cov
     - pip install coveralls

From c6b3bd0531dfeb33c0030ca6bb6a5c96105d1999 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Sat, 20 Feb 2021 19:41:32 +0100
Subject: [PATCH 42/54] Fix Travis error for veeresht/CommPy#102 & Modulation
 tests

---
 commpy/modulation.py            |  2 +-
 commpy/tests/test_modulation.py | 34 +++++++++++++++++++++++++++++++--
 2 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/commpy/modulation.py b/commpy/modulation.py
index b4a4f5c..4f90a52 100644
--- a/commpy/modulation.py
+++ b/commpy/modulation.py
@@ -72,7 +72,7 @@ def __init__(self, constellation, reorder_as_gray=True):
             m = log2(len(constellation))
             gray_code_sequence = GrayCode(m).generate_gray()
             gray_code_sequence_array = fromiter((int(g, 2) for g in gray_code_sequence), int, len(constellation))
-            self.constellation = constellation[gray_code_sequence_array.argsort()]
+            self.constellation = array(constellation)[gray_code_sequence_array.argsort()]
         else:
             self.constellation = constellation
 
diff --git a/commpy/tests/test_modulation.py b/commpy/tests/test_modulation.py
index 3dfc663..ab2057c 100644
--- a/commpy/tests/test_modulation.py
+++ b/commpy/tests/test_modulation.py
@@ -3,16 +3,21 @@
 
 from itertools import product
 
-from numpy import zeros, identity, arange, concatenate, log2, array, inf
+from numpy import zeros, identity, arange, concatenate, log2, log10, array, inf, sqrt, sin, pi
 from numpy.random import seed
 from numpy.testing import run_module_suite, assert_allclose, dec, assert_raises, assert_array_equal
+from scipy.special import erf
 
-from commpy.channels import MIMOFlatChannel
+from commpy.channels import MIMOFlatChannel, SISOFlatChannel
 from commpy.links import *
 from commpy.modulation import QAMModem, mimo_ml, bit_lvl_repr, max_log_approx, PSKModem, Modem
 from commpy.utilities import signal_power
 
 
+def Qfunc(x):
+    return 0.5 - 0.5 * erf(x / sqrt(2))
+
+
 @dec.slow
 def test_bit_lvl_repr():
     # Set seed
@@ -124,8 +129,33 @@ def do_custom(self, modem):
         pass
 
 
+@dec.slow
 class TestModulateHardDemodulate(ModemTestcase):
 
+    @staticmethod
+    def check_BER(modem, EbN0dB, BERs_expected):
+        seed(8071996)
+        model = LinkModel(modem.modulate,
+                          SISOFlatChannel(fading_param=(1 + 0j, 0)),
+                          lambda y, _, __, ___: modem.demodulate(y, 'hard'),
+                          modem.num_bits_symbol, modem.constellation, modem.Es)
+        BERs = model.link_performance(EbN0dB + 10 * log10(log2(modem.m)), 5e5, 400, 720)
+        assert_allclose(BERs, BERs_expected, atol=1e-4, rtol=.1,
+                        err_msg='Wrong BER for {}-PSK modulation'.format(modem.m))
+
+    def do_qam(self, modem):
+        EbN0dB = arange(8, 25, 4)
+        nb_pam = sqrt(modem.m)
+        BERs_expected = 2 * (1 - 1 / nb_pam) / log2(nb_pam) * \
+                        Qfunc(sqrt(3 * log2(nb_pam) / (nb_pam ** 2 - 1) * (2 * 10 ** (EbN0dB / 10))))
+        self.check_BER(modem, EbN0dB, BERs_expected)
+
+    def do_psk(self, modem):
+        EbN0dB = arange(15, 25, 4)
+        SERs_expected = 2 * Qfunc(sqrt(2 * modem.num_bits_symbol * 10 ** (EbN0dB / 10)) * sin(pi / modem.m))
+        BERs_expected = SERs_expected / modem.num_bits_symbol
+        self.check_BER(modem, EbN0dB, BERs_expected)
+
     def do(self, modem):
         for bits in product(*((0, 1),) * modem.num_bits_symbol):
             assert_array_equal(bits, modem.demodulate(modem.modulate(bits), 'hard'),

From 055ae3aa501554372cec4632ec731f8c8da02068 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Sat, 20 Feb 2021 19:43:32 +0100
Subject: [PATCH 43/54] [minor] Typo in comment

---
 commpy/tests/test_modulation.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/tests/test_modulation.py b/commpy/tests/test_modulation.py
index ab2057c..ba158da 100644
--- a/commpy/tests/test_modulation.py
+++ b/commpy/tests/test_modulation.py
@@ -141,7 +141,7 @@ def check_BER(modem, EbN0dB, BERs_expected):
                           modem.num_bits_symbol, modem.constellation, modem.Es)
         BERs = model.link_performance(EbN0dB + 10 * log10(log2(modem.m)), 5e5, 400, 720)
         assert_allclose(BERs, BERs_expected, atol=1e-4, rtol=.1,
-                        err_msg='Wrong BER for {}-PSK modulation'.format(modem.m))
+                        err_msg='Wrong BER for a standard modulation with {} symbols'.format(modem.m))
 
     def do_qam(self, modem):
         EbN0dB = arange(8, 25, 4)

From 1e0a04600c671a7e55e3322a127da35c1a54f039 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Thu, 25 Mar 2021 15:45:47 +0100
Subject: [PATCH 44/54] Remove test_wifi80211.py that was based on unreliable
 previous tests.

---
 commpy/tests/test_modulation.py |  6 ++---
 commpy/tests/test_wifi80211.py  | 47 ---------------------------------
 2 files changed, 3 insertions(+), 50 deletions(-)
 delete mode 100644 commpy/tests/test_wifi80211.py

diff --git a/commpy/tests/test_modulation.py b/commpy/tests/test_modulation.py
index ba158da..52d5780 100644
--- a/commpy/tests/test_modulation.py
+++ b/commpy/tests/test_modulation.py
@@ -145,9 +145,9 @@ def check_BER(modem, EbN0dB, BERs_expected):
 
     def do_qam(self, modem):
         EbN0dB = arange(8, 25, 4)
-        nb_pam = sqrt(modem.m)
-        BERs_expected = 2 * (1 - 1 / nb_pam) / log2(nb_pam) * \
-                        Qfunc(sqrt(3 * log2(nb_pam) / (nb_pam ** 2 - 1) * (2 * 10 ** (EbN0dB / 10))))
+        nb_symb_pam = sqrt(modem.m)
+        BERs_expected = 2 * (1 - 1 / nb_symb_pam) / log2(nb_symb_pam) * \
+                        Qfunc(sqrt(3 * log2(nb_symb_pam) / (nb_symb_pam ** 2 - 1) * (2 * 10 ** (EbN0dB / 10))))
         self.check_BER(modem, EbN0dB, BERs_expected)
 
     def do_psk(self, modem):
diff --git a/commpy/tests/test_wifi80211.py b/commpy/tests/test_wifi80211.py
deleted file mode 100644
index 88a6095..0000000
--- a/commpy/tests/test_wifi80211.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Authors: CommPy contributors
-# License: BSD 3-Clause
-
-from __future__ import division  # Python 2 compatibility
-
-from numpy import arange, log10
-from numpy.random import seed
-from numpy.testing import run_module_suite, dec, assert_allclose
-
-from commpy.channels import MIMOFlatChannel, SISOFlatChannel
-from commpy.modulation import kbest
-from commpy.wifi80211 import Wifi80211
-
-
-@dec.slow
-def test_wifi80211_siso_channel():
-    seed(17121996)
-    wifi80211 = Wifi80211(1)
-    BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)[0]
-    desired = (0.548, 0.508, 0.59, 0.81, 0.18)  # From previous tests
-    # for i, val in enumerate(desired):
-    #     print((BERs[i] - val) / val)
-    assert_allclose(BERs, desired, rtol=0.3,
-                    err_msg='Wrong performance for SISO QPSK and AWGN channel')
-
-
-@dec.slow
-def test_wifi80211_mimo_channel():
-    seed(17121996)
-    # Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
-    wifi80211 = Wifi80211(3)
-    RayleighChannel = MIMOFlatChannel(4, 4)
-    RayleighChannel.uncorr_rayleigh_fading(complex)
-    modem = wifi80211.get_modem()
-
-    def receiver(y, h, constellation, noise_var):
-        return modem.demodulate(kbest(y, h, constellation, 16), 'hard')
-
-    BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5) + 10 * log10(modem.num_bits_symbol), 10 ** 4,
-                                      600, receiver=receiver)[0]
-    desired = (0.535, 0.508, 0.521, 0.554, 0.475)  # From previous test
-    assert_allclose(BERs, desired, rtol=1.25,
-                    err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
-
-
-if __name__ == "__main__":
-    run_module_suite()

From c373d797adf282d0a561bbbadfd5f4eca56582a8 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Thu, 25 Mar 2021 15:50:46 +0100
Subject: [PATCH 45/54] Add sympy to the requirements in the README

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index fe6164d..e38358b 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,7 @@ Requirements/Dependencies
 - scipy 0.15 or above
 - matplotlib 1.4 or above
 - nose 1.3 or above
+- sympy 1.7 or above
 
 Installation
 ------------

From aab0f22b57bfbccee95f44b78108824feee3af91 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Mon, 21 Jun 2021 21:09:39 +0200
Subject: [PATCH 46/54] setup.py for v0.7 release

---
 setup.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index e07c722..501482e 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
 MAINTAINER_EMAIL = 'veeresht@gmail.com'
 URL = 'http://veeresht.github.com/CommPy'
 LICENSE = 'BSD 3-Clause'
-VERSION = '0.6.0'
+VERSION = '0.7.0'
 
 #This is a list of files to install, and where
 #(relative to the 'root' dir, where setup.py is)
@@ -35,6 +35,7 @@
           'numpy',
           'scipy',
           'matplotlib',
+          'sympy'
     ],
     #'package' package must contain files (see list above)
     #This dict maps the package name =to=> directories

From 63152680c0792b6c988e962cf6d9b5798eed595b Mon Sep 17 00:00:00 2001
From: Edson Porto da Silva <edsonporto88@gmail.com>
Date: Mon, 16 Aug 2021 15:45:03 -0300
Subject: [PATCH 47/54] update setup.py

---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index e07c722..b03475c 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 # Taken from scikit-learn setup.py
 DISTNAME = 'scikit-commpy'
 DESCRIPTION = 'Digital Communication Algorithms with Python'
-LONG_DESCRIPTION = open('README.md').read()
+LONG_DESCRIPTION = open('README.md', encoding="utf8").read()
 MAINTAINER = 'Veeresh Taranalli & Bastien Trotobas'
 MAINTAINER_EMAIL = 'veeresht@gmail.com'
 URL = 'http://veeresht.github.com/CommPy'

From ef6517ba71ccd99097aa13bf4d9b153b46626f82 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Tue, 21 Sep 2021 11:16:38 +0200
Subject: [PATCH 48/54] Typo in specular_compo

---
 commpy/channels.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/channels.py b/commpy/channels.py
index 416a901..b58b1cc 100644
--- a/commpy/channels.py
+++ b/commpy/channels.py
@@ -449,7 +449,7 @@ def specular_compo(self, thetat, dt, thetar, dr):
         H = zeros((self.nb_rx, self.nb_tx), dtype=complex)
         for n in range(self.nb_rx):
             for m in range(self.nb_tx):
-                H[n, m] = exp(1j * 2 * pi * (n * dr * cos(thetar) - m * dt * cos(thetat)))
+                H[n, m] = exp(1j * 2 * pi * (n * dr * cos(thetar) + m * dt * cos(thetat)))
         return H
 
     @property

From e9aa296b398605e81e582dcf8c08e0ebb4515b8d Mon Sep 17 00:00:00 2001
From: Michele Sardo <37845722+msmttchr@users.noreply.github.com>
Date: Tue, 11 Jan 2022 16:08:22 +0100
Subject: [PATCH 49/54] Replaced double quote chars

This fix allows successfully install in Windows
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e38358b..5b31cd7 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ Citing CommPy
 -------------
 If you use CommPy for a publication, presentation or a demo, a citation would be greatly appreciated. A citation example is presented here and we suggest to had the revision or version number and the date:
 
-V. Taranalli, B. Trotobas, and contributors, “CommPy: Digital Communication with Python”. [Online]. Available: github.com/veeresht/CommPy
+V. Taranalli, B. Trotobas, and contributors, "CommPy: Digital Communication with Python". [Online]. Available: github.com/veeresht/CommPy
 
 
 I would also greatly appreciate your feedback if you have found CommPy useful. Just send me a mail: veeresht@gmail.com

From 719a24494046d500611bb75dee2156ce6d91289b Mon Sep 17 00:00:00 2001
From: Jon McGee <jmcgee@weezing.office.geontechnologies.com>
Date: Tue, 22 Mar 2022 10:39:34 -0400
Subject: [PATCH 50/54] Separated the string of directories for the files list
 so that each individual directory is added including the txt files in
 gallager/wimax

---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index 501482e..d1dd6de 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@
 #This is a list of files to install, and where
 #(relative to the 'root' dir, where setup.py is)
 #You could be more specific.
-files = ["channelcoding/*, channelcoding/tests/*, tests/*, channelcoding/designs/ldpc/gallager/*, channelcoding/designs/ldpc/wimax/*"]
+files = ["channelcoding/*", "channelcoding/tests/*", "tests/*", "channelcoding/designs/ldpc/gallager/*", "channelcoding/designs/ldpc/wimax/*"]
 
 setup(
     name=DISTNAME,

From 96beca170cff7547711405efda1ae9dc22f19ba0 Mon Sep 17 00:00:00 2001
From: Aaron Holtzman <77404441+aholtzma-am@users.noreply.github.com>
Date: Thu, 31 Mar 2022 16:41:58 -0400
Subject: [PATCH 51/54] Remove usage of deprecated np.complex

np.complex is just an alias to python builtin `complex` and is deprecated as of numpy 1.20.
---
 commpy/channels.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/channels.py b/commpy/channels.py
index 416a901..8481f3f 100644
--- a/commpy/channels.py
+++ b/commpy/channels.py
@@ -19,7 +19,7 @@
 
 from __future__ import division, print_function  # Python 2 compatibility
 
-from numpy import complex, abs, sqrt, sum, zeros, identity, hstack, einsum, trace, kron, absolute, fromiter, array, exp, \
+from numpy import abs, sqrt, sum, zeros, identity, hstack, einsum, trace, kron, absolute, fromiter, array, exp, \
     pi, cos
 from numpy.random import randn, random, standard_normal
 from scipy.linalg import sqrtm

From 87403159aa03cf8208e0d025a14c247bfc90857b Mon Sep 17 00:00:00 2001
From: Leo <oliveiraleo@users.noreply.github.com>
Date: Wed, 6 Apr 2022 11:45:28 -0300
Subject: [PATCH 52/54] Updates the reference URL at the end of the README file

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 5b31cd7..e3b47a3 100644
--- a/README.md
+++ b/README.md
@@ -131,4 +131,4 @@ V. Taranalli, B. Trotobas, and contributors, "CommPy: Digital Communication with
 
 I would also greatly appreciate your feedback if you have found CommPy useful. Just send me a mail: veeresht@gmail.com
 
-For more details on CommPy, please visit http://veeresht.github.com/CommPy
+For more details on CommPy, please visit https://veeresht.info/CommPy/

From d92ecbae618a72abd8f6f6cfbb8860d95e4c7713 Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@centralesupelec.fr>
Date: Tue, 21 Sep 2021 11:16:38 +0200
Subject: [PATCH 53/54] Typo in specular_compo

---
 commpy/channels.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/commpy/channels.py b/commpy/channels.py
index 8481f3f..d374755 100644
--- a/commpy/channels.py
+++ b/commpy/channels.py
@@ -449,7 +449,7 @@ def specular_compo(self, thetat, dt, thetar, dr):
         H = zeros((self.nb_rx, self.nb_tx), dtype=complex)
         for n in range(self.nb_rx):
             for m in range(self.nb_tx):
-                H[n, m] = exp(1j * 2 * pi * (n * dr * cos(thetar) - m * dt * cos(thetat)))
+                H[n, m] = exp(1j * 2 * pi * (n * dr * cos(thetar) + m * dt * cos(thetat)))
         return H
 
     @property

From e5806967cd7e00688942da2655b55541213aec3f Mon Sep 17 00:00:00 2001
From: BastienTr <bastien.trotobas@ens-rennes.fr>
Date: Mon, 10 Oct 2022 09:47:12 +0200
Subject: [PATCH 54/54] Ready for v0.8 pip update

---
 commpy/channelcoding/ldpc.py | 7 ++++---
 setup.py                     | 7 ++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/commpy/channelcoding/ldpc.py b/commpy/channelcoding/ldpc.py
index b3005e2..b8fe73c 100644
--- a/commpy/channelcoding/ldpc.py
+++ b/commpy/channelcoding/ldpc.py
@@ -304,7 +304,7 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
     Encode bits using the LDPC code specified. If the  generator matrix is not computed, this function will build it
     and add it to the dictionary. It will also add the parity check matrix.
 
-    This function work only for LDPC specified by a triangular parity check matrix.
+    This function work only for LDPC specified by a approximate triangular parity check matrix.
 
     Parameters
     ----------
@@ -312,7 +312,8 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
         Message bit to encode.
 
     ldpc_code_params : dictionary that at least contains one of these options:
-        Option 1: generator matrix is available.
+        Option 1: generator matrix and parity-check matrix are available.
+                parity_check_matrix (CSC sparse matrix of int8) - parity check matrix.
                 generator_matrix (2D-array or sparse matrix) - generator matrix of the code.
         Option 2: generator and parity check matrices will be added as sparse matrices.
                 n_vnodes (int) - number of variable nodes.
@@ -337,7 +338,7 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
             If the message length is not a multiple of block length and pad is False.
     """
 
-    if ldpc_code_params.get('generator_matrix') is None:
+    if ldpc_code_params.get('generator_matrix') is None or ldpc_code_params.get('parity_check_matrix') is None:
         build_matrix(ldpc_code_params)
 
     block_length = ldpc_code_params['generator_matrix'].shape[1]
diff --git a/setup.py b/setup.py
index d1dd6de..7d3d2ce 100644
--- a/setup.py
+++ b/setup.py
@@ -6,12 +6,12 @@
 # Taken from scikit-learn setup.py
 DISTNAME = 'scikit-commpy'
 DESCRIPTION = 'Digital Communication Algorithms with Python'
-LONG_DESCRIPTION = open('README.md').read()
+LONG_DESCRIPTION = open('README.md', encoding="utf8").read()
 MAINTAINER = 'Veeresh Taranalli & Bastien Trotobas'
-MAINTAINER_EMAIL = 'veeresht@gmail.com'
+MAINTAINER_EMAIL = 'bastien.trotobas@gmail.com'
 URL = 'http://veeresht.github.com/CommPy'
 LICENSE = 'BSD 3-Clause'
-VERSION = '0.7.0'
+VERSION = '0.8.0'
 
 #This is a list of files to install, and where
 #(relative to the 'root' dir, where setup.py is)
@@ -53,6 +53,7 @@
         'Intended Audience :: Science/Research',
         'Intended Audience :: Telecommunications Industry',
         'Operating System :: Unix',
+        'Operating System :: Microsoft :: Windows',
         'Programming Language :: Python',
         'Topic :: Scientific/Engineering',
         'Topic :: Software Development',