161 lines
7.9 KiB
Python
161 lines
7.9 KiB
Python
"""Регрессии вычислительной архитектуры Lab051, без файлов/оборудования."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
from dataclasses import replace
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from scipy.signal import resample_poly
|
|
|
|
from experiments import lab051_streaming_receiver as lab
|
|
from experiments.lab050_streaming_receiver import StreamingReceiver as PreviousReceiver
|
|
from experiments.lab050_streaming_receiver import streaming_mode
|
|
|
|
|
|
@pytest.mark.parametrize("seed", range(8))
|
|
def test_all_timing_offsets_preserved_including_negative_start(seed):
|
|
random_generator = np.random.default_rng(seed)
|
|
samples = (random_generator.normal(size=23616) + 1j * random_generator.normal(size=23616)).astype(np.complex64)
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
expected = lab.iq.radio.bpsk_modulate(lab.lab045.lab044.preamble_bits())
|
|
if seed % 2:
|
|
samples[416 + 47:416 + 47 + len(expected)*16:16] += expected * 3
|
|
old = lab.ORIGINAL_TIMING(samples, 416, expected)
|
|
assert lab.fast_preamble_timing(samples, 416, expected) == old
|
|
|
|
|
|
def test_zero_timing_tie_is_same_first_offset():
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
expected = lab.iq.radio.bpsk_modulate(lab.lab045.lab044.preamble_bits())
|
|
assert lab.fast_preamble_timing(np.zeros(23616, np.complex64), 416, expected) == -800
|
|
|
|
|
|
@pytest.mark.parametrize("value", [1, 1j, 1+1j, 44])
|
|
def test_equal_nonzero_timing_scores_preserve_original_tie(value):
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
expected = lab.iq.radio.bpsk_modulate(lab.lab045.lab044.preamble_bits())
|
|
samples = np.full(23616, value, np.complex64)
|
|
assert lab.fast_preamble_timing(samples, 416, expected) == lab.ORIGINAL_TIMING(samples,416,expected)
|
|
|
|
|
|
@pytest.mark.parametrize("value", [1, 1j, 44])
|
|
def test_refinement_constant_iq_preserves_zero_after_local_dc(value):
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
samples=np.full(100000,value,np.complex64)
|
|
front=lab.BlockFrontEnd(samples,65536,Counter())
|
|
window=samples[1000:28456]
|
|
assert front.refine(window,front.reference,2000)==lab.ORIGINAL_REFINE(window,front.reference,2000)==0
|
|
|
|
|
|
def test_refinement_equal_nonzero_peaks_preserve_first_maximum():
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
samples=np.zeros(100000,np.complex64)
|
|
samples[1200]=samples[2200]=1
|
|
front=lab.BlockFrontEnd(samples,65536,Counter())
|
|
reference=np.zeros(front.reference_length,np.complex64)
|
|
reference[0],reference[1]=1,-1
|
|
front.reference=reference
|
|
window=samples[1000:28456]
|
|
assert front.refine(window,reference,2000)==lab.ORIGINAL_REFINE(window,reference,2000)==199
|
|
|
|
|
|
@pytest.mark.parametrize("seed", range(5))
|
|
def test_shared_matched_preserves_local_dc_and_both_zero_padded_edges(seed):
|
|
random_generator = np.random.default_rng(seed)
|
|
values = (random_generator.normal(size=110000) + 1j * random_generator.normal(size=110000) + 4 + 3j).astype(np.complex64)
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
front = lab.BlockFrontEnd(values, 70000, Counter())
|
|
segment = values[5000 + seed:5000 + seed + 23456]
|
|
expected, nominal = lab.ORIGINAL_MATCHED(segment, 0, len(segment))
|
|
actual, actual_nominal = front.matched_window(segment, 0, len(segment))
|
|
assert actual_nominal == nominal
|
|
np.testing.assert_allclose(actual, expected, atol=2e-5, rtol=2e-5)
|
|
|
|
|
|
def test_reused_decimation_is_bit_exact_for_moving_and_truncated_windows():
|
|
random_generator = np.random.default_rng(51001)
|
|
values = (random_generator.normal(size=1800000) + 1j * random_generator.normal(size=1800000)).astype(np.complex64)
|
|
cache = lab.DecimationCache(8)
|
|
for base in range(0, len(values) - 10000, 65536):
|
|
window = values[base:base + 400123]
|
|
expected = resample_poly(window, 1, 8, window=("kaiser", 8))
|
|
np.testing.assert_array_equal(cache.get(window, base), expected)
|
|
assert cache.values.size <= 50016
|
|
assert cache.reused_samples > 1000000
|
|
|
|
|
|
@pytest.mark.parametrize("seed,count", [(0, 1), (1, 32), (2, 244*8), (3, 268*8), (4, 1024*8)])
|
|
def test_viterbi_exact_bits_for_noisy_metrics_and_ties(seed, count):
|
|
random_generator = np.random.default_rng(seed)
|
|
for values in (np.zeros(lab.lab045.conv.encoded_bit_count(count)),
|
|
random_generator.normal(size=lab.lab045.conv.encoded_bit_count(count)),
|
|
random_generator.choice([-1., 1.], size=lab.lab045.conv.encoded_bit_count(count))):
|
|
np.testing.assert_array_equal(lab.fast_viterbi(values, count), lab.ORIGINAL_VITERBI(values, count))
|
|
|
|
|
|
@pytest.mark.parametrize("values,count", [(np.array([np.nan, 0.]), 1), (np.zeros((2,2)), 1),
|
|
(np.zeros(12), 1), (np.zeros(12), 0), (np.zeros(12), 1.5)])
|
|
def test_viterbi_rejects_same_invalid_input(values, count):
|
|
with pytest.raises((ValueError, TypeError)):
|
|
lab.fast_viterbi(values, count)
|
|
|
|
|
|
def signal_with_unknown_offsets(seed=51):
|
|
random_generator = np.random.default_rng(seed)
|
|
plan = lab.lab045.build_transmission_plan(bytes(range(256)))
|
|
values = lab.phy.build_parameterized_tx_complex(plan, lab.phy.PhyMode(500000)).astype(np.complex64)
|
|
values *= np.exp(1j * (0.73 + 2*np.pi*10*np.arange(len(values))/8000000)).astype(np.complex64)
|
|
values += (random_generator.normal(size=len(values)) + 1j*random_generator.normal(size=len(values))).astype(np.complex64)
|
|
return np.concatenate((np.zeros(63217,np.complex64), values, np.zeros(450000,np.complex64)))
|
|
|
|
|
|
@pytest.mark.parametrize("block", [32749, 65536])
|
|
def test_independent_stream_packets_equal_old_decoder_across_block_boundaries(block):
|
|
samples = signal_with_unknown_offsets()
|
|
results = []
|
|
for receiver in (PreviousReceiver(500000), lab.StreamingReceiver()):
|
|
output = []
|
|
for start in range(0,len(samples),block):
|
|
output.extend(receiver.push_iq_block(samples[start:start + block]))
|
|
assert [item.payload for item in output] == [bytes(range(256))]
|
|
output.extend(receiver.finish())
|
|
results.append([(item.start_sample,item.end_sample,item.payload) for item in output])
|
|
assert results[0] == results[1]
|
|
|
|
|
|
def test_invalid_push_and_finish_preserve_bounded_state():
|
|
receiver = lab.StreamingReceiver()
|
|
for invalid in (np.array([complex(float("nan"),0)]), np.zeros((1,2)), np.zeros(65537)):
|
|
with pytest.raises(ValueError): receiver.push_iq_block(invalid)
|
|
assert receiver.total_samples == 0
|
|
for unused in range(100):
|
|
assert receiver.push_iq_block(np.zeros(65536, np.complex64)) == []
|
|
assert receiver.buffer.size <= receiver.buffer_limit_samples
|
|
assert receiver.decimator.values.size <= (receiver.buffer_limit_samples + 7)//8
|
|
assert receiver.finish() == []
|
|
assert receiver.buffer.size == receiver.decimator.values.size == 0
|
|
with pytest.raises(RuntimeError): receiver.finish()
|
|
with pytest.raises(RuntimeError): receiver.push_iq_block(np.zeros(1,np.complex64))
|
|
|
|
|
|
def test_truncated_frame_cannot_reach_crc_pass():
|
|
samples = signal_with_unknown_offsets()[:100000]
|
|
receiver = lab.StreamingReceiver()
|
|
output = []
|
|
for start in range(0,len(samples),65536):
|
|
output.extend(receiver.push_iq_block(samples[start:start+65536]))
|
|
assert output + receiver.finish() == []
|
|
|
|
|
|
def test_context_restores_functions_even_on_exception():
|
|
originals = (lab.lab043._matched_waveform, lab.iq._extract_soft_window,
|
|
lab.lab045.conv.viterbi_decode_soft, lab.offline._correlation_scores)
|
|
with streaming_mode(lab.phy.PhyMode(500000)):
|
|
front = lab.BlockFrontEnd(np.zeros(100000,np.complex64),65536,Counter())
|
|
with pytest.raises(RuntimeError):
|
|
with lab.optimized_context(front): raise RuntimeError("test")
|
|
assert originals == (lab.lab043._matched_waveform, lab.iq._extract_soft_window,
|
|
lab.lab045.conv.viterbi_decode_soft, lab.offline._correlation_scores)
|