"""Регрессии вычислительной архитектуры 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)