Skip to content

Commit 169b3d3

Browse files
authored
Merge pull request #81
Add Wifi 802.11 physical simulation
2 parents 8687174 + 78c6e25 commit 169b3d3

File tree

8 files changed

+425
-9
lines changed

8 files changed

+425
-9
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ Available Features
3737
- Binary Symmetric Channel (BSC)
3838
- Binary AWGN Channel (BAWGNC)
3939

40+
[Wifi 802.11 Simulation Class](https://github.com/veeresht/CommPy/blob/master/commpy/wifi80211.py)
41+
- A class to simulate the transmissions and receiving parameters of physical layer 802.11 (currently till VHT (ac))
42+
4043
[Filters](https://github.com/veeresht/CommPy/blob/master/commpy/filters.py)
4144
-------
4245
- Rectangular

THANKS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Please add names as needed so that we can keep up with all the contributors.
22

33
Veeresh Taranalli for initial creation and contribution of CommPy.
44
Bastien Trotobas for adding some features, bugfixes, maintenance.
5+
Eduardo Soares (@eSoares) for several enhancements including WiFi 802.11 class and code speedups.
56
Vladimir Fadeev for bugfixes, addition of convolutional code puncturing and readme explanation of codings.
67
Youness Akourim for adding features and fixing some bugs.
78
Rey Tucker for python3 compatibility fixes.

commpy/channelcoding/convcode.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,8 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
747747

748748
return decoded_bits[:L]
749749

750-
def puncturing(message, punct_vec):
750+
751+
def puncturing(message: np.ndarray, punct_vec: np.ndarray) -> np.ndarray:
751752
"""
752753
Applying of the punctured procedure.
753754
Parameters
@@ -771,7 +772,8 @@ def puncturing(message, punct_vec):
771772
shift = shift + 1
772773
return np.array(punctured)
773774

774-
def depuncturing(punctured, punct_vec, shouldbe):
775+
776+
def depuncturing(punctured: np.ndarray, punct_vec: np.ndarray, shouldbe: int) -> np.ndarray:
775777
"""
776778
Applying of the inserting zeros procedure.
777779
Parameters
@@ -797,5 +799,5 @@ def depuncturing(punctured, punct_vec, shouldbe):
797799
else:
798800
shift2 = shift2 + 1
799801
if idx%N == 0:
800-
shift = shift + 1;
802+
shift = shift + 1
801803
return depunctured
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Authors: CommPy contributors
2+
# License: BSD 3-Clause
3+
4+
import math
5+
6+
import matplotlib.pyplot as plt
7+
import numpy as np
8+
9+
import commpy.channels as chan
10+
# ==================================================================================================
11+
# Complete example using Commpy Wifi 802.11 physical parameters
12+
# ==================================================================================================
13+
from commpy.wifi80211 import Wifi80211
14+
15+
# AWGN channel
16+
channels = chan.SISOFlatChannel(None, (1 + 0j, 0j))
17+
18+
w2 = Wifi80211(mcs=2)
19+
w3 = Wifi80211(mcs=3)
20+
21+
# SNR range to test
22+
SNRs2 = np.arange(0, 6) + 10 * math.log10(w2.get_modem().num_bits_symbol)
23+
SNRs3 = np.arange(0, 6) + 10 * math.log10(w3.get_modem().num_bits_symbol)
24+
25+
BERs_mcs2 = w2.link_performance(channels, SNRs2, 10, 10, 600, stop_on_surpass_error=False)
26+
BERs_mcs3 = w3.link_performance(channels, SNRs3, 10, 10, 600, stop_on_surpass_error=False)
27+
28+
# Test
29+
plt.semilogy(SNRs2, BERs_mcs2, 'o-', SNRs3, BERs_mcs3, 'o-')
30+
plt.grid()
31+
plt.xlabel('Signal to Noise Ration (dB)')
32+
plt.ylabel('Bit Error Rate')
33+
plt.legend(('MCS 2', 'MCS 3'))
34+
plt.show()

commpy/links.py

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
5858
BERs : 1d ndarray
5959
Estimated Bit Error Ratio corresponding to each SNRs
6060
"""
61-
61+
if not send_chunk:
62+
send_chunk = err_min
6263
return link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate)
6364

6465

@@ -133,7 +134,7 @@ class LinkModel:
133134
*Default* is 1.
134135
"""
135136

136-
def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1):
137+
def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1.):
137138
self.modulate = modulate
138139
self.channel = channel
139140
self.receive = receive
@@ -146,8 +147,10 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
146147
self.decoder = lambda msg: msg
147148
else:
148149
self.decoder = decoder
150+
self.full_simulation_results = None
149151

