feat: add PEG base matrix constructor with shift optimization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -512,6 +512,195 @@ def optimize_degree_distribution(m_base=7, lam_b=0.1, top_k=10,
|
|||||||
return results[:top_k]
|
return results[:top_k]
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PEG Base Matrix Constructor
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def _build_full_h_from_base(H_base, z=32):
|
||||||
|
"""Expand a QC base matrix to a full binary parity-check matrix."""
|
||||||
|
m_base_local = H_base.shape[0]
|
||||||
|
n_base_local = H_base.shape[1]
|
||||||
|
m_full = m_base_local * z
|
||||||
|
n_full = n_base_local * z
|
||||||
|
H_full = np.zeros((m_full, n_full), dtype=np.int8)
|
||||||
|
for r in range(m_base_local):
|
||||||
|
for c in range(n_base_local):
|
||||||
|
shift = int(H_base[r, c])
|
||||||
|
if shift < 0:
|
||||||
|
continue
|
||||||
|
for zz in range(z):
|
||||||
|
col_idx = c * z + (zz + shift) % z
|
||||||
|
H_full[r * z + zz, col_idx] = 1
|
||||||
|
return H_full
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_random_pattern(vn_degrees, m_base):
|
||||||
|
"""
|
||||||
|
Generate a random connection pattern matching target VN degrees.
|
||||||
|
|
||||||
|
Keeps the staircase backbone fixed and randomly places extra connections
|
||||||
|
among available positions.
|
||||||
|
"""
|
||||||
|
n_base = len(vn_degrees)
|
||||||
|
pattern = -np.ones((m_base, n_base), dtype=np.int16)
|
||||||
|
|
||||||
|
# Col 0 (info) connects to all rows
|
||||||
|
for r in range(m_base):
|
||||||
|
pattern[r, 0] = 0
|
||||||
|
|
||||||
|
# Staircase backbone
|
||||||
|
for r in range(m_base):
|
||||||
|
pattern[r, r + 1] = 0
|
||||||
|
for r in range(1, m_base):
|
||||||
|
pattern[r, r] = 0
|
||||||
|
|
||||||
|
# Current degrees from backbone
|
||||||
|
current_degrees = [0] * n_base
|
||||||
|
for r in range(m_base):
|
||||||
|
for c in range(n_base):
|
||||||
|
if pattern[r, c] >= 0:
|
||||||
|
current_degrees[c] += 1
|
||||||
|
|
||||||
|
# Randomly place extra connections
|
||||||
|
for c in range(1, n_base):
|
||||||
|
needed = vn_degrees[c] - current_degrees[c]
|
||||||
|
if needed <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
available = [r for r in range(m_base) if pattern[r, c] < 0]
|
||||||
|
if len(available) < needed:
|
||||||
|
continue
|
||||||
|
chosen = np.random.choice(available, size=needed, replace=False)
|
||||||
|
for r in chosen:
|
||||||
|
pattern[r, c] = 0
|
||||||
|
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
|
||||||
|
def construct_base_matrix(vn_degrees, z=32, n_trials=1000):
|
||||||
|
"""
|
||||||
|
Construct a concrete base matrix with circulant shifts optimized
|
||||||
|
for maximum girth and guaranteed full rank.
|
||||||
|
|
||||||
|
Algorithm:
|
||||||
|
1. Try multiple random connection placements (extras beyond staircase)
|
||||||
|
2. For each placement, try random shift assignments
|
||||||
|
3. Keep the best (full-rank, highest girth) result
|
||||||
|
|
||||||
|
Returns (H_base, girth).
|
||||||
|
"""
|
||||||
|
from ldpc_analysis import compute_girth
|
||||||
|
|
||||||
|
m_base = len(vn_degrees) - 1
|
||||||
|
n_base = len(vn_degrees)
|
||||||
|
assert vn_degrees[0] == m_base
|
||||||
|
|
||||||
|
expected_rank = m_base * z
|
||||||
|
best_H = None
|
||||||
|
best_girth = 0
|
||||||
|
best_has_rank = False
|
||||||
|
|
||||||
|
# Divide trials between placement variations and shift optimization
|
||||||
|
n_patterns = max(10, n_trials // 20)
|
||||||
|
n_shifts_per_pattern = max(20, n_trials // n_patterns)
|
||||||
|
|
||||||
|
for _p in range(n_patterns):
|
||||||
|
pattern = _generate_random_pattern(vn_degrees, m_base)
|
||||||
|
|
||||||
|
positions = [(r, c) for r in range(m_base) for c in range(n_base)
|
||||||
|
if pattern[r, c] >= 0]
|
||||||
|
|
||||||
|
for _s in range(n_shifts_per_pattern):
|
||||||
|
H_candidate = pattern.copy()
|
||||||
|
for r, c in positions:
|
||||||
|
H_candidate[r, c] = np.random.randint(0, z)
|
||||||
|
|
||||||
|
# Check rank
|
||||||
|
H_full = _build_full_h_from_base(H_candidate, z)
|
||||||
|
rank = np.linalg.matrix_rank(H_full.astype(float))
|
||||||
|
has_full_rank = rank >= expected_rank
|
||||||
|
|
||||||
|
if has_full_rank and not best_has_rank:
|
||||||
|
girth = compute_girth(H_candidate, z)
|
||||||
|
best_girth = girth
|
||||||
|
best_H = H_candidate.copy()
|
||||||
|
best_has_rank = True
|
||||||
|
elif has_full_rank and best_has_rank:
|
||||||
|
girth = compute_girth(H_candidate, z)
|
||||||
|
if girth > best_girth:
|
||||||
|
best_girth = girth
|
||||||
|
best_H = H_candidate.copy()
|
||||||
|
elif not best_has_rank:
|
||||||
|
girth = compute_girth(H_candidate, z)
|
||||||
|
if best_H is None or girth > best_girth:
|
||||||
|
best_girth = girth
|
||||||
|
best_H = H_candidate.copy()
|
||||||
|
|
||||||
|
if best_has_rank and best_girth >= 8:
|
||||||
|
return best_H, best_girth
|
||||||
|
|
||||||
|
if best_H is None:
|
||||||
|
# Fallback: return pattern with zero shifts
|
||||||
|
best_H = _generate_random_pattern(vn_degrees, m_base)
|
||||||
|
best_girth = compute_girth(best_H, z)
|
||||||
|
|
||||||
|
return best_H, best_girth
|
||||||
|
|
||||||
|
|
||||||
|
def verify_matrix(H_base, z=32):
|
||||||
|
"""
|
||||||
|
Comprehensive validity checks for a base matrix.
|
||||||
|
|
||||||
|
Returns dict with check results.
|
||||||
|
"""
|
||||||
|
from ldpc_analysis import compute_girth, peg_encode
|
||||||
|
|
||||||
|
m_base_local = H_base.shape[0]
|
||||||
|
n_base_local = H_base.shape[1]
|
||||||
|
k = z
|
||||||
|
|
||||||
|
# Build full matrix
|
||||||
|
H_full = _build_full_h_from_base(H_base, z)
|
||||||
|
|
||||||
|
# Column degrees
|
||||||
|
col_degrees = []
|
||||||
|
for c in range(n_base_local):
|
||||||
|
col_degrees.append(int(np.sum(H_base[:, c] >= 0)))
|
||||||
|
|
||||||
|
# Full rank check
|
||||||
|
expected_rank = m_base_local * z
|
||||||
|
actual_rank = np.linalg.matrix_rank(H_full.astype(float))
|
||||||
|
full_rank = actual_rank >= expected_rank
|
||||||
|
|
||||||
|
# Parity submatrix rank (columns k onwards)
|
||||||
|
H_parity = H_full[:, k:]
|
||||||
|
parity_rank = np.linalg.matrix_rank(H_parity.astype(float))
|
||||||
|
parity_full_rank = parity_rank >= min(H_parity.shape)
|
||||||
|
|
||||||
|
# Girth
|
||||||
|
girth = compute_girth(H_base, z)
|
||||||
|
|
||||||
|
# Encoding test
|
||||||
|
encodable = False
|
||||||
|
try:
|
||||||
|
info = np.random.randint(0, 2, k).astype(np.int8)
|
||||||
|
codeword = peg_encode(info, H_base, H_full, z=z)
|
||||||
|
syndrome = H_full @ codeword % 2
|
||||||
|
encodable = np.all(syndrome == 0)
|
||||||
|
except Exception:
|
||||||
|
encodable = False
|
||||||
|
|
||||||
|
return {
|
||||||
|
'col_degrees': col_degrees,
|
||||||
|
'full_rank': full_rank,
|
||||||
|
'actual_rank': actual_rank,
|
||||||
|
'expected_rank': expected_rank,
|
||||||
|
'parity_rank': parity_full_rank,
|
||||||
|
'girth': girth,
|
||||||
|
'encodable': encodable,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# CLI placeholder (will be extended in later tasks)
|
# CLI placeholder (will be extended in later tasks)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -131,3 +131,47 @@ class TestDegreeDistributionOptimizer:
|
|||||||
best_degrees, best_threshold = results[0]
|
best_degrees, best_threshold = results[0]
|
||||||
# Original staircase threshold is ~3-5 photons
|
# Original staircase threshold is ~3-5 photons
|
||||||
assert best_threshold < 6.0, f"Best threshold {best_threshold} should be < 6.0"
|
assert best_threshold < 6.0, f"Best threshold {best_threshold} should be < 6.0"
|
||||||
|
|
||||||
|
|
||||||
|
class TestPEGBaseMatrixConstructor:
|
||||||
|
"""Tests for the PEG base matrix constructor."""
|
||||||
|
|
||||||
|
def test_construct_matches_target_degrees(self):
|
||||||
|
"""Constructed matrix should have the target column degrees."""
|
||||||
|
from density_evolution import construct_base_matrix
|
||||||
|
np.random.seed(42)
|
||||||
|
target = [7, 3, 3, 3, 2, 2, 2, 2]
|
||||||
|
H_base, girth = construct_base_matrix(target, z=32, n_trials=500)
|
||||||
|
# Check column degrees
|
||||||
|
for c in range(H_base.shape[1]):
|
||||||
|
actual_deg = np.sum(H_base[:, c] >= 0)
|
||||||
|
assert actual_deg == target[c], (
|
||||||
|
f"Col {c}: expected degree {target[c]}, got {actual_deg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_construct_has_valid_rank(self):
|
||||||
|
"""Full H matrix should have full rank, parity submatrix should too."""
|
||||||
|
from density_evolution import construct_base_matrix, verify_matrix
|
||||||
|
np.random.seed(42)
|
||||||
|
target = [7, 3, 3, 3, 2, 2, 2, 2]
|
||||||
|
H_base, girth = construct_base_matrix(target, z=32, n_trials=500)
|
||||||
|
checks = verify_matrix(H_base, z=32)
|
||||||
|
assert checks['full_rank'], f"Full matrix rank {checks['actual_rank']} < expected {checks['expected_rank']}"
|
||||||
|
assert checks['parity_rank'], f"Parity submatrix not full rank"
|
||||||
|
|
||||||
|
def test_construct_encodable(self):
|
||||||
|
"""Encoding a random info word should produce zero syndrome."""
|
||||||
|
from density_evolution import construct_base_matrix, verify_matrix
|
||||||
|
np.random.seed(42)
|
||||||
|
target = [7, 3, 3, 3, 2, 2, 2, 2]
|
||||||
|
H_base, girth = construct_base_matrix(target, z=32, n_trials=500)
|
||||||
|
checks = verify_matrix(H_base, z=32)
|
||||||
|
assert checks['encodable'], "Should be able to encode and verify syndrome=0"
|
||||||
|
|
||||||
|
def test_construct_girth_at_least_4(self):
|
||||||
|
"""Constructed matrix should have girth >= 4."""
|
||||||
|
from density_evolution import construct_base_matrix
|
||||||
|
np.random.seed(42)
|
||||||
|
target = [7, 3, 3, 3, 2, 2, 2, 2]
|
||||||
|
H_base, girth = construct_base_matrix(target, z=32, n_trials=500)
|
||||||
|
assert girth >= 4, f"Girth {girth} should be >= 4"
|
||||||
|
|||||||
Reference in New Issue
Block a user