Skip to content

Commit e3bb4f7

Browse files
committed
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.
1 parent 5096d4c commit e3bb4f7

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

commpy/examples/conv_encode_decode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ def decoder_soft(msg):
137137
decoder_soft, code_rate)
138138

139139
# Test
140-
BERs_hard = model_hard.link_performance(SNRs, 2, 600, 5000, code_rate)
141-
BERs_soft = model_soft.link_performance(SNRs, 2, 600, 5000, code_rate)
140+
BERs_hard = model_hard.link_performance(SNRs, 10000, 600, 5000, code_rate)
141+
BERs_soft = model_soft.link_performance(SNRs, 10000, 600, 5000, code_rate)
142142
plt.semilogy(SNRs, BERs_hard, 'o-', SNRs, BERs_soft, 'o-')
143143
plt.grid()
144144
plt.xlabel('Signal to Noise Ration (dB)')

commpy/links.py

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
6060
"""
6161
if not send_chunk:
6262
send_chunk = err_min
63-
return link_model.link_performance(SNRs, math.ceil(send_max / send_chunk), err_min, send_chunk, code_rate)
63+
return link_model.link_performance(SNRs, send_max, err_min, send_chunk, code_rate)
6464

6565

6666
class LinkModel:
@@ -149,8 +149,8 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
149149
self.decoder = decoder
150150
self.full_simulation_results = None
151151

152-
def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: float = 1.,
153-
number_chunks_per_send=1, stop_on_surpass_error=True):
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):
154154
"""
155155
Estimate the BER performance of a link model with Monte Carlo simulation.
156156
@@ -184,8 +184,15 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: fl
184184
185185
Returns
186186
-------
187-
BERs : 1d ndarray
188-
Estimated Bit Error Ratio corresponding to each SNRs
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
189196
"""
190197

191198
# Initialization
@@ -251,6 +258,78 @@ def link_performance(self, SNRs, tx_max, err_min, send_chunk=None, code_rate: fl
251258
if BEs[id_SNR].sum() < err_min:
252259
break
253260
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.
282+
Returns
283+
-------
284+
BERs : 1d ndarray
285+
Estimated Bit Error Ratio corresponding to each SNRs
286+
"""
287+
288+
# Initialization
289+
BERs = np.zeros_like(SNRs, dtype=float)
290+
# Set chunk size and round it to be a multiple of num_bits_symbol*nb_tx to avoid padding
291+
if send_chunk is None:
292+
send_chunk = err_min
293+
divider = self.num_bits_symbol * self.channel.nb_tx
294+
send_chunk = max(divider, send_chunk // divider * divider)
295+
296+
receive_size = self.channel.nb_tx * self.num_bits_symbol
297+
full_args_decoder = len(getfullargspec(self.decoder).args) > 1
298+
299+
# Computations
300+
for id_SNR in range(len(SNRs)):
301+
self.channel.set_SNR_dB(SNRs[id_SNR], code_rate, self.Es)
302+
bit_send = 0
303+
bit_err = 0
304+
while bit_send < send_max and bit_err < err_min:
305+
# Propagate some bits
306+
msg = np.random.choice((0, 1), send_chunk)
307+
symbs = self.modulate(msg)
308+
channel_output = self.channel.propagate(symbs)
309+
310+
# Deals with MIMO channel
311+
if isinstance(self.channel, MIMOFlatChannel):
312+
nb_symb_vector = len(channel_output)
313+
received_msg = np.empty(int(math.ceil(len(msg) / self.rate)), dtype=np.int8)
314+
for i in range(nb_symb_vector):
315+
received_msg[receive_size * i:receive_size * (i + 1)] = \
316+
self.receive(channel_output[i], self.channel.channel_gains[i],
317+
self.constellation, self.channel.noise_std ** 2)
318+
else:
319+
received_msg = self.receive(channel_output, self.channel.channel_gains,
320+
self.constellation, self.channel.noise_std ** 2)
321+
# Count errors
322+
if full_args_decoder:
323+
decoded_bits = self.decoder(channel_output, self.channel.channel_gains,
324+
self.constellation, self.channel.noise_std ** 2,
325+
received_msg, self.channel.nb_tx * self.num_bits_symbol)
326+
bit_err += np.bitwise_xor(msg, decoded_bits[:len(msg)]).sum()
327+
else:
328+
bit_err += np.bitwise_xor(msg, self.decoder(received_msg)[:len(msg)]).sum()
329+
bit_send += send_chunk
330+
BERs[id_SNR] = bit_err / bit_send
331+
if bit_err < err_min:
332+
break
254333
return BERs
255334

256335

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__":

0 commit comments

Comments
 (0)