initial commit

This commit is contained in:
LittleSam129
2026-07-20 13:58:58 +03:00
commit acf7a76546
104 changed files with 27464 additions and 0 deletions

View File

@@ -0,0 +1,468 @@
"""
Lab017. Фильтрация Eb/N0 и гистерезис режимов.
Эксперимент сравнивает два адаптера:
1. Наивный адаптер:
переключает режим при каждом пересечении порога.
2. Устойчивый адаптер:
использует:
- экспоненциальное усреднение;
- гистерезис;
- подтверждение повышения;
- аварийное быстрое понижение.
Профиль Eb/N0 специально содержит колебания
около порогов переключения.
"""
from csv import DictWriter
from pathlib import Path
import matplotlib.pyplot as plt
from protocol.link_controller import (
FRAGMENT_MODE_1024,
IMAGE_OFF,
LinkModeController,
)
# ============================================================
# Настройки
# ============================================================
OUTPUT_DIRECTORY = Path(
"data/processed/lab017"
)
OUTPUT_DIRECTORY.mkdir(
parents=True,
exist_ok=True,
)
CSV_PATH = (
OUTPUT_DIRECTORY
/ "lab017_hysteresis_results.csv"
)
GRAPH_PATH = (
OUTPUT_DIRECTORY
/ "lab017_hysteresis_comparison.png"
)
# ============================================================
# Профиль качества канала
# ============================================================
EB_N0_PROFILE_DB = [
10.2,
9.7,
9.4,
9.6,
9.3,
9.7,
9.2,
9.6,
9.0,
8.7,
8.4,
8.6,
8.3,
8.7,
8.2,
8.6,
8.0,
7.8,
7.5,
7.7,
7.4,
7.8,
7.2,
6.8,
6.5,
6.3,
6.6,
6.2,
6.7,
6.1,
5.5,
6.0,
6.4,
6.7,
6.9,
7.1,
7.3,
8.0,
8.8,
9.0,
9.4,
9.8,
10.0,
10.2,
10.1,
]
# ============================================================
# Наивное решение без гистерезиса
# ============================================================
def choose_naive_mode(
eb_n0_db: float,
) -> int:
"""
Немедленно выбрать режим по одному измерению.
Гистерезиса и фильтрации нет.
"""
if eb_n0_db < 6.5:
return 0
if eb_n0_db < 8.5:
return 128
if eb_n0_db < 9.5:
return 512
return 1024
# ============================================================
# Запуск устойчивого контроллера
# ============================================================
controller = LinkModeController(
initial_mode=FRAGMENT_MODE_1024,
filter_alpha=0.35,
upgrade_confirmation_count=3,
minimum_hold_steps=2,
)
results = []
for measured_eb_n0_db in EB_N0_PROFILE_DB:
stable_result = controller.update(
measured_eb_n0_db
)
naive_mode = choose_naive_mode(
measured_eb_n0_db
)
results.append(
{
"step": stable_result.step,
"measured_eb_n0_db": (
stable_result.measured_eb_n0_db
),
"filtered_eb_n0_db": (
stable_result.filtered_eb_n0_db
),
"naive_mode": naive_mode,
"stable_mode": (
stable_result.selected_mode
),
"mode_changed": (
stable_result.mode_changed
),
"pending_upgrade_mode": (
stable_result.pending_upgrade_mode
),
"pending_upgrade_count": (
stable_result.pending_upgrade_count
),
"reason": stable_result.reason,
}
)
# ============================================================
# Число переключений
# ============================================================
def count_mode_switches(
modes: list[int],
) -> int:
"""
Посчитать число изменений режима.
"""
return sum(
previous_mode != current_mode
for previous_mode, current_mode in zip(
modes,
modes[1:],
)
)
naive_modes = [
result["naive_mode"]
for result in results
]
stable_modes = [
result["stable_mode"]
for result in results
]
naive_switch_count = count_mode_switches(
naive_modes
)
stable_switch_count = count_mode_switches(
stable_modes
)
# ============================================================
# Вывод результатов
# ============================================================
print(
"=== Lab017. Гистерезис режима канала ==="
)
print("\nРезультаты по шагам:")
print(
f"{'Шаг':>5}"
f"{'Eb/N0':>10}"
f"{'Фильтр':>11}"
f"{'Наивный':>11}"
f"{'Устойчивый':>13}"
f"{'Изменён':>10}"
f"{'Причина':>45}"
)
print("-" * 105)
for result in results:
print(
f"{result['step']:>5}"
f"{result['measured_eb_n0_db']:>7.1f} дБ"
f"{result['filtered_eb_n0_db']:>8.2f} дБ"
f"{result['naive_mode']:>11}"
f"{result['stable_mode']:>13}"
f"{str(result['mode_changed']):>10}"
f" {result['reason']}"
)
print("\nЧисло переключений:")
print(
"Наивный адаптер:",
naive_switch_count,
)
print(
"Устойчивый адаптер:",
stable_switch_count,
)
print(
"Предотвращено лишних переключений:",
naive_switch_count - stable_switch_count,
)
# ============================================================
# Сохранение CSV
# ============================================================
with CSV_PATH.open(
"w",
newline="",
encoding="utf-8-sig",
) as csv_file:
writer = DictWriter(
csv_file,
fieldnames=list(
results[0].keys()
),
)
writer.writeheader()
writer.writerows(results)
# ============================================================
# График
# ============================================================
steps = [
result["step"]
for result in results
]
measured_values = [
result["measured_eb_n0_db"]
for result in results
]
filtered_values = [
result["filtered_eb_n0_db"]
for result in results
]
figure, axes = plt.subplots(
2,
1,
figsize=(12, 9),
sharex=True,
)
# ------------------------------------------------------------
# Верхний график: Eb/N0
# ------------------------------------------------------------
axes[0].plot(
steps,
measured_values,
marker=".",
label="Измеренное Eb/N0",
)
axes[0].plot(
steps,
filtered_values,
linewidth=2,
label="Фильтрованное Eb/N0",
)
axes[0].axhline(
6.5,
linestyle=":",
label="Порог наивного адаптера",
)
axes[0].axhline(
8.5,
linestyle=":",
)
axes[0].axhline(
9.5,
linestyle=":",
)
axes[0].set_ylabel(
"Eb/N0, дБ"
)
axes[0].set_title(
"Фильтрация измерения качества канала"
)
axes[0].grid(
True
)
axes[0].legend()
# ------------------------------------------------------------
# Нижний график: режимы
# ------------------------------------------------------------
axes[1].step(
steps,
naive_modes,
where="mid",
marker=".",
label="Наивный адаптер",
)
axes[1].step(
steps,
stable_modes,
where="mid",
marker="o",
label="Гистерезис и фильтрация",
)
axes[1].set_yticks(
[
0,
128,
512,
1024,
],
[
"IMAGE OFF",
"128 B",
"512 B",
"1024 B",
],
)
axes[1].set_xlabel(
"Шаг времени"
)
axes[1].set_ylabel(
"Режим"
)
axes[1].set_title(
"Сравнение переключений режимов"
)
axes[1].grid(
True
)
axes[1].legend()
figure.tight_layout()
figure.savefig(
GRAPH_PATH,
dpi=160,
)
plt.close(
figure
)
# ============================================================
# Проверки
# ============================================================
assert results
assert IMAGE_OFF in stable_modes
assert FRAGMENT_MODE_1024 in stable_modes
assert stable_switch_count < naive_switch_count
assert CSV_PATH.exists()
assert GRAPH_PATH.exists()
print("\nCSV:")
print(CSV_PATH)
print("\nГрафик:")
print(GRAPH_PATH)
print(
"\nПроверка пройдена: "
"гистерезис уменьшил количество переключений."
)