101 lines
4.5 KiB
Python
101 lines
4.5 KiB
Python
"""Быстрые тесты диагностических примитивов Lab058."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from experiments import lab058_abab_symbol_quality as lab058
|
|
|
|
|
|
def test_training_and_validation_tiles_are_disjoint_and_complete():
|
|
for count in (4096, 102400):
|
|
train = lab058.training_mask(count)
|
|
assert train.dtype == bool and train.size == count
|
|
assert np.all((lab058.tile_ids(count)[train] % 2) == 0)
|
|
assert np.all((lab058.tile_ids(count)[~train] % 2) == 1)
|
|
|
|
|
|
def test_guarded_tiles_are_disjoint_and_exclude_boundaries():
|
|
for count in (4096, 102400):
|
|
train, validation = lab058.guarded_tile_masks(count)
|
|
assert not np.any(train & validation)
|
|
assert train.sum() == validation.sum() == count // 2 - 16 * 32
|
|
|
|
|
|
@pytest.mark.parametrize("symbol_rate", [20_000, 500_000])
|
|
def test_cfo_gain_fit_recovers_known_symbol_model(symbol_rate):
|
|
expected = lab058.expected_symbols(symbol_rate)[:4096]
|
|
indexes = np.arange(len(expected))
|
|
cfo = 1.75 * symbol_rate / 20_000
|
|
h = .7 - .25j
|
|
samples = h * expected * np.exp(2j * np.pi * cfo * (indexes + .5) / symbol_rate)
|
|
found_cfo, found_h, coherence = lab058.fit_cfo_gain(samples, expected, indexes, symbol_rate)
|
|
assert found_cfo == pytest.approx(cfo, abs=1e-8)
|
|
assert found_h == pytest.approx(h, abs=1e-10)
|
|
assert coherence == pytest.approx(1.0)
|
|
|
|
|
|
def test_cfo_gain_subset_keeps_original_tile_time_axis():
|
|
symbol_rate = 500_000
|
|
full = lab058.expected_symbols(symbol_rate)
|
|
indexes = np.flatnonzero(lab058.training_mask(len(full)))[::31]
|
|
cfo, h = 3.25, .4 + .2j
|
|
samples = h * full[indexes] * np.exp(2j * np.pi * cfo * (indexes + .5) / symbol_rate)
|
|
found, gain, _ = lab058.fit_cfo_gain(samples, full[indexes], indexes, symbol_rate, len(full))
|
|
assert found == pytest.approx(cfo, abs=1e-8)
|
|
assert gain == pytest.approx(h, abs=1e-9)
|
|
|
|
|
|
def test_oracle_estimate_does_not_read_guarded_validation_symbols():
|
|
symbol_rate = 500_000
|
|
expected = lab058.expected_symbols(symbol_rate)[:4096]
|
|
samples_per_symbol = lab058.SAMPLE_RATE // symbol_rate
|
|
values = lab058.shaping.shape_frame_like_lab042(expected, samples_per_symbol)
|
|
values *= np.exp(2j * np.pi * lab058.CARRIER_HZ * np.arange(len(values)) / lab058.SAMPLE_RATE)
|
|
changed = values.copy()
|
|
validation = np.flatnonzero(lab058.guarded_tile_masks(len(expected))[1])
|
|
centers = 21 * samples_per_symbol + validation * samples_per_symbol
|
|
for center in centers:
|
|
changed[center:center + samples_per_symbol] += 3 - 2j
|
|
frontend = lab058.lab057.Frontend(symbol_rate)
|
|
first_matched = lab058.oracle_matched_filter(values, frontend.taps, symbol_rate, len(expected))
|
|
second_matched = lab058.oracle_matched_filter(changed, frontend.taps, symbol_rate, len(expected))
|
|
first = lab058.oracle_estimate(first_matched, frontend.nominal, expected, symbol_rate)
|
|
second = lab058.oracle_estimate(second_matched, frontend.nominal, expected, symbol_rate)
|
|
assert first["timing_fraction"] == second["timing_fraction"]
|
|
assert first["cfo_hz"] == pytest.approx(second["cfo_hz"], abs=1e-5)
|
|
assert first["h"] == pytest.approx(second["h"], abs=1e-7)
|
|
assert first["train_coherence"] == pytest.approx(second["train_coherence"], abs=1e-8)
|
|
|
|
|
|
def test_evm_and_margin_formula_on_exact_symbols():
|
|
expected = lab058.expected_symbols(20_000)
|
|
indexes = np.arange(len(expected))
|
|
h = .4 + .3j
|
|
estimate = {"symbols": h * expected, "h": h, "cfo_hz": 0.0}
|
|
result = lab058.evaluate(estimate, expected, 20_000, np.ones(len(expected), bool))
|
|
assert result["errors"] == 0 and result["ber"] == 0
|
|
assert result["evm_rms"] < 1e-14
|
|
assert result["margin_median"] == pytest.approx(1.0)
|
|
|
|
|
|
def test_isi_fit_localizes_single_main_tap():
|
|
expected = lab058.expected_symbols(20_000)
|
|
h = .2 - .1j
|
|
estimate = {"symbols": h * expected, "h": h, "cfo_hz": 0.0}
|
|
result, taps = lab058.isi_metrics(estimate, expected, 20_000)
|
|
assert abs(taps[5] - h) < 1e-12
|
|
assert result["adjacent_taps_power"] < result["main_tap_power"] * 1e-20
|
|
|
|
|
|
def test_averaged_transfer_estimate_is_flat_for_flat_synthetic_channel():
|
|
random_generator = np.random.default_rng(5802)
|
|
count = 131072
|
|
tx = (random_generator.normal(size=count) + 1j * random_generator.normal(size=count)).astype(np.complex64)
|
|
gain = .3 - .2j
|
|
result, _ = lab058.channel_metrics(tx, gain * tx, 500_000, 0.0)
|
|
assert result["stable"]
|
|
assert result["median_coherence"] > .999
|
|
assert result["amplitude_ripple_p95_p05_db"] < .01
|