Skip to content

Commit b0cf955

Browse files
committed
Several LDPC updates.
* Add 2 WiMAX LDPC designs, and a link function to model MIMO IDD process. * Max-log approximation now return the opposite of the previously LLRs to be consistent with the definition used in the LDPC module. * Fix some bugs.
1 parent 1f8e9c6 commit b0cf955

File tree

8 files changed

+160
-75
lines changed

8 files changed

+160
-75
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Links
7373
-----
7474
- Estimate the BER performance of a link model with Monte Carlo simulation.
7575
- Link model object.
76+
- Helper function for MIMO Iteration Detection and Decoding scheme.
7677

7778
FAQs
7879
----

commpy/channelcoding/ldpc.py

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
def build_matrix(ldpc_code_params):
1313
"""
14-
Build the parity check matrix from parameters dictionary and add the result in this dictionary.
14+
Build the parity check and generator matrices from parameters dictionary and add the result in this dictionary.
1515
1616
Parameters
1717
----------
@@ -28,6 +28,7 @@ def build_matrix(ldpc_code_params):
2828
---
2929
to ldpc_code_param:
3030
parity_check_matrix (CSC sparse matrix of int8) - parity check matrix.
31+
generator_matrix (CSR sparse matrix) - generator matrix of the code.
3132
"""
3233
n_cnodes = ldpc_code_params['n_cnodes']
3334
cnode_deg_list = ldpc_code_params['cnode_deg_list']
@@ -37,7 +38,12 @@ def build_matrix(ldpc_code_params):
3738
for cnode_idx in range(n_cnodes):
3839
parity_check_matrix[cnode_idx, cnode_adj_list[cnode_idx, :cnode_deg_list[cnode_idx]]] = 1
3940

40-
ldpc_code_params['parity_check_matrix'] = parity_check_matrix.tocsc()
41+
parity_check_matrix = parity_check_matrix.tocsc()
42+
systematic_part = parity_check_matrix[:, -n_cnodes:]
43+
parity_part = parity_check_matrix[:, :-n_cnodes]
44+
45+
ldpc_code_params['parity_check_matrix'] = parity_check_matrix
46+
ldpc_code_params['generator_matrix'] = splg.inv(systematic_part).dot(parity_part).tocsr()
4147

4248

