Porównaj commity

...

2 Commity

Autor SHA1 Wiadomość Data
Karlis Goba aec8f7677a Added more files/dirs 2022-08-12 23:30:52 +03:00
Karlis Goba d2b9790fd6 Added fine/coarse sync search 2022-08-12 23:30:21 +03:00
3 zmienionych plików z 511 dodań i 64 usunięć

8
.gitignore vendored
Wyświetl plik

@ -1,3 +1,9 @@
*.o
gen_ft8
decode_ft8
test_ft8
libft8.a
wsjtx2/
.build/
.DS_Store
.vscode/
__pycache__/

Wyświetl plik

@ -2,6 +2,7 @@ import scipy.io.wavfile as wavfile
from scipy import signal
import numpy as np
import sys
import ldpc
FT8_NUM_TONES = 8
FT8_NUM_SYMBOLS = 79
@ -9,6 +10,9 @@ FT8_TONE_DEVIATION = 6.25
FT8_SYMBOL_PERIOD = 0.160
FT8_SYNC_SYMS = [3, 1, 4, 0, 6, 5, 2]
FT8_SYNC_POS = [0, 36, 72]
FT8_DATA_POS = [7, 43]
FT8_LDPC_PAYLOAD_BITS = 91
FT8_PAYLOAD_BITS = 77
MIN_FREQ = 300
MAX_FREQ = 3000
@ -25,16 +29,78 @@ def load_wav(path):
samples = np.array(samples / 32768.0)
return (rate, samples)
def quantize(H, mag_db_step=0.5, phase_divs=256):
mag_db = lin_to_db(np.abs(H))
mag_db = mag_db_step * np.ceil(mag_db / mag_db_step)
phase = np.angle(H)
phase = np.ceil(0.5 + phase * phase_divs / (2*np.pi)) / phase_divs * (2*np.pi)
return db_to_lin(mag_db) * np.exp(1j * phase)
class Waterfall:
def __init__(self):
self.H = None
self.freq_osr = self.time_osr = None
pass
def search_sync_coarse(H, bin_min, bin_max, freq_osr, time_osr, min_score=4.0, max_cand=30):
Adb = lin_to_db(np.abs(H))
freq_step = FT8_TONE_DEVIATION / freq_osr
time_step = FT8_SYMBOL_PERIOD / time_osr
print(f'Using bins {bin_min}..{bin_max} ({bin_max - bin_min})')
score_map = dict()
for freq_sub in range(freq_osr):
for bin_first in range(bin_min + freq_sub, bin_max - FT8_NUM_TONES * freq_osr, freq_osr):
for time_sub in range(time_osr):
for time_start in range(-10 * time_osr + time_sub, 21 * time_osr + time_sub, time_osr):
# calc sync score at (bin_first, time_start)
score = []
for sync_start in FT8_SYNC_POS:
for sync_pos, sync_tone in enumerate(FT8_SYNC_SYMS):
pos = time_start + (sync_start + sync_pos) * time_osr
if pos >= 0 and pos < Adb.shape[1]:
sym_db = Adb[bin_first + sync_tone * freq_osr, pos]
if pos - 1 >= 0:
sym_prev_db = Adb[bin_first + sync_tone * freq_osr, pos - 1]
score.append(sym_db - sym_prev_db)
if pos + 1 < H.shape[1]:
sym_next_db = Adb[bin_first + sync_tone * freq_osr, pos + 1]
score.append(sym_db - sym_next_db)
if bin_first + (sync_tone - 1) * freq_osr >= bin_min:
sym_down_db = Adb[bin_first + (sync_tone - 1) * freq_osr, pos]
score.append(sym_db - sym_down_db)
if bin_first + (sync_tone + 1) * freq_osr < bin_max:
sym_up_db = Adb[bin_first + (sync_tone + 1) * freq_osr, pos]
score.append(sym_db - sym_up_db)
score_avg = np.mean(score)
if score_avg > min_score:
is_better = True
# if (bin_first, time_start) in score_map:
# if score_map[(bin_first, time_start)] >= score_avg:
# is_better = False
for delta_bin in [-2, -1, 0, 1, 2]:
for delta_pos in [-2, -1, 0, 1, 2]:
key = (bin_first + delta_bin, time_start + delta_pos)
if key in score_map:
if score_map[key] <= score_avg:
del score_map[key]
else:
is_better = False
if is_better:
score_map[(bin_first, time_start)] = score_avg
top_keys = sorted(score_map.keys(), key=lambda x: score_map[x], reverse=True)[:max_cand]
for idx, (bin, pos) in enumerate(sorted(top_keys)):
print(f'{idx+1}: {freq_step * bin:.2f}\t{time_step * pos:+.02f}\t{score_map[(bin, pos)]:.2f}')
def downsample_fft(H, bin_f0, fs2=100, freq_osr=1, time_osr=1):
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
nfft2 = sym_size2 * freq_osr
bin_freq2 = fs2 / nfft2
taper_width = 6
freq_step2 = fs2 / nfft2
taper_width = 4
pad_width = ((nfft2 - 2*taper_width - freq_osr*FT8_NUM_TONES) // 2)
H2 = H[bin_f0 - taper_width - pad_width: bin_f0 + freq_osr*FT8_NUM_TONES + taper_width + pad_width, :]
W_taper = np.linspace(0, 1, taper_width)
@ -46,11 +112,11 @@ def downsample_fft(H, bin_f0, fs2=100, freq_osr=1, time_osr=1):
H2 = np.roll(H2, -shift, axis=0)
_, sig2 = signal.istft(H2, window='hann', nperseg=nfft2, noverlap=nfft2 - (sym_size2//time_osr), input_onesided=False)
f0_new = (taper_width + pad_width - shift) * bin_freq2
f0_new = (taper_width + pad_width - shift) * freq_step2
return sig2, f0_new
def locate_fine_sync(sig2, fs2, f0_new, pos_start):
def search_sync_fine(sig2, fs2, f0_new, pos_start):
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
n = np.arange(sym_size2)
@ -79,11 +145,11 @@ def locate_fine_sync(sig2, fs2, f0_new, pos_start):
mag2_plus = np.abs(np.sum(demod * ctweak_plus_tone))**2
power += 2*mag2_sym - mag2_minus - mag2_plus
# demod_prev = win * sig2[pos1 - sym_size2:pos1] * ctones_conj[:, sync_tone] * ctweak
# demod_next = win * sig2[pos1 + sym_size2:pos1 + 2*sym_size2] * ctones_conj[:, sync_tone] * ctweak
# mag2_prev = np.abs(np.sum(demod_prev))**2
# mag2_next = np.abs(np.sum(demod_next))**2
# power += 2*mag2_sym - mag2_prev - mag2_next
# demod_prev = win * sig2[pos1 - sym_size2:pos1] * ctones_conj[:, sync_tone] * ctweak
# demod_next = win * sig2[pos1 + sym_size2:pos1 + 2*sym_size2] * ctones_conj[:, sync_tone] * ctweak
# mag2_prev = np.abs(np.sum(demod_prev))**2
# mag2_next = np.abs(np.sum(demod_next))**2
# power += 2*mag2_sym - mag2_prev - mag2_next
power_time.append(power)
if max_power is None or power > max_power:
max_power = power
@ -96,75 +162,95 @@ def locate_fine_sync(sig2, fs2, f0_new, pos_start):
return max_freq_offset, max_pos_offset
def extract_logl_db(A2db):
# FT8 bits -> channel symbols 0, 1, 3, 2, 5, 6, 4, 7
A2db_bit0 = np.max(A2db[[5, 6, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 3, 2], :], axis=0) # 4/5/6/7 - 0/1/2/3
A2db_bit1 = np.max(A2db[[3, 2, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 5, 6], :], axis=0) # 2/3/6/7 - 0/1/4/5
A2db_bit2 = np.max(A2db[[1, 2, 6, 7], :], axis=0) - np.max(A2db[[0, 3, 5, 4], :], axis=0) # 1/3/5/7 - 0/2/4/6
A2db_bits = np.stack((A2db_bit0, A2db_bit1, A2db_bit2)).transpose()
# a = [
# A2db[7, :] - A2db[0, :],
# A2db[3, :] - A2db[0, :],
# A2db[6, :] - A2db[3, :],
# A2db[6, :] - A2db[2, :],
# A2db[7, :] - A2db[4, :],
# A2db[4, :] - A2db[1, :],
# A2db[5, :] - A2db[1, :],
# A2db[5, :] - A2db[2, :]
# ]
# W = np.array([[ 48., 6., 36., 30., 6., 36., 30., 24.],
# [ 42., 35., -28., -29., 1., 40., 5., -30.],
# [ 42., 1., 40., 5., 35., -28., -29., -30.]])/34/6
# A2db_bits = np.matmul(W, a).transpose()
bits_logl = np.concatenate((A2db_bits[7:36], A2db_bits[43:72])).flatten() * 0.6
return bits_logl, A2db_bits
fs, sig = load_wav(sys.argv[1])
print(f'Sample rate {fs} Hz')
freq_osr = 2
time_osr = 4
time_osr = 2
sym_size = int(fs * FT8_SYMBOL_PERIOD)
nfft = sym_size * freq_osr
bin_freq = fs / nfft
freq_step = fs / nfft
_, _, H = signal.stft(sig, window='hann', nperseg=nfft, noverlap=nfft - (sym_size//time_osr), boundary=None, padded=None)
H = quantize(H)
Adb = lin_to_db(np.abs(H))
print(f'Max magnitude {Adb.max(axis=(0, 1)):.1f} dB')
print(f'Waterfall shape {Adb.shape}')
# bin_min = int(MIN_FREQ / bin_freq)
# bin_max = int(MAX_FREQ / bin_freq) + 1
# print(f'Using bins {bin_min}..{bin_max} ({bin_max - bin_min})')
# for freq_sub in range(freq_osr):
# for bin_first in range(bin_min + freq_sub, bin_max - FT8_NUM_TONES, freq_osr):
# for time_sub in range(time_osr):
# for time_start in range(-10 + time_sub, 20, time_osr):
# # calc sync score at (bin_first, time_start)
# pass
bin_min = int(MIN_FREQ / freq_step)
bin_max = int(MAX_FREQ / freq_step) + 1
search_sync_coarse(H, bin_min, bin_max, freq_osr, time_osr)
use_downsample = True
f0 = float(sys.argv[2])
bin_f0 = int(0.5 + f0 / bin_freq)
f0_real = bin_f0 * bin_freq
fs2 = 200
sig2, f0_new = downsample_fft(H[:, ::time_osr], bin_f0, fs2=fs2, freq_osr=freq_osr, time_osr=1)
print(f'Downsampled signal to {fs2} Hz sample rate, freq shift {f0_real} Hz -> {f0_new} Hz')
time_start = float(sys.argv[3])
pos_start = int(0.5 + time_start * fs2)
max_freq_offset, max_pos_offset = locate_fine_sync(sig2, fs2, f0_new, pos_start)
print(f'Max power at {f0_real:.1f} + {max_freq_offset:.1f} = {f0_real + max_freq_offset:.1f} Hz, {max_pos_offset/fs2:.3f} s')
bin_f0 = int(0.5 + f0 / freq_step)
f0_real = bin_f0 * freq_step
print(f'Frequency {f0:.2f} Hz (bin {bin_f0}), coarse {f0_real:.2f} Hz')
env = signal.filtfilt(0.04, [1, -(1-0.04)], np.abs(sig2))
# env = signal.filtfilt(0.01, [1, -(1-0.01)], np.abs(sig2))
if use_downsample:
fs2 = 100
env_alpha = 0.06
# max_freq_offset = f0 - f0_real
# max_pos_offset = 0
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
ctweak = np.exp(-1j * 2*np.pi * np.arange(len(sig2)) * max_freq_offset/fs2)
sig3 = (sig2*ctweak/env)[pos_start + max_pos_offset:pos_start + max_pos_offset + int(FT8_NUM_SYMBOLS*FT8_SYMBOL_PERIOD*fs2)]
_, _, H2 = signal.stft(sig3, window='boxcar', nperseg=sym_size2, noverlap=0, return_onesided=False, boundary=None, padded=False)
A2db = lin_to_db(np.abs(H2))
A2db = A2db[0:8, :]
sig2, f0_new = downsample_fft(H[:, ::time_osr], bin_f0, fs2=fs2, freq_osr=freq_osr, time_osr=1)
print(f'Downsampled signal to {fs2} Hz sample rate, freq shift {f0_real} Hz -> {f0_new} Hz')
# FT8 bits -> channel symbols 0, 1, 3, 2, 5, 6, 4, 7
A2db_bit0 = np.max(A2db[[5, 6, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 3, 2], :], axis=0) # 4/5/6/7 - 0/1/2/3
A2db_bit1 = np.max(A2db[[3, 2, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 5, 6], :], axis=0) # 2/3/6/7 - 0/1/4/5
A2db_bit2 = np.max(A2db[[1, 2, 6, 7], :], axis=0) - np.max(A2db[[0, 3, 5, 4], :], axis=0) # 1/3/5/7 - 0/2/4/6
A2db_bits = np.stack((A2db_bit0, A2db_bit1, A2db_bit2))
# a = [
# A2db[7, :] - A2db[0, :],
# A2db[3, :] - A2db[0, :],
# A2db[6, :] - A2db[3, :],
# A2db[6, :] - A2db[2, :],
# A2db[7, :] - A2db[4, :],
# A2db[4, :] - A2db[1, :],
# A2db[5, :] - A2db[1, :]
# ]
# A2db_bit0a = (a[0] + a[2] + a[3] + a[5] + a[6])/5
# A2db_bit1a = a[0] / 4 + (a[1] - a[3]) * 5 / 24 + (a[5] - a[2]) / 6 + (a[4] - a[6]) / 24
# A2db_bit2a = a[0] / 4 + (a[1] - a[3]) / 24 + (a[2] - a[5]) / 6 + (a[4] - a[6]) * 5 / 24
pos_start = int(0.5 + time_start * fs2)
max_freq_offset, max_pos_offset = search_sync_fine(sig2, fs2, f0_new, pos_start)
print(f'Max power at {f0_real:.2f} + {max_freq_offset:.2f} = {f0_real + max_freq_offset:.2f} Hz, {max_pos_offset/fs2:.3f} s')
env = signal.filtfilt(env_alpha, [1, -(1-env_alpha)], np.abs(sig2))
# max_freq_offset = f0 - f0_real
# max_pos_offset = 0
sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD)
ctweak = np.exp(-1j * 2*np.pi * np.arange(len(sig2)) * (f0_new + max_freq_offset)/fs2)
sig3 = (sig2*ctweak)[pos_start + max_pos_offset:pos_start + max_pos_offset + int(FT8_NUM_SYMBOLS*FT8_SYMBOL_PERIOD*fs2)]
_, _, H2 = signal.stft(sig3, window='boxcar', nperseg=sym_size2, noverlap=0, return_onesided=False, boundary=None, padded=False)
A2db = lin_to_db(np.abs(H2))
A2db = A2db[0:FT8_NUM_TONES, :]
else:
pos_start = int(0.5 + (time_start + FT8_SYMBOL_PERIOD/2) * fs / sym_size * time_osr)
print(f'Start time {time_start:.3f} s (pos {pos_start}), coarse {pos_start / time_osr * sym_size / fs - FT8_SYMBOL_PERIOD/2:.3f} s')
A2db = Adb[bin_f0:bin_f0+freq_osr*FT8_NUM_TONES:freq_osr, pos_start:pos_start+FT8_NUM_SYMBOLS*time_osr:time_osr]
A2db -= np.max(A2db, axis=0)
bits_logl, A2db_bits = extract_logl_db(A2db)
(num_errors, bits) = ldpc.bp_solve(bits_logl, max_iters=30, max_no_improvement=15)
print(f'LDPC decode: {num_errors} errors')
if num_errors == 0:
print(f'Payload bits: {"".join([str(x) for x in bits[:FT8_PAYLOAD_BITS]])}')
print(f'CRC bits : {"".join([str(x) for x in bits[FT8_PAYLOAD_BITS:FT8_LDPC_PAYLOAD_BITS]])}')
print(f'Parity bits : {"".join([str(x) for x in bits[FT8_LDPC_PAYLOAD_BITS:]])}')
# 3140652 00000000117217624541127053545 3140652 33170166234757420515470163426 3140652
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
@ -172,11 +258,11 @@ import matplotlib.colors as pltcolors
fig, ax = plt.subplots(4)
plt.colorbar(ax[0].imshow(A2db, cmap='inferno', norm=pltcolors.Normalize(-30, 0, clip=True)), orientation='horizontal', ax=ax[0])
# ax[2].imshow(np.stack((A2db_bit0a, A2db_bit1a, A2db_bit2a)), cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True))
plt.colorbar(ax[1].imshow(A2db_bits, cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True)), orientation='horizontal', ax=ax[1])
ax[2].hist(A2db_bits.flatten(), bins=25)
ax[3].plot(np.arange(len(sig3))/sym_size2, np.real(sig3))
ax[3].plot(np.arange(len(sig3))/sym_size2, np.abs(sig3))
plt.colorbar(ax[1].imshow(A2db_bits.transpose(), cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True)), orientation='horizontal', ax=ax[1])
# ax[2].imshow(A2db_bits2, cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True))
ax[2].hist(bits_logl, bins=25)
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.real(sig3))
# ax[3].plot(np.arange(len(sig3))/sym_size2, np.abs(sig3))
ax[3].margins(0, 0)
# loc = plticker.MultipleLocator(base=32.0) # this locator puts ticks at regular intervals
# ax[1].xaxis.set_major_locator(loc)

355
utils/ldpc.py 100644
Wyświetl plik

@ -0,0 +1,355 @@
import numpy as np
FTX_LDPC_N = 174
FTX_LDPC_M = 83
def ldpc_check(codeword):
errors = 0
for m in range(FTX_LDPC_M):
x = False
for i in range(kFTX_LDPC_Num_rows[m]):
x ^= (codeword[kFTX_LDPC_Nm[m][i] - 1] > 0)
if x:
errors += 1
return errors
def bp_solve(codeword, max_iters=25, max_no_improvement=5):
tov = np.zeros((FTX_LDPC_N, 3))
toc = np.zeros((FTX_LDPC_M, 7))
# int min_errors = FTX_LDPC_M;
min_errors = FTX_LDPC_M
last_errors = FTX_LDPC_M
plain = None
no_improvement = 1
for iter in range(max_iters):
# Do a hard decision guess (tov=0 in iter 0)
plain = np.where((codeword + tov[:, 0] + tov[:, 1] + tov[:, 2]) > 0, 1, 0)
if np.sum(plain) == 0:
# Message converged to all-zeros, which is prohibited
break
# Check to see if we have a codeword (check before we do any iter)
errors = ldpc_check(plain)
print(f'iter {iter}, errors {errors}')
if errors < last_errors:
no_improvement = 1
last_errors = errors
else:
no_improvement += 1
if no_improvement >= max_no_improvement:
break
if errors < min_errors:
# We have a better guess - update the result
min_errors = errors
if errors == 0:
# Found a perfect answer
break
# Send messages from bits to check nodes
for m in range(FTX_LDPC_M):
for n_idx in range(kFTX_LDPC_Num_rows[m]):
n = kFTX_LDPC_Nm[m][n_idx] - 1
# for each (n, m)
Tnm = codeword[n]
for m_idx in range(3):
if (kFTX_LDPC_Mn[n][m_idx] - 1) != m:
Tnm += tov[n][m_idx]
toc[m][n_idx] = np.tanh(-Tnm / 2)
# Send messages from check nodes to variable nodes
for n in range(FTX_LDPC_N):
for m_idx in range(3):
m = kFTX_LDPC_Mn[n][m_idx] - 1
# for each (n, m)
Tmn = 1.0
for n_idx in range(kFTX_LDPC_Num_rows[m]):
if (kFTX_LDPC_Nm[m][n_idx] - 1) != n:
Tmn *= toc[m][n_idx]
tov[n][m_idx] = -2 * np.arctanh(Tmn)
return (min_errors, plain)
# Each row describes one LDPC parity check.
# Each number is an index into the codeword (1-origin).
# The codeword bits mentioned in each row must XOR to zero.
kFTX_LDPC_Nm = [
[ 4, 31, 59, 91, 92, 96, 153 ],
[ 5, 32, 60, 93, 115, 146, 0 ],
[ 6, 24, 61, 94, 122, 151, 0 ],
[ 7, 33, 62, 95, 96, 143, 0 ],
[ 8, 25, 63, 83, 93, 96, 148 ],
[ 6, 32, 64, 97, 126, 138, 0 ],
[ 5, 34, 65, 78, 98, 107, 154 ],
[ 9, 35, 66, 99, 139, 146, 0 ],
[ 10, 36, 67, 100, 107, 126, 0 ],
[ 11, 37, 67, 87, 101, 139, 158 ],
[ 12, 38, 68, 102, 105, 155, 0 ],
[ 13, 39, 69, 103, 149, 162, 0 ],
[ 8, 40, 70, 82, 104, 114, 145 ],
[ 14, 41, 71, 88, 102, 123, 156 ],
[ 15, 42, 59, 106, 123, 159, 0 ],
[ 1, 33, 72, 106, 107, 157, 0 ],
[ 16, 43, 73, 108, 141, 160, 0 ],
[ 17, 37, 74, 81, 109, 131, 154 ],
[ 11, 44, 75, 110, 121, 166, 0 ],
[ 45, 55, 64, 111, 130, 161, 173 ],
[ 8, 46, 71, 112, 119, 166, 0 ],
[ 18, 36, 76, 89, 113, 114, 143 ],
[ 19, 38, 77, 104, 116, 163, 0 ],
[ 20, 47, 70, 92, 138, 165, 0 ],
[ 2, 48, 74, 113, 128, 160, 0 ],
[ 21, 45, 78, 83, 117, 121, 151 ],
[ 22, 47, 58, 118, 127, 164, 0 ],
[ 16, 39, 62, 112, 134, 158, 0 ],
[ 23, 43, 79, 120, 131, 145, 0 ],
[ 19, 35, 59, 73, 110, 125, 161 ],
[ 20, 36, 63, 94, 136, 161, 0 ],
[ 14, 31, 79, 98, 132, 164, 0 ],
[ 3, 44, 80, 124, 127, 169, 0 ],
[ 19, 46, 81, 117, 135, 167, 0 ],
[ 7, 49, 58, 90, 100, 105, 168 ],
[ 12, 50, 61, 118, 119, 144, 0 ],
[ 13, 51, 64, 114, 118, 157, 0 ],
[ 24, 52, 76, 129, 148, 149, 0 ],
[ 25, 53, 69, 90, 101, 130, 156 ],
[ 20, 46, 65, 80, 120, 140, 170 ],
[ 21, 54, 77, 100, 140, 171, 0 ],
[ 35, 82, 133, 142, 171, 174, 0 ],
[ 14, 30, 83, 113, 125, 170, 0 ],
[ 4, 29, 68, 120, 134, 173, 0 ],
[ 1, 4, 52, 57, 86, 136, 152 ],
[ 26, 51, 56, 91, 122, 137, 168 ],
[ 52, 84, 110, 115, 145, 168, 0 ],
[ 7, 50, 81, 99, 132, 173, 0 ],
[ 23, 55, 67, 95, 172, 174, 0 ],
[ 26, 41, 77, 109, 141, 148, 0 ],
[ 2, 27, 41, 61, 62, 115, 133 ],
[ 27, 40, 56, 124, 125, 126, 0 ],
[ 18, 49, 55, 124, 141, 167, 0 ],
[ 6, 33, 85, 108, 116, 156, 0 ],
[ 28, 48, 70, 85, 105, 129, 158 ],
[ 9, 54, 63, 131, 147, 155, 0 ],
[ 22, 53, 68, 109, 121, 174, 0 ],
[ 3, 13, 48, 78, 95, 123, 0 ],
[ 31, 69, 133, 150, 155, 169, 0 ],
[ 12, 43, 66, 89, 97, 135, 159 ],
[ 5, 39, 75, 102, 136, 167, 0 ],
[ 2, 54, 86, 101, 135, 164, 0 ],
[ 15, 56, 87, 108, 119, 171, 0 ],
[ 10, 44, 82, 91, 111, 144, 149 ],
[ 23, 34, 71, 94, 127, 153, 0 ],
[ 11, 49, 88, 92, 142, 157, 0 ],
[ 29, 34, 87, 97, 147, 162, 0 ],
[ 30, 50, 60, 86, 137, 142, 162 ],
[ 10, 53, 66, 84, 112, 128, 165 ],
[ 22, 57, 85, 93, 140, 159, 0 ],
[ 28, 32, 72, 103, 132, 166, 0 ],
[ 28, 29, 84, 88, 117, 143, 150 ],
[ 1, 26, 45, 80, 128, 147, 0 ],
[ 17, 27, 89, 103, 116, 153, 0 ],
[ 51, 57, 98, 163, 165, 172, 0 ],
[ 21, 37, 73, 138, 152, 169, 0 ],
[ 16, 47, 76, 130, 137, 154, 0 ],
[ 3, 24, 30, 72, 104, 139, 0 ],
[ 9, 40, 90, 106, 134, 151, 0 ],
[ 15, 58, 60, 74, 111, 150, 163 ],
[ 18, 42, 79, 144, 146, 152, 0 ],
[ 25, 38, 65, 99, 122, 160, 0 ],
[ 17, 42, 75, 129, 170, 172, 0 ]
]
# Each row corresponds to a codeword bit.
# The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit.
# 1-origin.
kFTX_LDPC_Mn = [
[ 16, 45, 73 ],
[ 25, 51, 62 ],
[ 33, 58, 78 ],
[ 1, 44, 45 ],
[ 2, 7, 61 ],
[ 3, 6, 54 ],
[ 4, 35, 48 ],
[ 5, 13, 21 ],
[ 8, 56, 79 ],
[ 9, 64, 69 ],
[ 10, 19, 66 ],
[ 11, 36, 60 ],
[ 12, 37, 58 ],
[ 14, 32, 43 ],
[ 15, 63, 80 ],
[ 17, 28, 77 ],
[ 18, 74, 83 ],
[ 22, 53, 81 ],
[ 23, 30, 34 ],
[ 24, 31, 40 ],
[ 26, 41, 76 ],
[ 27, 57, 70 ],
[ 29, 49, 65 ],
[ 3, 38, 78 ],
[ 5, 39, 82 ],
[ 46, 50, 73 ],
[ 51, 52, 74 ],
[ 55, 71, 72 ],
[ 44, 67, 72 ],
[ 43, 68, 78 ],
[ 1, 32, 59 ],
[ 2, 6, 71 ],
[ 4, 16, 54 ],
[ 7, 65, 67 ],
[ 8, 30, 42 ],
[ 9, 22, 31 ],
[ 10, 18, 76 ],
[ 11, 23, 82 ],
[ 12, 28, 61 ],
[ 13, 52, 79 ],
[ 14, 50, 51 ],
[ 15, 81, 83 ],
[ 17, 29, 60 ],
[ 19, 33, 64 ],
[ 20, 26, 73 ],
[ 21, 34, 40 ],
[ 24, 27, 77 ],
[ 25, 55, 58 ],
[ 35, 53, 66 ],
[ 36, 48, 68 ],
[ 37, 46, 75 ],
[ 38, 45, 47 ],
[ 39, 57, 69 ],
[ 41, 56, 62 ],
[ 20, 49, 53 ],
[ 46, 52, 63 ],
[ 45, 70, 75 ],
[ 27, 35, 80 ],
[ 1, 15, 30 ],
[ 2, 68, 80 ],
[ 3, 36, 51 ],
[ 4, 28, 51 ],
[ 5, 31, 56 ],
[ 6, 20, 37 ],
[ 7, 40, 82 ],
[ 8, 60, 69 ],
[ 9, 10, 49 ],
[ 11, 44, 57 ],
[ 12, 39, 59 ],
[ 13, 24, 55 ],
[ 14, 21, 65 ],
[ 16, 71, 78 ],
[ 17, 30, 76 ],
[ 18, 25, 80 ],
[ 19, 61, 83 ],
[ 22, 38, 77 ],
[ 23, 41, 50 ],
[ 7, 26, 58 ],
[ 29, 32, 81 ],
[ 33, 40, 73 ],
[ 18, 34, 48 ],
[ 13, 42, 64 ],
[ 5, 26, 43 ],
[ 47, 69, 72 ],
[ 54, 55, 70 ],
[ 45, 62, 68 ],
[ 10, 63, 67 ],
[ 14, 66, 72 ],
[ 22, 60, 74 ],
[ 35, 39, 79 ],
[ 1, 46, 64 ],
[ 1, 24, 66 ],
[ 2, 5, 70 ],
[ 3, 31, 65 ],
[ 4, 49, 58 ],
[ 1, 4, 5 ],
[ 6, 60, 67 ],
[ 7, 32, 75 ],
[ 8, 48, 82 ],
[ 9, 35, 41 ],
[ 10, 39, 62 ],
[ 11, 14, 61 ],
[ 12, 71, 74 ],
[ 13, 23, 78 ],
[ 11, 35, 55 ],
[ 15, 16, 79 ],
[ 7, 9, 16 ],
[ 17, 54, 63 ],
[ 18, 50, 57 ],
[ 19, 30, 47 ],
[ 20, 64, 80 ],
[ 21, 28, 69 ],
[ 22, 25, 43 ],
[ 13, 22, 37 ],
[ 2, 47, 51 ],
[ 23, 54, 74 ],
[ 26, 34, 72 ],
[ 27, 36, 37 ],
[ 21, 36, 63 ],
[ 29, 40, 44 ],
[ 19, 26, 57 ],
[ 3, 46, 82 ],
[ 14, 15, 58 ],
[ 33, 52, 53 ],
[ 30, 43, 52 ],
[ 6, 9, 52 ],
[ 27, 33, 65 ],
[ 25, 69, 73 ],
[ 38, 55, 83 ],
[ 20, 39, 77 ],
[ 18, 29, 56 ],
[ 32, 48, 71 ],
[ 42, 51, 59 ],
[ 28, 44, 79 ],
[ 34, 60, 62 ],
[ 31, 45, 61 ],
[ 46, 68, 77 ],
[ 6, 24, 76 ],
[ 8, 10, 78 ],
[ 40, 41, 70 ],
[ 17, 50, 53 ],
[ 42, 66, 68 ],
[ 4, 22, 72 ],
[ 36, 64, 81 ],
[ 13, 29, 47 ],
[ 2, 8, 81 ],
[ 56, 67, 73 ],
[ 5, 38, 50 ],
[ 12, 38, 64 ],
[ 59, 72, 80 ],
[ 3, 26, 79 ],
[ 45, 76, 81 ],
[ 1, 65, 74 ],
[ 7, 18, 77 ],
[ 11, 56, 59 ],
[ 14, 39, 54 ],
[ 16, 37, 66 ],
[ 10, 28, 55 ],
[ 15, 60, 70 ],
[ 17, 25, 82 ],
[ 20, 30, 31 ],
[ 12, 67, 68 ],
[ 23, 75, 80 ],
[ 27, 32, 62 ],
[ 24, 69, 75 ],
[ 19, 21, 71 ],
[ 34, 53, 61 ],
[ 35, 46, 47 ],
[ 33, 59, 76 ],
[ 40, 43, 83 ],
[ 41, 42, 63 ],
[ 49, 75, 83 ],
[ 20, 44, 48 ],
[ 42, 49, 57 ]
]
kFTX_LDPC_Num_rows = [
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
6, 6, 6
]