Skip to content

Commit 3cec183

Browse files
committed
best_first_detector seems to perform as expected
1 parent 623b7da commit 3cec183

File tree

2 files changed

+77
-42
lines changed

2 files changed

+77
-42
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Available Features
5454
- OFDM Tx/Rx signal processing
5555
- MIMO Maximum Likelihood (ML) Detection.
5656
- MIMO K-best Schnorr-Euchner Detection.
57+
- MIMO Best-First Detection.
5758
- Convert channel matrix to Bit-level representation.
5859
- Computation of LogLikelihood ratio using max-log approximation.
5960

commpy/modulation.py

Lines changed: 76 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
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.
19-
max_log_approx -- Max-log approximation.
18+
best_first_detector -- MIMO Best-First Detection.
19+
bit_lvl_repr -- Bit Level Representation.
20+
max_log_approx -- Max-Log Approximation.
2021
2122
"""
2223
from bisect import insort
@@ -31,7 +32,8 @@
3132

3233
from commpy.utilities import bitarray2dec, dec2bitarray
3334

34-
__all__ = ['PSKModem', 'QAMModem', 'ofdm_tx', 'ofdm_rx', 'mimo_ml', 'kbest', 'bit_lvl_repr', 'max_log_approx']
35+
__all__ = ['PSKModem', 'QAMModem', 'ofdm_tx', 'ofdm_rx', 'mimo_ml', 'kbest', 'best_first_detector',
36+
'bit_lvl_repr', 'max_log_approx']
3537

3638

3739
class Modem:
@@ -347,48 +349,80 @@ def kbest(y, h, constellation, K, noise_var=0, output_type='hard', demode=None):
347349
raise ValueError('output_type must be "hard" or "soft"')
348350

349351

350-
class _Node:
351-
""" Helper data model for best_first_detector. Implement __lt__ aka '<' as required to use bisect.insort. """
352-
def __init__(self, symb_vectors, partial_metrics):
353-
"""
354-
Recursive initializer that build a sequence of siblings.
355-
Inputs are assumed to be ordered based on metric
356-
"""
357-
if len(partial_metrics) == 1:
358-
# There is one node to build
359-
self.symb_vector = symb_vectors.reshape(-1) # Insure that self.symb_vector is a 1d-ndarray
360-
self.partial_metric = partial_metrics
361-
self.best_sibling = None
362-
else:
363-
# Recursive call to build several nodes
364-
self.symb_vector = symb_vectors[:, 0].reshape(-1) # Insure that self.symb_vector is a 1d-ndarray
365-
self.partial_metric = partial_metrics[0]
366-
self.best_sibling = _Node(symb_vectors[:, 1:], partial_metrics[1:])
352+
def best_first_detector(y, h, constellation, stack_size, noise_var, demode, llr_max):
353+
""" MIMO Best-First Detection.
367354
368-
def __lt__(self, other):
369-
return self.partial_metric < other.partial_metric
355+
Reference: G. He, X. Zhang, et Z. Liang, "Algorithm and Architecture of an Efficient MIMO Detector With Cross-Level
356+
Parallel Tree-Search", IEEE Transactions on Very Large Scale Integration (VLSI) Systems, 2019
370357
371-
def expand(self, yt, r, constellation):
372-
""" Build all children and return the best one. constellation must be a numpy ndarray."""
373-
# Construct children's symbol vector
374-
child_size = self.symb_vector.size + 1
375-
children_symb_vectors = empty((child_size, constellation.size), constellation.dtype)
376-
children_symb_vectors[:-1] = self.symb_vector[:, newaxis]
377-
children_symb_vectors[-1] = constellation
378358
379-
# Compute children's partial metric and sort
380-
children_metric = abs(yt[-child_size] - r[-child_size, -child_size:].dot(children_symb_vectors)) ** 2
381-
children_metric += self.partial_metric
382-
ordering = children_metric.argsort()
359+
Parameters
360+
----------
361+
y : 1D ndarray
362+
Received complex symbols (length: num_receive_antennas)
363+
364+
h : 2D ndarray
365+
Channel Matrix (shape: num_receive_antennas x num_transmit_antennas)
383366
384-
# Build children and return the best one
385-
return _Node(children_symb_vectors[:, ordering], children_metric[ordering])
367+
constellation : 1D ndarray of floats
368+
Constellation used to modulate the symbols
386369
370+
stack_size : tuple of integers
371+
Size of each stack (length: num_transmit_antennas - 1)
387372
388-
def best_first_detector(y, h, constellation, stack_size, noise_var, demode, llr_max):
389-
# TODO doc, __all__, readme
390-
# TODO doc details : ref sans a priori, ordre de modulation entier
391-
# TODO TESTS!!!
373+
noise_var : positive float
374+
Noise variance.
375+
*Default* value is 0.
376+
377+
demode : function with prototype binary_word = demode(point)
378+
Function that provide the binary word corresponding to a symbol vector.
379+
380+
llr_max : float
381+
Max value for LLR clipping
382+
383+
Returns
384+
-------
385+
x : 1D ndarray of Log-Likelihood Ratios.
386+
Detected vector (length: num_receive_antennas).
387+
"""
388+
389+
class _Node:
390+
""" Helper data model that implements __lt__ (aka '<') as required to use bisect.insort. """
391+
392+
def __init__(self, symb_vectors, partial_metrics):
393+
"""
394+
Recursive initializer that build a sequence of siblings.
395+
Inputs are assumed to be ordered based on metric
396+
"""
397+
if len(partial_metrics) == 1:
398+
# There is one node to build
399+
self.symb_vector = symb_vectors.reshape(-1) # Insure that self.symb_vector is a 1d-ndarray
400+
self.partial_metric = partial_metrics[0]
401+
self.best_sibling = None
402+
else:
403+
# Recursive call to build several nodes
404+
self.symb_vector = symb_vectors[:, 0].reshape(-1) # Insure that self.symb_vector is a 1d-ndarray
405+
self.partial_metric = partial_metrics[0]
406+
self.best_sibling = _Node(symb_vectors[:, 1:], partial_metrics[1:])
407+
408+
def __lt__(self, other):
409+
return self.partial_metric < other.partial_metric
410+
411+
def expand(self, yt, r, constellation):
412+
""" Build all children and return the best one. constellation must be a numpy ndarray."""
413+
# Construct children's symbol vector
414+
child_size = self.symb_vector.size + 1
415+
children_symb_vectors = empty((child_size, constellation.size), constellation.dtype)
416+
children_symb_vectors[1:] = self.symb_vector[:, newaxis]
417+
children_symb_vectors[0] = constellation
418+
419+
# Compute children's partial metric and sort
420+
children_metric = abs(yt[-child_size] - r[-child_size, -child_size:].dot(children_symb_vectors)) ** 2
421+
children_metric += self.partial_metric
422+
ordering = children_metric.argsort()
423+
424+
# Build children and return the best one
425+
return _Node(children_symb_vectors[:, ordering], children_metric[ordering])
392426

393427
# Extract information from arguments
394428
nb_tx, nb_rx = h.shape
@@ -409,7 +443,7 @@ def best_first_detector(y, h, constellation, stack_size, noise_var, demode, llr_
409443
# Start process by adding the best root's child in the last stack
410444
stacks[-1].append(_Node(empty(0, constellation.dtype), array(0, float, ndmin=1)).expand(yt, r, constellation))
411445

412-
# While there is at least one non-empty stack (exempt first one)
446+
# While there is at least one non-empty stack (exempt the first one)
413447
while any(stacks[1:]):
414448
# Node processing
415449
for idx_next_stack in range(len(stacks) - 1):
@@ -428,7 +462,7 @@ def best_first_detector(y, h, constellation, stack_size, noise_var, demode, llr_
428462
try:
429463
a2 = counter_hyp_metric[idx_this_stack:][map_bit_vector[idx_this_stack:] != bit_vector].max()
430464
except ValueError:
431-
a2 = inf # NumPy can compute max on empty an matrix
465+
a2 = inf # NumPy cannot compute max on an empty matrix
432466
radius = max(counter_hyp_metric[:idx_this_stack].max(), a2)
433467

434468
# Process best sibling
@@ -458,7 +492,7 @@ def best_first_detector(y, h, constellation, stack_size, noise_var, demode, llr_
458492
for idx_next_stack in range(len(stacks) - 1):
459493
del stacks[idx_next_stack + 1][stack_size[idx_next_stack]:]
460494

461-
return (counter_hyp_metric - map_metric) * map_bit_vector / 2 / noise_var
495+
return ((map_metric - counter_hyp_metric) * map_bit_vector).reshape(-1) / 2 / noise_var
462496

463497

464498
def bit_lvl_repr(H, w):

0 commit comments

Comments
 (0)