4349
def get_ldpc_code_params(ldpc_design_filename, compute_matrix=False):
@@ -170,7 +176,7 @@ def ldpc_bp_decode(llr_vec, ldpc_code_params, decoder_algorithm, n_iters):
170176
Parameters
171177
----------
172178
llr_vec : 1D array of float
173-
Received codeword LLR values from the channel.
179+
Received codeword LLR values from the channel. They will be clipped in [-38, 38].
174180
175181
ldpc_code_params : dictionary that at least contains these parameters
176182
Parameters of the LDPC code as provided by `get_ldpc_code_params`:
@@ -204,6 +210,9 @@ def ldpc_bp_decode(llr_vec, ldpc_code_params, decoder_algorithm, n_iters):
204210
LLR values corresponding to the decoded output.
205211
"""
206212

213+
# Clip LLRs into [-38, 38]
214+
llr_vec.clip(-38, 38, llr_vec)
215+
207216
n_cnodes = ldpc_code_params['n_cnodes']
208217
n_vnodes = ldpc_code_params['n_vnodes']
209218
max_cnode_deg = ldpc_code_params['max_cnode_deg']
@@ -256,7 +265,7 @@ def ldpc_bp_decode(llr_vec, ldpc_code_params, decoder_algorithm, n_iters):
256265
msg_sum = np.sum(cnode_list_msgs)
257266

258267
# Compute messages on outgoing edges using the incoming message sum (LLRs are clipped in [-38, 38])
259-
np.clip(llr_vec[vnode_idx] + msg_sum - cnode_list_msgs, -38, 38, vnode_msgs[start_idx:start_idx+offset])
268+
vnode_msgs[start_idx:start_idx+offset] = llr_vec[vnode_idx] + msg_sum - cnode_list_msgs
260269

261270
# Update output LLRs and decoded word
262271
out_llrs[vnode_idx] = llr_vec[vnode_idx] + msg_sum
@@ -329,8 +338,8 @@ def write_ldpc_params(parity_check_matrix, file_path):
329338

330339
def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
331340
"""
332-
Encode bits using the LDPC code specified. If the parity check matrix and/or the generator matrix are not computed,
333-
this function will build the missing one(s) and add them to the dictionary.
341+
Encode bits using the LDPC code specified. If the generator matrix is not computed, this function will build it
342+
and add it to the dictionary. It will also add the parity check matrix.
334343
335344
This function work only for LDPC specified by a triangular parity check matrix.
336345
@@ -339,27 +348,25 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
339348
message_bits : 1D-array
340349
Message bit to encode.
341350
342-
ldpc_code_params : dictionary that at least contains on of these options:
351+
ldpc_code_params : dictionary that at least contains one of these options:
343352
Option 1: generator matrix is available.
344-
generator_matrix (2D-array or sparse matrix) - generator matrix of the code.
345-
Option 2: parity check matrix is available, the generator matrix will be added as a CSR sparse matrix.
346-
parity_check_matrix (sparse matrix) - parity check matrix of the code.
347-
Option 3: generator and parity check matrices will be added as sparse matrices of integers.
348-
n_vnodes (int) - number of variable nodes.
349-
n_cnodes (int) - number of check nodes.
350-
max_cnode_deg (int) - maximal degree of a check node.
351-
cnode_adj_list (1D-ndarray of ints) - flatten array so that
352-
cnode_adj_list.reshape((n_cnodes, max_cnode_deg)) gives for each check node the adjacent variable nodes.
353-
cnode_deg_list (1D-ndarray of ints) - degree of each check node.
353+
generator_matrix (2D-array or sparse matrix) - generator matrix of the code.
354+
Option 2: generator and parity check matrices will be added as sparse matrices.
355+
n_vnodes (int) - number of variable nodes.
356+
n_cnodes (int) - number of check nodes.
357+
max_cnode_deg (int) - maximal degree of a check node.
358+
cnode_adj_list (1D-ndarray of ints) - flatten array so that
359+
cnode_adj_list.reshape((n_cnodes, max_cnode_deg)) gives for each check node the adjacent variable nodes.
360+
cnode_deg_list (1D-ndarray of ints) - degree of each check node.
354361
355362
pad : boolean
356-
Whether to add '0' padding to the message to fit the block length.
357-
*Default* is True.
363+
Whether to add '0' padding to the message to fit the block length.
364+
*Default* is True.
358365
359366
Returns
360367
-------
361-
coded_message : 1D-ndarray or 2D-ndarray depending on the number of blocks
362-
Coded message with the systematic part at the beginning.
368+
coded_message : 1D-ndarray or 2D-ndarray of int8 depending on the number of blocks
369+
Coded message with the systematic part at the beginning.
363370
364371
Raises
365372
------
@@ -368,15 +375,7 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
368375
"""
369376

370377
if ldpc_code_params.get('generator_matrix') is None:
371-
if ldpc_code_params.get('parity_check_matrix') is None:
372-
build_matrix(ldpc_code_params)
373-
374-
parity_check_matrix = ldpc_code_params['parity_check_matrix']
375-
block_length = parity_check_matrix.shape[0]
376-
377-
systematic_part = parity_check_matrix[:, -block_length:]
378-
parity_part = parity_check_matrix[:, :-block_length]
379-
ldpc_code_params['generator_matrix'] = splg.inv(systematic_part).dot(parity_part).tocsr()
378+
build_matrix(ldpc_code_params)
380379

381380
block_length = ldpc_code_params['generator_matrix'].shape[1]
382381
modulo = len(message_bits) % block_length
@@ -388,4 +387,4 @@ def triang_ldpc_systematic_encode(message_bits, ldpc_code_params, pad=True):
388387
message_bits = message_bits.reshape(block_length, -1, order='F')
389388

390389
parity_part = ldpc_code_params['generator_matrix'].dot(message_bits) % 2
391-
return np.vstack((message_bits, parity_part)).squeeze()
390+
return np.vstack((message_bits, parity_part)).squeeze().astype(np.int8)

commpy/channelcoding/tests/test_ldpc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,16 @@ def test_ldpc_bp_decode(self):
6565

6666
def test_write_ldpc_params(self):
6767
with TemporaryDirectory() as tmp_dir:
68-
parity_check_matrix = choice((0, 1), (1440, 720))
68+
parity_check_matrix = choice((0, 1), (720, 1440))
6969

7070
file_path = tmp_dir + '/matrix.txt'
7171
write_ldpc_params(parity_check_matrix, file_path)
7272
assert_equal(get_ldpc_code_params(file_path, True)['parity_check_matrix'].toarray(), parity_check_matrix,
7373
'The loaded matrix is not equal to the written one.')
7474

7575
def test_triang_ldpc_systematic_encode(self):
76-
ldpc_design_files = (os.path.join(self.dir, '../designs/ldpc/802.16e/1440.720.txt'),
77-
os.path.join(self.dir, '../designs/ldpc/802.16e/960.720.a.txt'))
76+
ldpc_design_files = (os.path.join(self.dir, '../designs/ldpc/wimax/1440.720.txt'),
77+
os.path.join(self.dir, '../designs/ldpc/wimax/960.720.a.txt'))
7878
wimax_ldpc_params = [get_ldpc_code_params(ldpc_design_file) for ldpc_design_file in ldpc_design_files]
7979

8080
for param in wimax_ldpc_params:

commpy/links.py

Lines changed: 123 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111
1212
link_performance -- Estimate the BER performance of a link model with Monte Carlo simulation.
1313
LinkModel -- Link model object.
14+
idd_decoder -- Produce the decoder function to model a MIMO IDD decoder.
1415
"""
1516
from __future__ import division # Python 2 compatibility
1617

