Skip to content

Commit 31f24fc

Browse files
committed
Soft decoding for convolutionnal codes using log-likelihood as metrics.
1 parent 41ad792 commit 31f24fc

File tree

5 files changed

+49
-25
lines changed

5 files changed

+49
-25
lines changed

commpy/channelcoding/convcode.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ class Trellis:
9999
[2 1]]
100100
References
101101
----------
102-
[1] S. Benedetto, R. Garello, et G. Montorsi, "A search for good convolutional codes to be used in the
103-
construction of turbo codes", IEEE Transactions on Communications, vol. 46, nᵒ 9, p. 1101‑1105, sept. 1998.
102+
[1] S. Benedetto, R. Garello et G. Montorsi, "A search for good convolutional codes to be used in the
103+
construction of turbo codes", IEEE Transactions on Communications, vol. 46, n. 9, p. 1101-1005, spet. 1998
104104
"""
105105
def __init__(self, memory, g_matrix, feedback = None, code_type = 'default'):
106106

@@ -389,7 +389,7 @@ def conv_encode(message_bits, trellis, termination = 'term', puncture_matrix=Non
389389

390390
outbits = np.zeros(number_outbits, 'int')
391391
if puncture_matrix is not None:
392-
p_outbits = np.zeros(number_outbits)
392+
p_outbits = np.zeros(number_outbits, 'int')
393393
else:
394394
p_outbits = np.zeros(int(number_outbits*
395395
puncture_matrix[0:].sum()/np.size(puncture_matrix, 1)), 'int')
@@ -477,7 +477,9 @@ def _acs_traceback(r_codeword, trellis, decoding_type,
477477
if decoding_type == 'hard':
478478
branch_metric = hamming_dist(r_codeword.astype(int), i_codeword_array.astype(int))
479479
elif decoding_type == 'soft':
480-
pass
480+
neg_LL_0 = np.log(np.exp(r_codeword) + 1) # negative log-likelihood to have received a 0
481+
neg_LL_1 = neg_LL_0 - r_codeword # negative log-likelihood to have received a 1
482+
branch_metric = np.where(i_codeword_array, neg_LL_1, neg_LL_0).sum()
481483
elif decoding_type == 'unquantized':
482484
i_codeword_array = 2*i_codeword_array - 1
483485
branch_metric = euclid_dist(r_codeword, i_codeword_array)
@@ -531,7 +533,7 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
531533
decoding_type : str {'hard', 'soft', 'unquantized'}
532534
The type of decoding to be used.
533535
'hard' option is used for hard inputs (bits) to the decoder, e.g., BSC channel.
534-
'soft' option is used for soft inputs (LLRs) to the decoder.
536+
'soft' option is used for soft inputs (LLRs) to the decoder. LLRs are clipped in [-500, 500].
535537
'unquantized' option is used for soft inputs (real numbers) to the decoder, e.g., BAWGN channel.
536538
Returns
537539
-------
@@ -561,26 +563,29 @@ def viterbi_decode(coded_bits, trellis, tb_depth=None, decoding_type='hard'):
561563

562564
path_metrics = np.full((trellis.number_states, 2), np.inf)
563565
path_metrics[0][0] = 0
564-
paths = np.full((trellis.number_states, tb_depth), np.iinfo(int).max, 'int')
566+
paths = np.empty((trellis.number_states, tb_depth), 'int')
565567
paths[0][0] = 0
566568

567569
decoded_symbols = np.zeros([trellis.number_states, tb_depth], 'int')
568-
decoded_bits = np.empty(math.ceil(L / k) * k + tb_depth, 'int')
570+
decoded_bits = np.empty(int(math.ceil((L + tb_depth) / k) * k), 'int')
569571
r_codeword = np.zeros(n, 'int')
570572

571573
tb_count = 1
572574
count = 0
573575
current_number_states = trellis.number_states
574576

577+
coded_bits = coded_bits.clip(-500, 500)
578+
575579
for t in range(1, int((L+total_memory)/k)):
576580
# Get the received codeword corresponding to t
577581
if t <= L // k:
578582
r_codeword = coded_bits[(t-1)*n:t*n]
583+
# Pad with '0'
579584
else:
580585
if decoding_type == 'hard':
581586
r_codeword[:] = 0
582587
elif decoding_type == 'soft':
583-
pass
588+
r_codeword[:] = np.iinfo(int).min
584589
elif decoding_type == 'unquantized':
585590
r_codeword[:] = -1
586591
else:

commpy/channelcoding/tests/test_convcode.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Authors: Veeresh Taranalli <veeresht@gmail.com>
22
# License: BSD 3-Clause
33

4-
from numpy import array
5-
from numpy.random import randint
6-
from numpy.testing import assert_array_equal, dec
4+
from numpy import array, inf
5+
from numpy.random import randint, randn
6+
from numpy.testing import assert_array_equal, dec, assert_array_less
77

88
from commpy.channelcoding.convcode import Trellis, conv_encode, viterbi_decode
99

@@ -122,3 +122,13 @@ def test_conv_encode_viterbi_decode(self):
122122
coded_syms = 2.0 * coded_bits - 1
123123
decoded_bits = viterbi_decode(coded_syms, self.trellis[i], 15, 'unquantized')
124124
assert_array_equal(decoded_bits[:len(msg)], msg)
125+
126+
coded_bits = conv_encode(msg, self.trellis[i])
127+
coded_syms = 10.0 * coded_bits - 5 + randn(len(coded_bits)) * 2
128+
decoded_bits = viterbi_decode(coded_syms, self.trellis[i], 15, 'soft')
129+
assert_array_less((decoded_bits[:len(msg)] - msg).sum(), 0.03 * blocklength)
130+
131+
coded_bits = conv_encode(msg, self.trellis[i])
132+
coded_syms = (2.0 * coded_bits - 1) * inf
133+
decoded_bits = viterbi_decode(coded_syms, self.trellis[i], 15, 'soft')
134+
assert_array_less((decoded_bits[:len(msg)] - msg).sum(), 0.03 * blocklength)

commpy/links.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,20 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
6161
divider = link_model.num_bits_symbol * link_model.channel.nb_tx
6262
send_chunk = max(divider, send_chunk // divider * divider)
6363

64+
# Evaluate the size of each reception
65+
msg = np.random.choice((0, 1), link_model.channel.nb_tx)
66+
link_model.channel.noise_std = 0
67+
symbs = link_model.modulate(msg)
68+
channel_output = link_model.channel.propagate(symbs)
69+
received_msg = link_model.receive(channel_output[0], link_model.channel.channel_gains[0],
70+
link_model.constellation, link_model.channel.noise_std ** 2)
71+
receive_size = len(received_msg)
72+
6473
# Computations
6574
for id_SNR in range(len(SNRs)):
6675
link_model.channel.set_SNR_dB(SNRs[id_SNR], code_rate, link_model.Es)
6776
bit_send = 0
6877
bit_err = 0
69-
receive_size = link_model.channel.nb_tx * link_model.num_bits_symbol
7078
while bit_send < send_max and bit_err < err_min:
7179
# Propagate some bits
7280
msg = np.random.choice((0, 1), send_chunk)
@@ -78,12 +86,12 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
7886
nb_symb_vector = len(channel_output)
7987
received_msg = np.empty_like(msg, int)
8088
for i in range(nb_symb_vector):
81-
received_msg[receive_size * i:receive_size * (i+1)] = \
82-
link_model.receive(channel_output[i], link_model.channel.channel_gains[i],
83-
link_model.constellation, link_model.channel.noise_std**2)
89+
received_msg[receive_size * i:receive_size * (i + 1)] = \
90+
link_model.receive(channel_output[i], link_model.channel.channel_gains[i],
91+
link_model.constellation, link_model.channel.noise_std ** 2)
8492
else:
8593
received_msg = link_model.receive(channel_output, link_model.channel.channel_gains,
86-
link_model.constellation, link_model.channel.noise_std**2)
94+
link_model.constellation, link_model.channel.noise_std ** 2)
8795
# Count errors
8896
bit_err += (msg != received_msg).sum()
8997
bit_send += send_chunk
@@ -144,6 +152,7 @@ class LinkModel:
144152
Average energy per symbols.
145153
*Default* Es=1.
146154
"""
155+
147156
def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1):
148157
self.modulate = modulate
149158
self.channel = channel

