Files
SDR-Rover/experiments/lab053_viterbi.py

99 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Lab053: те же переходы soft Viterbi, циклы компилируются только для CPU."""
from __future__ import annotations
from pathlib import Path
import sys
import time
import numpy as np
if __package__ in (None, ""):
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from experiments import lab051_streaming_receiver as previous
runtime = Path(__file__).resolve().parents[1] / ".lab053_cpu_runtime/packages"
if runtime.is_dir():
sys.path.insert(0, str(runtime))
try:
from numba import njit
except ImportError:
njit = None
conv = previous.lab045.conv
symbols = conv._BRANCH_SYMBOLS
CODES = (2 * (symbols[:, :, 0] > 0) + (symbols[:, :, 1] > 0)).astype(np.intp)
PREDECESSORS = conv._PREDECESSORS.copy()
CODES.flags.writeable = PREDECESSORS.flags.writeable = False
def trellis_decode(metrics, predecessors, codes):
"""Порядок сложений исходный; строгое < сохраняет tie в ветвь 0.
Не объединяем несколько шагов: это могло бы изменить решение при
равных округлённых суммах. Старт/финиш 0, 64 состояния, хвост 6 бит.
"""
step_count = metrics.shape[0]
path = np.full(64, 1e18, dtype=np.float64)
path[0] = 0.0
next_path = np.empty(64, np.float64)
choices = np.empty((step_count, 64), np.uint8)
for step in range(step_count):
for state in range(64):
zero = path[predecessors[state, 0]] + metrics[step, codes[state, 0]]
one = path[predecessors[state, 1]] + metrics[step, codes[state, 1]]
take_one = one < zero
choices[step, state] = take_one
next_path[state] = one if take_one else zero
path, next_path = next_path, path
decoded = np.empty(step_count, np.uint8)
state = 0
for step in range(step_count - 1, -1, -1):
decoded[step] = state >> 5
state = predecessors[state, choices[step, state]]
assert state == 0
return decoded[:-6]
compiled_trellis = njit(cache=True, nogil=True, fastmath=False)(trellis_decode) if njit is not None else None
def viterbi_decode_soft(soft_values, information_bit_count):
if compiled_trellis is None:
return previous.fast_viterbi(soft_values, information_bit_count)
if not isinstance(information_bit_count, (int, np.integer)) or information_bit_count <= 0:
return previous.ORIGINAL_VITERBI(soft_values, information_bit_count)
values = np.asarray(soft_values, dtype=np.float64)
if (values.ndim != 1 or values.size != conv.encoded_bit_count(information_bit_count)
or not np.all(np.isfinite(values))):
return previous.ORIGINAL_VITERBI(soft_values, information_bit_count)
received = values.reshape(int(information_bit_count) + 6, 2)
negative = (received + 1.0) ** 2
positive = (received - 1.0) ** 2
metrics = np.column_stack((negative[:, 0] + negative[:, 1], negative[:, 0] + positive[:, 1],
positive[:, 0] + negative[:, 1], positive[:, 0] + positive[:, 1]))
return compiled_trellis(metrics, PREDECESSORS, CODES)
def warmup() -> dict:
started = time.perf_counter()
values = np.ones(conv.encoded_bit_count(8), np.float64)
assert np.array_equal(viterbi_decode_soft(values, 8), previous.fast_viterbi(values, 8))
return {"backend": "NUMBA_CPU" if compiled_trellis is not None else "NUMPY_LAB051",
"warmup_seconds": time.perf_counter() - started, "fastmath": False, "gpu_used": False}
def run_functional_tests() -> None:
values = np.zeros(conv.encoded_bit_count(8), np.float64)
assert np.array_equal(viterbi_decode_soft(values, 8), previous.fast_viterbi(values, 8))
def main() -> None:
print(warmup())
run_functional_tests()
if __name__ == "__main__":
main()