"""Lab063: автономные проверки локального сопоставления без SDR.""" from __future__ import annotations import numpy as np from experiments import lab063_tx_underrun_localization as lab def test_underrun_scale_is_exact(): result = lab.underrun_scale(16_672) assert result["complex_samples"] == 8_336 assert result["duration_seconds"] == 0.001042 assert result["symbols"] == 521 assert np.isclose(result["fraction_of_packet"], 8_336 / 92_256) def test_normalized_match_recovers_offset_gain_and_low_residual(): random_generator = np.random.default_rng(6301) transmitted = (random_generator.choice((-1.0, 1.0), 2048) * np.exp(1j * np.arange(2048) * 0.01)) gain = 0.35 - 0.2j search = np.concatenate((np.zeros(321, complex), gain * transmitted, np.zeros(400, complex))) result = lab.normalized_match(transmitted, search) assert result["offset"] == 321 assert result["normalized_correlation"] > 0.999999 assert abs(result["gain"] - gain) < 1e-12 assert result["residual_power"] < 1e-20 def test_alignment_localizes_inserted_delay_jump(): random_generator = np.random.default_rng(6302) transmitted = random_generator.choice((-1.0, 1.0), 8192).astype(np.complex64) split, inserted = 4096, 700 received = np.concatenate((np.zeros(1000), transmitted[:split], np.zeros(inserted), transmitted[split:], np.zeros(1000))) rows = lab.track_alignment( lab.ArrayReader(transmitted), lab.ArrayReader(received), 1000, frame_samples=8192, window_samples=512, step_samples=256, search_margin=1000, reliable_packet_start=0, reliable_packet_end=8192, ) verdict = lab.alignment_verdict(rows, inserted) assert verdict["local_delay_discontinuity"] assert any(abs(row["delay_change_samples"]) >= inserted - 1 for row in verdict["jump_candidates"]) def test_alignment_compares_reliable_windows_across_packet_boundary(): rows = [ { "window_index": 4, "tx_sample": 1000, "rx_sample": 2000, "packet_index": 1, "position_inside_packet_samples": 1000, "local_delay_samples": 0, "normalized_correlation": 0.9, "inside_reliable_region": True, "correlation_above_threshold": True, "reliable_for_delay": True, }, { "window_index": 9, "tx_sample": 9000, "rx_sample": 10700, "packet_index": 2, "position_inside_packet_samples": 800, "local_delay_samples": 700, "normalized_correlation": 0.9, "inside_reliable_region": True, "correlation_above_threshold": True, "reliable_for_delay": True, }, ] verdict = lab.alignment_verdict(rows, 700) assert verdict["local_delay_discontinuity"] assert verdict["jump_candidates"][0]["packet_index"] == 2 def test_alignment_discloses_internal_low_correlation_window(): rows = [ { "window_index": 0, "tx_sample": 1000, "rx_sample": 2000, "packet_index": 1, "position_inside_packet_samples": 1000, "local_delay_samples": 0, "normalized_correlation": 0.9, "inside_reliable_region": True, "correlation_above_threshold": True, "reliable_for_delay": True, }, { "window_index": 1, "tx_sample": 1256, "rx_sample": 2956, "packet_index": 1, "position_inside_packet_samples": 1256, "local_delay_samples": 700, "normalized_correlation": 0.1, "inside_reliable_region": True, "correlation_above_threshold": False, "reliable_for_delay": False, }, ] verdict = lab.alignment_verdict(rows, 700) assert verdict["internal_low_correlation_windows"]["count"] == 1 assert "UNDETERMINED_IN_1_INTERNAL_LOW_CORRELATION_WINDOWS" in verdict["local_delay_discontinuity_scope"] assert verdict["local_delay_discontinuity"] is False def test_classification_marks_global_degradation_without_jump(): packets = [{ "packet_index": index, "symbol_error_rate": 0.1, "symbol_rms_deviation": 1.2, "matched_residual_db": -1.0, } for index in range(1, 17)] result = lab.classify(packets, {"local_delay_discontinuity": False}) assert result["quality_distribution"] == "GLOBAL" assert result["primary_cause_status"] == "PARTIALLY_LOCALIZED" def test_classification_marks_mixed_when_jump_and_global_damage_coexist(): packets = [{ "packet_index": index, "symbol_error_rate": 0.1, "symbol_rms_deviation": 1.2, "matched_residual_db": -1.0, } for index in range(1, 17)] result = lab.classify(packets, {"local_delay_discontinuity": True}) assert result["quality_distribution"] == "MIXED"