commpy/modulation.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def mimo_ml(y, h, constellation):
249249
return x_r
250250

251251

252-
def kbest(y, h, constellation, K, noise_var=0, output_type='hard', decode=None):
252+
def kbest(y, h, constellation, K, noise_var=0, output_type='hard', demode=None):
253253
""" MIMO K-best Schnorr-Euchner Detection.
254254
255255
Reference: Zhan Guo and P. Nilsson, 'Algorithm and implementation of the K-best sphere decoding for MIMO detection',
@@ -278,7 +278,7 @@ def kbest(y, h, constellation, K, noise_var=0, output_type='hard', decode=None):
278278
'soft': soft output i.e. output is a vector of Log-Likelihood Ratios.
279279
*Default* value is 'hard'
280280
281-
decode : function with prototype binary_word = decode(point)
281+
demode : function with prototype binary_word = demode(point)
282282
Function that provide the binary word corresponding to a symbol vector.
283283
284284
Returns
@@ -341,7 +341,7 @@ def kbest(y, h, constellation, K, noise_var=0, output_type='hard', decode=None):
341341
if output_type == 'hard':
342342
return X[:, 0]
343343
elif output_type == 'soft':
344-
return max_log_approx(y, h, noise_var, X, decode)
344+
return max_log_approx(y, h, noise_var, X, demode)
345345
else:
346346
raise ValueError('output_type must be "hard" or "soft"')
347347

@@ -372,8 +372,8 @@ def bit_lvl_repr(H, w):
372372
raise ValueError('Beta must be even.')
373373

374374

375-
def max_log_approx(y, h, noise_var, pts_list, decode):
376-
""" Max-log approximation
375+
def max_log_approx(y, h, noise_var, pts_list, demode):
376+
""" Max-log demode
377377
378378
parameters
379379
----------
@@ -390,7 +390,7 @@ def max_log_approx(y, h, noise_var, pts_list, decode):
390390
Set of points to compute max log approximation (points are column-wise).
391391
(shape: num_receive_antennas x num_points)
392392
393-
decode : function with prototype binary_word = decode(point)
393+
demode : function with prototype binary_word = demode(point)
394394
Function that provide the binary word corresponding to a symbol vector.
395395
396396
return
@@ -400,7 +400,7 @@ def max_log_approx(y, h, noise_var, pts_list, decode):
400400
"""
401401
# Decode all pts
402402
nb_pts = pts_list.shape[1]
403-
bits = decode(pts_list.reshape(-1, order='F')).reshape(nb_pts, -1) # Set of binary words (one word by row)
403+
bits = demode(pts_list.reshape(-1, order='F')).reshape(nb_pts, -1) # Set of binary words (one word by row)
404404

405405
# Prepare LLR computation
406406
nb_bits = bits.shape[1]

commpy/tests/test_modulation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_bit_lvl_repr():
2323

2424
SNR = arange(10, 16, 5)
2525

26-
def receiver_with_blr(y, H, cons):
26+
def receiver_with_blr(y, H, cons, noise_var):
2727
# Create w
2828
beta = int(log2(len(cons)))
2929
reel = [pow(2, i) for i in range(beta // 2 - 1, -1, -1)]
@@ -36,7 +36,7 @@ def receiver_with_blr(y, H, cons):
3636
mes[mes == -1] = 0
3737
return mes
3838

39-
def receiver_without_blr(y, H, cons):
39+
def receiver_without_blr(y, H, cons, noise_var):
4040
return qam.demodulate(mimo_ml(y, H, cons), 'hard')
4141

4242
my_model_without_blr = \

0 commit comments

Comments
 (0)