Skip to content

Commit 623b7da

Browse files
committed
[Not working] best_first_detector is written but need debug
1 parent 37432c8 commit 623b7da

File tree

1 file changed

+116
-1
lines changed

1 file changed

+116
-1
lines changed

commpy/modulation.py

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
max_log_approx -- Max-log approximation.
2020
2121
"""
22+
from bisect import insort
2223
from itertools import product
2324

2425
import matplotlib.pyplot as plt
2526
from numpy import arange, array, zeros, pi, cos, sin, sqrt, log2, argmin, \
2627
hstack, repeat, tile, dot, shape, concatenate, exp, \
27-
log, vectorize, empty, eye, kron, inf
28+
log, vectorize, empty, eye, kron, inf, full, abs, newaxis, minimum, clip
2829
from numpy.fft import fft, ifft
2930
from numpy.linalg import qr, norm
3031

@@ -346,6 +347,120 @@ def kbest(y, h, constellation, K, noise_var=0, output_type='hard', demode=None):
346347
raise ValueError('output_type must be "hard" or "soft"')
347348

348349

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:])
367+
368+
def __lt__(self, other):
369+
return self.partial_metric < other.partial_metric
370+
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
378+
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()
383+
384+
# Build children and return the best one
385+
return _Node(children_symb_vectors[:, ordering], children_metric[ordering])
386+
387+
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!!!
392+
393+
# Extract information from arguments
394+
nb_tx, nb_rx = h.shape
395+
constellation = array(constellation)
396+
m = constellation.size
397+
modulation_order = int(log2(m))
398+
399+
# QR decomposition
400+
q, r = qr(h)
401+
yt = q.conj().T.dot(y)
402+
403+
# Initialisation
404+
map_metric = inf
405+
map_bit_vector = None
406+
counter_hyp_metric = full((nb_tx, modulation_order), inf)
407+
stacks = tuple([] for _ in range(nb_tx))
408+
409+
# Start process by adding the best root's child in the last stack
410+
stacks[-1].append(_Node(empty(0, constellation.dtype), array(0, float, ndmin=1)).expand(yt, r, constellation))
411+
412+
# While there is at least one non-empty stack (exempt first one)
413+
while any(stacks[1:]):
414+
# Node processing
415+
for idx_next_stack in range(len(stacks) - 1):
416+
try:
417+
idx_this_stack = idx_next_stack + 1
418+
best_node = stacks[idx_this_stack].pop(0)
419+
420+
# Update search radius
421+
if map_bit_vector is None:
422+
radius = inf # No leaf has been reached yet so we keep all nodes
423+
else:
424+
bit_vector = demode(best_node.symb_vector).reshape(-1, modulation_order)
425+
bit_vector[bit_vector == 0] = -1
426+
427+
# Select the counter hyp metrics that could be affected by this node. Details: eq. (14)-(16) in [1].
428+
try:
429+
a2 = counter_hyp_metric[idx_this_stack:][map_bit_vector[idx_this_stack:] != bit_vector].max()
430+
except ValueError:
431+
a2 = inf # NumPy can compute max on empty an matrix
432+
radius = max(counter_hyp_metric[:idx_this_stack].max(), a2)
433+
434+
# Process best sibling
435+
if best_node.best_sibling is not None and best_node.best_sibling.partial_metric <= radius:
436+
insort(stacks[idx_this_stack], best_node.best_sibling)
437+
438+
# Process children
439+
best_child = best_node.expand(yt, r, constellation)
440+
if best_child.partial_metric <= radius:
441+
insort(stacks[idx_next_stack], best_child)
442+
except IndexError: # Raised when popping an empty stack
443+
pass
444+
445+
# LLR update if there is a new leaf
446+
if stacks[0]:
447+
if stacks[0][0].partial_metric < map_metric:
448+
minimum(counter_hyp_metric, map_metric, out=counter_hyp_metric)
449+
map_metric = stacks[0][0].partial_metric
450+
map_bit_vector = demode(stacks[0][0].symb_vector).reshape(-1, modulation_order)
451+
map_bit_vector[map_bit_vector == 0] = -1
452+
else:
453+
minimum(counter_hyp_metric, stacks[0][0].partial_metric, out=counter_hyp_metric)
454+
clip(counter_hyp_metric, map_metric - llr_max, map_metric + llr_max, counter_hyp_metric)
455+
456+
# Trimming stack according to requested max stack size
457+
del stacks[0][0:] # there is no stack for the leafs
458+
for idx_next_stack in range(len(stacks) - 1):
459+
del stacks[idx_next_stack + 1][stack_size[idx_next_stack]:]
460+
461+
return (counter_hyp_metric - map_metric) * map_bit_vector / 2 / noise_var
462+
463+
349464
def bit_lvl_repr(H, w):
350465
""" Bit-level representation of matrix H with weights w.
351466

0 commit comments

Comments
 (0)