82 lines
3.6 KiB
Python
82 lines
3.6 KiB
Python
"""Программные варианты R=2/3 и 3/4 на базе штатного кода R=1/2.
|
||
|
||
Прокалывание удаляет известные позиции кодового слова. При декодировании
|
||
эти позиции имеют нулевую мягкую метрику (стирание), после чего работает
|
||
штатный терминированный декодер Витерби.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from functools import lru_cache
|
||
import numpy as np
|
||
|
||
from protocol import convolutional_fec
|
||
|
||
|
||
PUNCTURE_PATTERNS: dict[str, tuple[int, ...]] = {
|
||
"1/2": (1, 1),
|
||
"2/3": (1, 1, 1, 0),
|
||
"3/4": (1, 1, 1, 0, 0, 1),
|
||
}
|
||
|
||
|
||
@lru_cache(maxsize=32)
|
||
def _mask(mother_bit_count: int, rate: str) -> np.ndarray:
|
||
if rate not in PUNCTURE_PATTERNS:
|
||
raise ValueError("неизвестная скорость кода")
|
||
if mother_bit_count < 0:
|
||
raise ValueError("отрицательная длина кодового слова")
|
||
pattern = np.asarray(PUNCTURE_PATTERNS[rate], dtype=bool)
|
||
mask = np.resize(pattern, mother_bit_count)
|
||
mask.flags.writeable = False
|
||
return mask
|
||
|
||
|
||
def coded_bit_count(information_bit_count: int, rate: str) -> int:
|
||
"""Точное число передаваемых битов, включая шесть хвостовых битов кода."""
|
||
mother_count = convolutional_fec.encoded_bit_count(information_bit_count)
|
||
if rate not in PUNCTURE_PATTERNS:
|
||
raise ValueError("неизвестная скорость кода")
|
||
pattern = PUNCTURE_PATTERNS[rate]
|
||
periods, remaining = divmod(mother_count, len(pattern))
|
||
return periods * sum(pattern) + sum(pattern[:remaining])
|
||
|
||
|
||
def effective_rate(information_bit_count: int, rate: str) -> float:
|
||
"""Отношение полезных битов к реально передаваемым кодовым битам."""
|
||
if information_bit_count <= 0:
|
||
raise ValueError("число полезных битов должно быть положительным")
|
||
return information_bit_count / coded_bit_count(information_bit_count, rate)
|
||
|
||
|
||
def encode(information_bits: np.ndarray, rate: str) -> np.ndarray:
|
||
"""Сначала применить штатный код, затем удалить позиции по маске."""
|
||
mother_bits = convolutional_fec.encode(information_bits)
|
||
return mother_bits[_mask(len(mother_bits), rate)]
|
||
|
||
|
||
def depuncture_soft(
|
||
soft_values: np.ndarray, information_bit_count: int, rate: str
|
||
) -> np.ndarray:
|
||
"""Вернуть стёртые позиции с нулевой мягкой метрикой."""
|
||
values = np.asarray(soft_values, dtype=np.float64)
|
||
if values.ndim != 1 or not np.all(np.isfinite(values)):
|
||
raise ValueError("мягкие значения должны быть конечным одномерным массивом")
|
||
mother_count = convolutional_fec.encoded_bit_count(information_bit_count)
|
||
mask = _mask(mother_count, rate)
|
||
if len(values) != int(np.count_nonzero(mask)):
|
||
raise ValueError("неверная длина проколотого кодового слова")
|
||
restored = np.zeros(mother_count, dtype=np.float64)
|
||
restored[mask] = values
|
||
return restored
|
||
|
||
|
||
def decode_soft(
|
||
soft_values: np.ndarray, information_bit_count: int, rate: str
|
||
) -> np.ndarray:
|
||
"""Декодировать через существующий 64-состояний декодер Витерби."""
|
||
return convolutional_fec.viterbi_decode_soft(
|
||
depuncture_soft(soft_values, information_bit_count, rate),
|
||
information_bit_count,
|
||
)
|