Skip to content

Commit 26fd8ea

Browse files
committed
Merge remote-tracking branch 'origin/Improve_link_test' into Channels
# Conflicts: # commpy/modulation.py
2 parents b76249e + 17ebf24 commit 26fd8ea

File tree

7 files changed

+257
-97
lines changed

7 files changed

+257
-97
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Modulation/Demodulation
5454
- OFDM Tx/Rx signal processing
5555
- MIMO Maximum Likelihood (ML) Detection.
5656
- MIMO K-best Schnorr-Euchner Detection.
57+
- Convert channel matrix to Bit-level representation.
5758

5859
Sequences
5960
---------
@@ -66,7 +67,11 @@ Utilities
6667
- Hamming distance, Euclidean distance.
6768
- Upsample
6869
- Power of a discrete-time signal
70+
71+
Links
72+
-----
6973
- Estimate the BER performance of a link model with Monte Carlo simulation.
74+
- Link model object.
7075

7176
FAQs
7277
----

commpy/examples/plotConsModem.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Authors: Youness Akourim <akourim97@gmail.com>
2+
# License: BSD 3-Clause
3+
4+
from commpy.modulation import PSKModem, QAMModem
5+
6+
# =============================================================================
7+
# Example constellation plot of Modem
8+
# =============================================================================
9+
10+
# PSK corresponding to PSKModem for 4 bits
11+
psk = PSKModem(16)
12+
psk.plotCons()
13+
14+
# QAM corresponding to QAMModem for 2bits
15+
Qam = QAMModem(4)
16+
Qam.plotCons()

