feat: add frame synchronization prototype with tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
108
model/test_frame_sync.py
Normal file
108
model/test_frame_sync.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for the frame synchronization prototype (frame_sync.py).
|
||||
|
||||
Tests cover stream generation (length, offsets) and syndrome screening
|
||||
(correct offset identification, wrong offset rejection).
|
||||
|
||||
Run:
|
||||
python3 -m pytest model/test_frame_sync.py -v
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from ldpc_sim import N, build_full_h_matrix
|
||||
from frame_sync import generate_stream, syndrome_screen
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Fixtures
|
||||
# =============================================================================
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def H():
|
||||
"""Full expanded H matrix, built once per module."""
|
||||
return build_full_h_matrix()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestStreamGeneration
|
||||
# =============================================================================
|
||||
|
||||
class TestStreamGeneration:
|
||||
"""Validate continuous LLR stream generation."""
|
||||
|
||||
def test_stream_length(self, H):
|
||||
"""Stream should be n_frames * N + offset long."""
|
||||
np.random.seed(100)
|
||||
n_frames = 5
|
||||
offset = 37
|
||||
stream_llr, true_offset, info_list = generate_stream(
|
||||
H, n_frames=n_frames, lam_s=5.0, lam_b=0.1, offset=offset
|
||||
)
|
||||
assert len(stream_llr) == n_frames * N + offset
|
||||
assert true_offset == offset
|
||||
assert len(info_list) == n_frames
|
||||
|
||||
def test_stream_zero_offset(self, H):
|
||||
"""offset=0 should work, producing stream of length n_frames * N."""
|
||||
np.random.seed(101)
|
||||
n_frames = 3
|
||||
stream_llr, true_offset, info_list = generate_stream(
|
||||
H, n_frames=n_frames, lam_s=5.0, lam_b=0.1, offset=0
|
||||
)
|
||||
assert len(stream_llr) == n_frames * N
|
||||
assert true_offset == 0
|
||||
assert len(info_list) == n_frames
|
||||
|
||||
def test_stream_random_offset(self, H):
|
||||
"""offset=None should pick a random offset in [0, N-1]."""
|
||||
np.random.seed(102)
|
||||
stream_llr, true_offset, info_list = generate_stream(
|
||||
H, n_frames=4, lam_s=5.0, lam_b=0.1, offset=None
|
||||
)
|
||||
assert 0 <= true_offset < N
|
||||
assert len(stream_llr) == 4 * N + true_offset
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TestSyndromeScreen
|
||||
# =============================================================================
|
||||
|
||||
class TestSyndromeScreen:
|
||||
"""Validate syndrome-based offset screening."""
|
||||
|
||||
def test_correct_offset_low_syndrome(self, H):
|
||||
"""At high SNR (lam_s=10), the best offset should match the true offset."""
|
||||
np.random.seed(200)
|
||||
n_frames = 3
|
||||
offset = 42
|
||||
stream_llr, true_offset, _ = generate_stream(
|
||||
H, n_frames=n_frames, lam_s=10.0, lam_b=0.1, offset=offset
|
||||
)
|
||||
scores = syndrome_screen(stream_llr, n_offsets=N)
|
||||
# The true offset should have the lowest (or near-lowest) syndrome weight
|
||||
best_offset = min(scores, key=scores.get)
|
||||
assert best_offset == true_offset, (
|
||||
f"Best offset {best_offset} (sw={scores[best_offset]}) "
|
||||
f"!= true offset {true_offset} (sw={scores[true_offset]})"
|
||||
)
|
||||
|
||||
def test_wrong_offsets_high_syndrome(self, H):
|
||||
"""Wrong offsets should have average syndrome weight > 80."""
|
||||
np.random.seed(201)
|
||||
n_frames = 3
|
||||
offset = 10
|
||||
stream_llr, true_offset, _ = generate_stream(
|
||||
H, n_frames=n_frames, lam_s=10.0, lam_b=0.1, offset=offset
|
||||
)
|
||||
scores = syndrome_screen(stream_llr, n_offsets=N)
|
||||
# Collect syndrome weights for wrong offsets
|
||||
wrong_weights = [
|
||||
scores[o] for o in scores if o != true_offset
|
||||
]
|
||||
avg_wrong = np.mean(wrong_weights)
|
||||
assert avg_wrong > 80, (
|
||||
f"Average wrong-offset syndrome weight {avg_wrong:.1f} is not > 80"
|
||||
)
|
||||
Reference in New Issue
Block a user