150-
def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
152+
def link_performance_full_metrics(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
153+
number_chunks_per_send=1, stop_on_surpass_error=True):
151154
"""
152155
Estimate the BER performance of a link model with Monte Carlo simulation.
153156
@@ -157,8 +160,8 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
157160
Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
158161
where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
159162
160-
send_max : int
161-
Maximum number of bits send for each SNR.
163+
tx_max : int
164+
Maximum number of transmissions for each SNR.
162165
163166
err_min : int
164167
link_performance send bits until it reach err_min errors (see also send_max).
@@ -172,6 +175,110 @@ def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1
172175
Rate of the used code.
173176
*Default*: 1 i.e. no code.
174177
178+
number_chunks_per_send : int
179+
Number of chunks per transmission
180+
181+
stop_on_surpass_error : bool
182+
Controls if during simulation of a SNR it should break and move to the next SNR when
183+
the bit error is above the err_min parameter
184+
185+
Returns
186+
-------
187+
List[BERs, BEs, CEs, NCs]
188+
BERs : 1d ndarray
189+
Estimated Bit Error Ratio corresponding to each SNRs
190+
BEs : 2d ndarray
191+
Number of Estimated Bits with Error per transmission corresponding to each SNRs
192+
CEs : 2d ndarray
193+
Number of Estimated Chunks with Errors per transmission corresponding to each SNRs
194+
NCs : 2d ndarray
195+
Number of Chunks transmitted per transmission corresponding to each SNRs
196+
"""
197+
198+
# Initialization
199+
BERs = np.zeros_like(SNRs, dtype=float)
200+
BEs = np.zeros((len(SNRs), tx_max), dtype=int) # Bit errors per tx
201+
CEs = np.zeros((len(SNRs), tx_max), dtype=int) # Chunk Errors per tx
202+
NCs = np.zeros((len(SNRs), tx_max), dtype=int) # Number of Chunks per tx
203+
# Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
204+
if send_chunk is None:
205+
send_chunk = err_min
206+
divider = self.num_bits_symbol * self.channel.nb_tx
207+
send_chunk = max(divider, send_chunk // divider * divider)
208+
209+
receive_size = self.channel.nb_tx * self.num_bits_symbol
210+
full_args_decoder = len(getfullargspec(self.decoder).args) > 1
211+
212+
# Computations
213+
for id_SNR in range(len(SNRs)):
214+
self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
215+
total_tx_send = 0
216+
bit_err = np.zeros(tx_max, dtype=int)
217+
chunk_loss = np.zeros(tx_max, dtype=int)
218+
chunk_count = np.zeros(tx_max, dtype=int)
219+
for id_tx in range(tx_max):
220+
if stop_on_surpass_error and bit_err.sum() > err_min:
221+
break
222+
# Propagate some bits
223+
msg = np.random.choice((0, 1), send_chunk * number_chunks_per_send)
224+
symbs = self.modulate(msg)
225+
channel_output = self.channel.propagate(symbs)
226+
227+
# Deals with MIMO channel
228+
if isinstance(self.channel, MIMOFlatChannel):
229+
nb_symb_vector = len(channel_output)
230+
received_msg = np.empty(int(math.ceil(len(msg) / self.rate)), dtype=np.int8)
231+
for i in range(nb_symb_vector):
232+
received_msg[receive_size * i:receive_size * (i + 1)] = \
233+
self.receive(channel_output[i], self.channel.channel_gains[i],
234+
self.constellation, self.channel.noise_std ** 2)
235+
else:
236+
received_msg = self.receive(channel_output, self.channel.channel_gains,
237+
self.constellation, self.channel.noise_std ** 2)
238+
# Count errors
239+
if full_args_decoder:
240+
decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
241+
self.constellation, self.channel.noise_std ** 2,
242+
received_msg, self.channel.nb_tx * self.num_bits_symbol)
243+
else:
244+
decoded_bits = self.decoder(received_msg)
245+
# calculate number of error frames
246+
for i in range(number_chunks_per_send):
247+
errors = np.bitwise_xor(msg[send_chunk * i:send_chunk * (i + 1)],
248+
decoded_bits[send_chunk * i:send_chunk * (i + 1)]).sum()
249+
bit_err[id_tx] += errors
250+
chunk_loss[id_tx] += 1 if errors > 0 else 0
251+
252+
chunk_count[id_tx] += number_chunks_per_send
253+
total_tx_send += 1
254+
BERs[id_SNR] = bit_err.sum() / (total_tx_send * send_chunk)
255+
BEs[id_SNR] = bit_err
256+
CEs[id_SNR] = np.where(bit_err > 0, 1, 0)
257+
NCs[id_SNR] = chunk_count
258+
if BEs[id_SNR].sum() < err_min:
259+
break
260+
self.full_simulation_results = BERs, BEs, CEs, NCs
261+
return BERs, BEs, CEs, NCs
262+
263+
def link_performance(self, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
264+
"""
265+
Estimate the BER performance of a link model with Monte Carlo simulation.
266+
Parameters
267+
----------
268+
SNRs : 1D arraylike
269+
Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
270+
where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
271+
send_max : int
272+
Maximum number of bits send for each SNR.
273+
err_min : int
274+
link_performance send bits until it reach err_min errors (see also send_max).
275+
send_chunk : int
276+
Number of bits to be send at each iteration. This is also the frame length of the decoder if available
277+
so it should be large enough regarding the code type.
278+
*Default*: send_chunck = err_min
279+
code_rate : float in (0,1]
280+
Rate of the used code.
281+
*Default*: 1 i.e. no code.
175282
Returns
176283
-------
177284
BERs : 1d ndarray

commpy/tests/test_links.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ def test_link_performance():
2323

2424
def receiver(y, h, constellation, noise_var):
2525
return QPSK.demodulate(y, 'hard')
26+
2627
model = LinkModel(QPSK.modulate, SISOFlatChannel(fading_param=(1 + 0j, 0)), receiver,
2728
QPSK.num_bits_symbol, QPSK.constellation, QPSK.Es)
2829

2930
BERs = link_performance(model, range(0, 9, 2), 600e4, 600)
30-
desired = erfc(sqrt(10**(arange(0, 9, 2) / 10) / 2)) / 2
31+
desired = erfc(sqrt(10 ** (arange(0, 9, 2) / 10) / 2)) / 2
3132
assert_allclose(BERs, desired, rtol=0.25,
3233
err_msg='Wrong performance for SISO QPSK and AWGN channel')
34+
full_metrics = model.link_performance_full_metrics(range(0, 9, 2), 1000, 600)
35+
assert_allclose(full_metrics[0], desired, rtol=0.25,
36+
err_msg='Wrong performance for SISO QPSK and AWGN channel')
3337

3438
# Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
3539
QAM16 = QAMModem(16)
@@ -38,6 +42,7 @@ def receiver(y, h, constellation, noise_var):
3842

3943
def receiver(y, h, constellation, noise_var):
4044
return QAM16.demodulate(kbest(y, h, constellation, 16), 'hard')
45+
4146
model = LinkModel(QAM16.modulate, RayleighChannel, receiver,
4247
QAM16.num_bits_symbol, QAM16.constellation, QAM16.Es)
4348
SNRs = arange(0, 21, 5) + 10 * log10(QAM16.num_bits_symbol)
@@ -46,6 +51,9 @@ def receiver(y, h, constellation, noise_var):
4651
desired = (2e-1, 1e-1, 3e-2, 2e-3, 4e-5) # From reference
4752
assert_allclose(BERs, desired, rtol=1.25,
4853
err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
54+
full_metrics = model.link_performance_full_metrics(SNRs, 1000, 600)
55+
assert_allclose(full_metrics[0], desired, rtol=1.25,
56+
err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
4957

5058

5159
if __name__ == "__main__":

commpy/tests/test_wifi80211.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Authors: CommPy contributors
2+
# License: BSD 3-Clause
3+
4+
from __future__ import division # Python 2 compatibility
5+
6+
from numpy import arange, log10
7+
from numpy.random import seed
8+
from numpy.testing import run_module_suite, dec, assert_allclose
9+
10+
from commpy.channels import MIMOFlatChannel, SISOFlatChannel
11+
from commpy.modulation import kbest
12+
from commpy.wifi80211 import Wifi80211
13+
14+
15+
@dec.slow
16+
def test_wifi80211_siso_channel():
17+
seed(17121996)
18+
wifi80211 = Wifi80211(1)
19+
BERs = wifi80211.link_performance(SISOFlatChannel(fading_param=(1 + 0j, 0)), range(0, 9, 2), 10 ** 4, 600)[0]
20+
desired = (0.489, 0.503, 0.446, 0.31, 0.015) # From previous tests
21+
# for i, val in enumerate(desired):
22+
# print((BERs[i] - val) / val)
23+
assert_allclose(BERs, desired, rtol=0.3,
24+
err_msg='Wrong performance for SISO QPSK and AWGN channel')
25+
26+
27+
@dec.slow
28+
def test_wifi80211_mimo_channel():
29+
seed(17121996)
30+
# Apply link_performance to MIMO 16QAM and 4x4 Rayleigh channel
31+
wifi80211 = Wifi80211(3)
32+
RayleighChannel = MIMOFlatChannel(4, 4)
33+
RayleighChannel.uncorr_rayleigh_fading(complex)
34+
modem = wifi80211.get_modem()
35+
36+
def receiver(y, h, constellation, noise_var):
37+
return modem.demodulate(kbest(y, h, constellation, 16), 'hard')
38+
39+
BERs = wifi80211.link_performance(RayleighChannel, arange(0, 21, 5) + 10 * log10(modem.num_bits_symbol), 10 ** 4,
40+
600, receiver=receiver)[0]
41+
desired = (0.535, 0.508, 0.521, 0.554, 0.475) # From previous test
42+
assert_allclose(BERs, desired, rtol=1.25,
43+
err_msg='Wrong performance for MIMO 16QAM and 4x4 Rayleigh channel')
44+
45+
46+
if __name__ == "__main__":
47+
run_module_suite()

0 commit comments

Comments
 (0)