1718
import math
19+
from inspect import getfullargspec
1820

1921
import numpy as np
2022

2123
from commpy.channels import MIMOFlatChannel
2224

23-
__all__ = ['link_performance', 'LinkModel']
25+
__all__ = ['link_performance', 'LinkModel', 'idd_decoder']
2426

2527

2628
def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_rate=1):
@@ -65,6 +67,7 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
6567
send_chunk = max(divider, send_chunk // divider * divider)
6668

6769
receive_size = link_model.channel.nb_tx * link_model.num_bits_symbol
70+
full_args_decoder = len(getfullargspec(link_model.decoder).args) > 1
6871

6972
# Computations
7073
for id_SNR in range(len(SNRs)):
@@ -89,68 +92,88 @@ def link_performance(link_model, SNRs, send_max, err_min, send_chunk=None, code_
8992
received_msg = link_model.receive(channel_output, link_model.channel.channel_gains,
9093
link_model.constellation, link_model.channel.noise_std ** 2)
9194
# Count errors
92-
bit_err += (msg != link_model.decoder(received_msg)).sum()
95+
if full_args_decoder:
96+
decoded_bits = link_model.decoder(channel_output, link_model.channel.channel_gains,
97+
link_model.constellation, link_model.channel.noise_std ** 2,
98+
received_msg, link_model.channel.nb_tx * link_model.num_bits_symbol)
99+
bit_err += (msg != decoded_bits[:len(msg)]).sum()
100+
else:
101+
bit_err += (msg != link_model.decoder(received_msg)[:len(msg)]).sum()
93102
bit_send += send_chunk
94103
BERs[id_SNR] = bit_err / bit_send
95104
return BERs
96105

97106

98107
class LinkModel:
99108
"""
100-
Construct a link model.
109+
Construct a link model.
110+
111+
Parameters
112+
----------
113+
modulate : function with same prototype as Modem.modulate
101114
102-
Parameters
103-
----------
104-
modulate : function with same prototype as Modem.modulate
115+
channel : FlatChannel object
105116
106-
channel : _FlatChannel object
117+
receive : function with prototype receive(y, H, constellation, noise_var) that return a binary array.
118+
y : 1D ndarray
119+
Received complex symbols (shape: num_receive_antennas x 1)
107120
108-
receive : function with prototype receive(y, H, constellation, noise_var) that return a binary array.
109-
y : 1D ndarray
110-
Received complex symbols (shape: num_receive_antennas x 1)
121+
h : 2D ndarray
122+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
111123
112-
h : 2D ndarray
113-
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
124+
constellation : 1D ndarray
114125
115-
constellation : 1D ndarray
126+
noise_var : positive float
127+
Noise variance
116128
117-
noise_var : positive float
118-
Noise variance
129+
num_bits_symbols : int
119130
120-
num_bits_symbols : int
131+
constellation : array of float or complex
121132
122-
constellation : array of float or complex
133+
Es : float
134+
Average energy per symbols.
135+
*Default* Es=1.
123136
124-
Es : float
125-
Average energy per symbols.
126-
*Default* Es=1.
137+
decoder : function with prototype decoder(array) or decoder(y, H, constellation, noise_var, array) that return a
138+
binary array.
139+
*Default* is no process.
127140
128-
decoder : function with prototype decoder(binary array) that return a binary array.
129-
*Default* is no process.
141+
rate : float
142+
Code rate.
143+
*Default* is 1.
130144
131-
Attributes
132-
----------
133-
modulate : function with same prototype as Modem.modulate
145+
Attributes
146+
----------
147+
modulate : function with same prototype as Modem.modulate
148+
149+
channel : _FlatChannel object
134150
135-
channel : _FlatChannel object
151+
receive : function with prototype receive(y, H, constellation, noise_var) that return a binary array.
152+
y : 1D ndarray
153+
Received complex symbols (shape: num_receive_antennas x 1)
136154
137-
receive : function with prototype receive(y, H, constellation) that return a binary array.
138-
y : 1D ndarray of floats
139-
Received complex symbols (shape: num_receive_antennas x 1)
155+
h : 2D ndarray
156+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
140157
141-
h : 2D ndarray of floats
142-
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
158+
constellation : 1D ndarray
143159
144-
constellation : 1D ndarray of floats
160+
noise_var : positive float
161+
Noise variance
145162
146-
num_bits_symbols : int
163+
num_bits_symbols : int
147164
148-
constellation : array of float or complex
165+
constellation : array of float or complex
149166
150-
Es : float
151-
Average energy per symbols.
152-
*Default* Es=1.
153-
"""
167+
Es : float
168+
Average energy per symbols.
169+
170+
decoder : function with prototype decoder(binary array) that return a binary array.
171+
*Default* is no process.
172+
173+
rate : float
174+
Code rate.
175+
*Default* is 1.
176+
"""
154177

155178
def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, Es=1, decoder=None, rate=1):
156179
self.modulate = modulate
@@ -165,3 +188,65 @@ def __init__(self, modulate, channel, receive, num_bits_symbol, constellation, E
165188
self.decoder = lambda msg: msg
166189
else:
167190
self.decoder = decoder
191+
192+
193+
def idd_decoder(word_size, detector, decoder, n_it):
194+
"""
195+
Produce a decoder function that model the specified MIMO iterative detection and decoding (IDD) process.
196+
The returned function can be used as is to build a working LinkModel object.
197+
198+
Parameters
199+
----------
200+
word_size : positive integer
201+
Size of the words exchanged between the detector and the decoder.
202+
203+
detector : function with prototype detector(y, H, constellation, noise_var, a_priori) that return a LLRs array.
204+
y : 1D ndarray
205+
Received complex symbols (shape: num_receive_antennas x 1).
206+
207+
h : 2D ndarray
208+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas).
209+
210+
constellation : 1D ndarray.
211+
212+
noise_var : positive float
213+
Noise variance.
214+
215+
a_priori : 1D ndarray of floats
216+
A priori as Log-Likelihood Ratios.
217+
218+
decoder : function with the same signature as detector.
219+
220+
n_it : positive integer
221+
Number or iteration during the IDD process.
222+
223+
Returns
224+
-------
225+
decode : function useable as it is to build a LinkModel object that produce a bit array from the parameters
226+
y : 1D ndarray
227+
Received complex symbols (shape: num_receive_antennas x 1).
228+
229+
h : 2D ndarray
230+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas).
231+
232+
constellation : 1D ndarray
233+
234+
noise_var : positive float
235+
Noise variance.
236+
237+
bits_per_send : positive integer
238+
Number or bit send at each symbol vector.
239+
"""
240+
def decode(y, h, constellation, noise_var, a_priori, bits_per_send):
241+
a_priori_decoder = a_priori
242+
nb_vect, nb_rx, nb_tx = h.shape
243+
for iteration in range(n_it):
244+
a_priori_detector = (decoder(a_priori_decoder) - a_priori_decoder)
245+
for i in range(nb_vect):
246+
a_priori_decoder[i * bits_per_send:(i + 1) * bits_per_send] = \
247+
detector(y[i], h[i], constellation, noise_var,
248+
a_priori_detector[i * bits_per_send:(i + 1) * bits_per_send])
249+
a_priori_decoder -= a_priori_detector
250+
return np.signbit(a_priori_decoder + a_priori_detector)
251+
252+
return decode

commpy/modulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,4 @@ def max_log_approx(y, h, noise_var, pts_list, demode):
419419
# Compute LLR
420420
LLR[k] = min(norms0) - min(norms1)
421421

422-
return LLR / (2 * noise_var)
422+
return -LLR / (2 * noise_var)

0 commit comments

Comments
 (0)