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"""
2223from bisect import insort
3132
3233from 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
3739class 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
464498def bit_lvl_repr (H , w ):
0 commit comments