-
Notifications
You must be signed in to change notification settings - Fork 182
/
links.py
407 lines (329 loc) · 16.6 KB
/
links.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# Authors: CommPy contributors
# License: BSD 3-Clause
"""
============================================
Links (:mod:`commpy.links`)
============================================
.. autosummary::
:toctree: generated/
link_performance -- Estimate the BER performance of a link model with Monte Carlo simulation.
LinkModel -- Link model object.
idd_decoder -- Produce the decoder function to model a MIMO IDD decoder.
"""
from __future__ import division # Python 2 compatibility
import math
from fractions import Fraction
from inspect import getfullargspec
import numpy as np
from commpy.channels import MIMOFlatChannel
__all__ = ['link_performance', 'LinkModel', 'idd_decoder']
def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
"""
Estimate the BER performance of a link model with Monte Carlo simulation.
Equivalent to call link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate).
Parameters
----------
link_model : linkModel object.
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 or Fraction 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
"""
if not send_chunk:
send_chunk = err_min
return link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate)
class LinkModel:
"""
Construct a link model.
Parameters
----------
modulate : function with same prototype as Modem.modulate
channel : FlatChannel object
receive : function with prototype receive(y, H, constellation, noise_var) that return a binary array.
y : 1D ndarray
Received complex symbols (shape: num_receive_antennas x 1)
h : 2D ndarray
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
constellation : 1D ndarray
noise_var : positive float
Noise variance
num_bits_symbols : int
constellation : array of float or complex
Es : float
Average energy per symbols.
*Default* Es=1.
decoder : function with prototype decoder(array) or decoder(y, H, constellation, noise_var, array) that return a
binary ndarray.
*Default* is no process.
rate : float or Fraction in (0,1]
Rate of the used code.
*Default*: 1 i.e. no code.
Attributes
----------
modulate : function with same prototype as Modem.modulate
channel : _FlatChannel object
receive : function with prototype receive(y, H, constellation, noise_var) that return a binary array.
y : 1D ndarray
Received complex symbols (shape: num_receive_antennas x 1)
h : 2D ndarray
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
constellation : 1D ndarray
noise_var : positive float
Noise variance
num_bits_symbols : int
constellation : array of float or complex
Es : float
Average energy per symbols.
decoder : function with prototype decoder(binary array) that return a binary ndarray.
*Default* is no process.
rate : float
Code rate.
*Default* is 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:
self.decoder = lambda msg: msg
else:
self.decoder = decoder
self.full_simulation_results = None
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.
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.
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 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 : Fraction in (0,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
-------
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
BERs = np.zeros_like(SNRs, dtype=float)
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 taking in to account the coding rate
if send_chunk is None:
send_chunk = err_min
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
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], 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)
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 * number_chunks_per_send)
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) / 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],
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)
else:
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)].astype(int)).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, 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 or Fraction 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
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
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], float(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) / 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],
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)].astype(int)).sum()
else:
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:
break
return BERs
def idd_decoder(detector, decoder, decision, n_it):
"""
Produce a decoder function that model the specified MIMO iterative detection and decoding (IDD) process.
The returned function can be used as is to build a working LinkModel object.
Parameters
----------
detector : function with prototype detector(y, H, constellation, noise_var, a_priori) that return a LLRs array.
y : 1D ndarray
Received complex symbols (shape: num_receive_antennas x 1).
h : 2D ndarray
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas).
constellation : 1D ndarray.
noise_var : positive float
Noise variance.
a_priori : 1D ndarray of floats
A priori as Log-Likelihood Ratios.
decoder : function with prototype(LLRs) that return a LLRs array.
LLRs : 1D ndarray of floats
A priori as Log-Likelihood Ratios.
decision : function wih prototype(LLRs) that return a binary 1D-array that model the decision to extract the
information bits from the LLRs array.
n_it : positive integer
Number or iteration during the IDD process.
Returns
-------
decode : function useable as it is to build a LinkModel object that produce a bit array from the parameters
y : 1D ndarray
Received complex symbols (shape: num_receive_antennas x 1).
h : 2D ndarray
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas).
constellation : 1D ndarray
noise_var : positive float
Noise variance.
bits_per_send : positive integer
Number or bit send at each symbol vector.
"""
def decode(y, h, constellation, noise_var, a_priori, bits_per_send):
a_priori_decoder = a_priori.copy()
nb_vect, nb_rx, nb_tx = h.shape
for iteration in range(n_it):
a_priori_detector = (decoder(a_priori_decoder) - a_priori_decoder)
for i in range(nb_vect):
a_priori_decoder[i * bits_per_send:(i + 1) * bits_per_send] = \
detector(y[i], h[i], constellation, noise_var,
a_priori_detector[i * bits_per_send:(i + 1) * bits_per_send])
a_priori_decoder -= a_priori_detector
return decision(a_priori_decoder + a_priori_detector)
return decode