|
19 | 19 | max_log_approx -- Max-log approximation. |
20 | 20 |
|
21 | 21 | """ |
| 22 | +from bisect import insort |
22 | 23 | from itertools import product |
23 | 24 |
|
24 | 25 | import matplotlib.pyplot as plt |
25 | 26 | from numpy import arange, array, zeros, pi, cos, sin, sqrt, log2, argmin, \ |
26 | 27 | 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 |
28 | 29 | from numpy.fft import fft, ifft |
29 | 30 | from numpy.linalg import qr, norm |
30 | 31 |
|
@@ -346,6 +347,120 @@ def kbest(y, h, constellation, K, noise_var=0, output_type='hard', demode=None): |
346 | 347 | raise ValueError('output_type must be "hard" or "soft"') |
347 | 348 |
|
348 | 349 |
|
| 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 | + |
349 | 464 | def bit_lvl_repr(H, w): |
350 | 465 | """ Bit-level representation of matrix H with weights w. |
351 | 466 |
|
|
0 commit comments