commpy/links.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Authors: Youness Akourim <akourim97@gmail.com> & Bastien Trotobas <bastien.trotobas@gmail.com>
2+
# License: BSD 3-Clause
3+
4+
"""
5+
============================================
6+
Links (:mod:`commpy.links`)
7+
============================================
8+
9+
.. autosummary::
10+
:toctree: generated/
11+
12+
link_performance -- Estimate the BER performance of a link model with Monte Carlo simulation.
13+
linkModel -- Link model object.
14+
"""
15+
from __future__ import division # Python 2 compatibility
16+
17+
import numpy as np
18+
from commpy.channels import MIMOFlatChannel
19+
20+
__all__ = ['link_performance', 'linkModel']
21+
22+
23+
def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
24+
"""
25+
Estimate the BER performance of a link model with Monte Carlo simulation.
26+
27+
Parameters
28+
----------
29+
link_model : linkModel object.
30+
31+
SNRs : 1D arraylike
32+
Signal to Noise ratio in dB defined as :math:`SNR_{dB} = (E_b/N_0)_{dB} + 10 \log_{10}(R_cM_c)`
33+
where :math:`Rc` is the code rate and :math:`Mc` the modulation rate.
34+
35+
send_max : int
36+
Maximum number of bits send for each SNR.
37+
38+
err_min : int
39+
link_performance send bits until it reach err_min errors (see also send_max).
40+
41+
send_chunk : int
42+
Number of bits to be send at each iteration.
43+
*Default*: send_chunck = err_min
44+
45+
code_rate : float in (0,1]
46+
Rate of the used code.
47+
*Default*: 1 i.e. no code.
48+
49+
Returns
50+
-------
51+
BERs : 1d ndarray
52+
Estimated Bit Error Ratio corresponding to each SNRs
53+
"""
54+
55+
# Initialization
56+
BERs = np.empty_like(SNRs, dtype=float)
57+
# Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
58+
if send_chunk is None:
59+
send_chunk = err_min
60+
divider = link_model.num_bits_symbol * link_model.channel.nb_tx
61+
send_chunk = max(divider, send_chunk // divider * divider)
62+
63+
# Computations
64+
for id_SNR in range(len(SNRs)):
65+
link_model.channel.set_SNR_dB(SNRs[id_SNR], code_rate, link_model.Es)
66+
bit_send = 0
67+
bit_err = 0
68+
receive_size = link_model.channel.nb_tx * link_model.num_bits_symbol
69+
while bit_send < send_max and bit_err < err_min:
70+
# Propagate some bits
71+
msg = np.random.choice((0, 1), send_chunk)
72+
symbs = link_model.modulate(msg)
73+
channel_output = link_model.channel.propagate(symbs)
74+
75+
# Deals with MIMO channel
76+
if isinstance(link_model.channel, MIMOFlatChannel):
77+
nb_symb_vector = len(channel_output)
78+
received_msg = np.empty_like(msg, int)
79+
for i in range(nb_symb_vector):
80+
received_msg[receive_size * i:receive_size * (i+1)] = \
81+
link_model.receive(channel_output[i], link_model.channel.channel_gains[i], link_model.constellation)
82+
else:
83+
received_msg = channel_output
84+
# Count errors
85+
bit_err += (msg != received_msg).sum() # Remove MIMO padding
86+
bit_send += send_chunk
87+
BERs[id_SNR] = bit_err / bit_send
88+
return BERs
89+
90+
91+
class linkModel:
92+
"""
93+
Construct a link model.
94+
95+
Parameters
96+
----------
97+
modulate : function with same prototype as Modem.modulate
98+
99+
channel : _FlatChannel object
100+
101+
receive : function with prototype receive(y, H, constellation) that return a binary array.
102+
y : 1D ndarray
103+
Received complex symbols (shape: num_receive_antennas x 1)
104+
105+
h : 2D ndarray
106+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
107+
108+
constellation : 1D ndarray
109+
110+
num_bits_symbols : int
111+
112+
constellation : array of float or complex
113+
114+
Es : float
115+
Average energy per symbols.
116+
*Default* Es=1.
117+
118+
Attributes
119+
----------
120+
modulate : function with same prototype as Modem.modulate
121+
122+
channel : _FlatChannel object
123+
124+
receive : function with prototype receive(y, H, constellation) that return a binary array.
125+
y : 1D ndarray of floats
126+
Received complex symbols (shape: num_receive_antennas x 1)
127+
128+
h : 2D ndarray of floats
129+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
130+
131+
constellation : 1D ndarray of floats
132+
133+
num_bits_symbols : int
134+
135+
constellation : array of float or complex
136+
137+
Es : float
138+
Average energy per symbols.
139+
*Default* Es=1.
140+
"""
141+
def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1):
142+
self.modulate = modulate
143+
self.channel = channel
144+
self.receive = receive
145+
self.num_bits_symbol = num_bits_symbol
146+
self.constellation = constellation
147+
self.Es = Es

commpy/modulation.py

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@
1515
ofdm_rx -- OFDM Receive Signal Processing
1616
mimo_ml -- MIMO Maximum Likelihood (ML) Detection.
1717
kbest -- MIMO K-best Schnorr-Euchner Detection.
18+
bit_lvl_repr -- Bit level representation
1819
1920
"""
2021
from itertools import product
2122

23+
import matplotlib.pyplot as plt
24+
from commpy.utilities import bitarray2dec, dec2bitarray
2225
from numpy import arange, array, zeros, pi, cos, sin, sqrt, log2, argmin, \
2326
hstack, repeat, tile, dot, sum, shape, concatenate, exp, \
24-
log, vectorize, empty
27+
log, vectorize, empty, eye, kron
2528
from numpy.fft import fft, ifft
2629
from numpy.linalg import qr
2730

28-
from commpy.utilities import bitarray2dec, dec2bitarray
29-
30-
__all__ = ['PSKModem', 'QAMModem', 'ofdm_tx', 'ofdm_rx', 'mimo_ml', 'kbest']
31+
__all__ = ['PSKModem', 'QAMModem', 'ofdm_tx', 'ofdm_rx', 'mimo_ml', 'kbest', 'bit_lvl_repr']
3132

3233

3334
class Modem:
34-
3535
def modulate(self, input_bits):
3636
""" Modulate (map) an array of bits to constellation symbols.
3737
@@ -100,6 +100,44 @@ def demodulate(self, input_symbols, demod_type, noise_var=0):
100100

