"""Детерминированный блочный перемежитель (запись строками, чтение столбцами). Перемежитель декоррелирует пачки ошибок канала: соседние по входу элементы после перестановки оказываются разнесены, а пачка подряд идущих ошибок канала после обратной перестановки распадается на разрозненные одиночные ошибки, которые свёрточный код исправляет лучше всего. Схема. Для длины ``length`` и числа столбцов ``columns`` элементы условно записываются построчно в матрицу ``rows × columns`` (``rows = ceil(length/columns)``) и считываются по столбцам. Если длина не кратна размеру матрицы, недостающие ячейки последнего ряда просто пропускаются — ни один элемент не теряется и не выдумывается. Результат выражен как перестановка индексов ``permutation`` длиной ``length``: перемежённый поток ``output[k] = input[permutation[k]]``. Обратная операция применяет обратную перестановку и работает как с битами, так и напрямую с soft- значениями BPSK на приёме. Перестановка зависит только от ``length`` и ``columns`` и не зависит от передаваемых данных. """ from __future__ import annotations import numpy as np def _validated_permutation(permutation: np.ndarray, length: int) -> np.ndarray: """Проверить, что перестановка содержит каждый допустимый индекс один раз.""" raw_permutation = np.asarray(permutation) if raw_permutation.ndim != 1: raise ValueError("permutation должен быть одномерным") if len(raw_permutation) != length: raise ValueError("длина values не совпадает с длиной перестановки") if not np.issubdtype(raw_permutation.dtype, np.integer): raise ValueError("permutation должен содержать целые индексы") checked = raw_permutation.astype(np.int64, copy=False) if not np.array_equal(np.sort(checked), np.arange(length, dtype=np.int64)): raise ValueError("permutation не является полной перестановкой индексов") return checked def build_permutation(length: int, columns: int) -> np.ndarray: """Построить перестановку чтения по столбцам для блока длиной ``length``. Возвращает одномерный массив индексов длиной ``length``: ``output[k] = input[permutation[k]]``. Гарантируется, что каждый индекс 0..length-1 встречается ровно один раз. """ if not isinstance(length, (int, np.integer)) or length < 1: raise ValueError("length должен быть положительным целым") if not isinstance(columns, (int, np.integer)) or columns < 1: raise ValueError("columns должен быть положительным целым") columns = int(columns) length = int(length) rows = (length + columns - 1) // columns permutation = [ row * columns + column for column in range(columns) for row in range(rows) if row * columns + column < length ] result = np.asarray(permutation, dtype=np.int64) assert len(result) == length return result def interleave(values: np.ndarray, permutation: np.ndarray) -> np.ndarray: """Применить перестановку: ``output[k] = values[permutation[k]]``.""" values = np.asarray(values) if values.ndim != 1: raise ValueError("values должен быть одномерным") checked_permutation = _validated_permutation(permutation, len(values)) return values[checked_permutation] def deinterleave(values: np.ndarray, permutation: np.ndarray) -> np.ndarray: """Обратить перестановку без потери типа (работает и с soft-значениями).""" values = np.asarray(values) if values.ndim != 1: raise ValueError("values должен быть одномерным") checked_permutation = _validated_permutation(permutation, len(values)) restored = np.empty_like(values) restored[checked_permutation] = values return restored