101101
return demod_bits
102102

103+
def plotCons(self):
104+
""" Plot the constellation """
105+
# init some arrays
106+
beta = self.num_bits_symbol
107+
numbit = '0' + str(beta) + 'b'
108+
Bin = []
109+
mot = []
110+
const = []
111+
112+
# creation of w array
113+
reel = [pow(2, i) for i in range(beta // 2 - 1, -1, -1)]
114+
im = [1j * pow(2, i) for i in range(beta // 2 - 1, -1, -1)]
115+
w = concatenate((reel, im), axis=None)
116+
117+
listBin = [format(i, numbit) for i in range(2 ** beta)]
118+
for e in listBin:
119+
for i in range(beta):
120+
Bin.append(ord(e[i]) - 48)
121+
if (ord(e[i]) - 48 == 0):
122+
mot.append(-1)
123+
else:
124+
mot.append(1)
125+
const.append(dot(w, mot))
126+
mot = []
127+
symb = self.modulate(Bin)
128+
129+
# plot the symbols
130+
x = symb.real
131+
y = symb.imag
132+
133+
plt.plot(x, y, '+',linewidth=4)
134+
for i in range(len(x)):
135+
plt.text(x[i], y[i] , listBin[i])
136+
137+
plt.title('Constellation')
138+
plt.grid()
139+
plt.show()
140+
103141

104142
class PSKModem(Modem):
105143
""" Creates a Phase Shift Keying (PSK) Modem object. """
@@ -231,27 +269,35 @@ def kbest(y, h, constellation, K):
231269
-------
232270
x : 1D ndarray of constellation points
233271
Detected vector (length: num_receive_antennas)
272+
273+
raises
274+
------
275+
ValueError
276+
If h has more columns than rows.
234277
"""
278+
nb_tx, nb_rx = h.shape
279+
if nb_rx > nb_tx:
280+
raise ValueError('h has more columns than rows')
281+
235282
# QR decomposition
236283
q, r = qr(h)
237284
yt = q.conj().T.dot(y)
238285

239286
# Initialization
240287
m = len(constellation)
241-
n = len(yt)
242288
nb_can = 1
243289

244290
if isinstance(constellation[0], complex):
245291
const_type = complex
246292
else:
247293
const_type = float
248-
X = empty((n, K * m), dtype=const_type) # Set of current candidates
294+
X = empty((nb_rx, K * m), dtype=const_type) # Set of current candidates
249295
d = tile(yt[:, None], (1, K * m)) # Corresponding distance vector
250296
d_tot = zeros(K * m, dtype=float) # Corresponding total distance
251297
hyp = empty(K * m, dtype=const_type) # Hypothesis vector
252298

253299
# Processing
254-
for coor in range(n-1, -1, -1):
300+
for coor in range(nb_rx-1, -1, -1):
255301
nb_hyp = nb_can * m
256302

257303
# Copy best candidates m times
@@ -275,3 +321,29 @@ def kbest(y, h, constellation, K):
275321
d[:coor, :nb_can] -= r[:coor, coor, None] * hyp[argsort[:nb_can]]
276322
d_tot[:nb_can] = d_tot[argsort[:nb_can]]
277323
return X[:, 0]
324+
325+
326+
def bit_lvl_repr(H, w):
327+
""" Bit-level representation of matrix H with weights w.
328+
329+
parameters
330+
----------
331+
H : 2D ndarray (shape : nb_rx, nb_tx)
332+
Channel Matrix.
333+
334+
w : 1D ndarray of complex (length : beta)
335+
Bit level representation weights. The length must be even.
336+
337+
return
338+
------
339+
A : 2D nbarray (shape : nb_rx, nb_tx*beta)
340+
Channel matrix adapted to the bit-level representation.
341+
"""
342+
beta = len(w)
343+
if beta%2 == 0:
344+
m, n = H.shape
345+
In = eye(n,n)
346+
kr = kron(In,w)
347+
return dot(H,kr)
348+
else:
349+
raise ValueError('Beta must be even.')

0 commit comments

Comments
 (0)