FT8 demod: initial commit of FT8 library with minimal changes and benchmark test

This commit is contained in:
f4exb 2023-01-08 19:03:29 +01:00
parent 6455c3ad3a
commit 902e58b46b
21 changed files with 7483 additions and 2 deletions

View File

@ -781,6 +781,10 @@ add_subdirectory(sdrbench)
add_subdirectory(modemm17)
if (LINUX)
add_subdirectory(ft8)
endif()
if (BUILD_GUI)
add_subdirectory(sdrgui)
add_subdirectory(plugins plugins)

29
ft8/CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
project(ft8)
set(ft8_SOURCES
fft.cpp
ft8.cpp
libldpc.cpp
osd.cpp
unpack.cpp
util.cpp
)
set(ft8_HEADERS
fft.h
ft8.h
libldpc.h
osd.h
unpack.h
util.h
)
add_library(ft8 SHARED
${ft8_SOURCES}
)
target_link_libraries(ft8
${FFTW3F_LIBRARIES}
)
install(TARGETS ft8 DESTINATION ${INSTALL_LIB_DIR})

300
ft8/arrays.h Normal file
View File

@ -0,0 +1,300 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
namespace FT8 {
//
// this is the LDPC(174,91) parity check matrix.
// each row describes one parity check.
// 83 rows.
// each number is an index into the codeword (1-origin).
// the codeword bits mentioned in each row must xor to zero.
// From WSJT-X's ldpc_174_91_c_reordered_parity.f90
//
int Nm[][7] = {
{ 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 },
};
// Mn from WSJT-X's ldpc_174_91_c_reordered_parity.f90
// each of the 174 rows corresponds to a codeword bit.
// the numbers indicate which three parity
// checks (rows in Nm) refer to the codeword bit.
// 1-origin.
int Mn[][3] = {
{ 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 },
};
} // namespace FT8

644
ft8/fft.cpp Normal file
View File

@ -0,0 +1,644 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "fft.h"
#include <mutex>
#include <unistd.h>
#include <assert.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "util.h"
#define TIMING 0
namespace FT8 {
// MEASURE=0, ESTIMATE=64, PATIENT=32
int fftw_type = FFTW_ESTIMATE;
// a cached fftw plan, for both of:
// fftwf_plan_dft_r2c_1d(n, m_in, m_out, FFTW_ESTIMATE);
// fftwf_plan_dft_c2r_1d(n, m_in, m_out, FFTW_ESTIMATE);
class Plan
{
public:
int n_;
int type_;
//
// real -> complex
//
fftwf_complex *c_; // (n_ / 2) + 1 of these
float *r_; // n_ of these
fftwf_plan fwd_; // forward plan
fftwf_plan rev_; // reverse plan
//
// complex -> complex
//
fftwf_complex *cc1_; // n
fftwf_complex *cc2_; // n
fftwf_plan cfwd_; // forward plan
fftwf_plan crev_; // reverse plan
// how much CPU time spent in FFTs that use this plan.
#if TIMING
float time_;
#endif
const char *why_;
int uses_;
};
static std::mutex plansmu;
static Plan *plans[1000];
static int nplans;
static int plan_master_pid = 0;
Plan *get_plan(int n, const char *why)
{
// cache fftw plans in the parent process,
// so they will already be there for fork()ed children.
plansmu.lock();
if (plan_master_pid == 0)
{
plan_master_pid = getpid();
}
for (int i = 0; i < nplans; i++)
{
if (plans[i]->n_ == n && plans[i]->type_ == fftw_type
#if TIMING
&& strcmp(plans[i]->why_, why) == 0
#endif
)
{
Plan *p = plans[i];
p->uses_ += 1;
plansmu.unlock();
return p;
}
}
float t0 = now();
// fftw_make_planner_thread_safe();
// the fftw planner is not thread-safe.
// can't rely on plansmu because both ft8.so
// and snd.so may be using separate copies of fft.cc.
// the lock file really should be per process.
// FIXME: Qt-fy this
int lockfd = creat("/tmp/fft-plan-lock", 0666);
assert(lockfd >= 0);
fchmod(lockfd, 0666);
int lockret = flock(lockfd, LOCK_EX);
assert(lockret == 0);
fftwf_set_timelimit(5);
//
// real -> complex
//
Plan *p = new Plan;
p->n_ = n;
#if TIMING
p->time_ = 0;
#endif
p->uses_ = 1;
p->why_ = why;
p->r_ = (float *)fftwf_malloc(n * sizeof(float));
assert(p->r_);
p->c_ = (fftwf_complex *)fftwf_malloc(((n / 2) + 1) * sizeof(fftwf_complex));
assert(p->c_);
// FFTW_ESTIMATE
// FFTW_MEASURE
// FFTW_PATIENT
// FFTW_EXHAUSTIVE
int type = fftw_type;
if (getpid() != plan_master_pid)
{
type = FFTW_ESTIMATE;
}
p->type_ = type;
p->fwd_ = fftwf_plan_dft_r2c_1d(n, p->r_, p->c_, type);
assert(p->fwd_);
p->rev_ = fftwf_plan_dft_c2r_1d(n, p->c_, p->r_, type);
assert(p->rev_);
//
// complex -> complex
//
p->cc1_ = (fftwf_complex *)fftwf_malloc(n * sizeof(fftwf_complex));
assert(p->cc1_);
p->cc2_ = (fftwf_complex *)fftwf_malloc(n * sizeof(fftwf_complex));
assert(p->cc2_);
p->cfwd_ = fftwf_plan_dft_1d(n, p->cc1_, p->cc2_, FFTW_FORWARD, type);
assert(p->cfwd_);
p->crev_ = fftwf_plan_dft_1d(n, p->cc2_, p->cc1_, FFTW_BACKWARD, type);
assert(p->crev_);
flock(lockfd, LOCK_UN);
close(lockfd);
assert(nplans + 1 < 1000);
plans[nplans] = p;
__sync_synchronize();
nplans += 1;
if (0 && getpid() == plan_master_pid)
{
float t1 = now();
fprintf(stderr, "miss pid=%d master=%d n=%d t=%.3f total=%d type=%d, %s\n",
getpid(), plan_master_pid, n, t1 - t0, nplans, type, why);
}
plansmu.unlock();
return p;
}
//
// do just one FFT on samples[i0..i0+block]
// real inputs, complex outputs.
// output has (block / 2) + 1 points.
//
std::vector<std::complex<float>> one_fft(
const std::vector<float> &samples,
int i0,
int block,
const char *why,
Plan *p
)
{
assert(i0 >= 0);
assert(block > 1);
int nsamples = samples.size();
int nbins = (block / 2) + 1;
if (p)
{
assert(p->n_ == block);
p->uses_ += 1;
}
else
{
p = get_plan(block, why);
}
fftwf_plan m_plan = p->fwd_;
#if TIMING
float t0 = now();
#endif
assert((int)samples.size() - i0 >= block);
int m_in_allocated = 0;
float *m_in = (float *)samples.data() + i0;
if ((((unsigned long long)m_in) % 16) != 0)
{
// m_in must be on a 16-byte boundary for FFTW.
m_in = (float *)fftwf_malloc(sizeof(float) * p->n_);
assert(m_in);
m_in_allocated = 1;
for (int i = 0; i < block; i++)
{
if (i0 + i < nsamples)
{
m_in[i] = samples[i0 + i];
}
else
{
m_in[i] = 0;
}
}
}
fftwf_complex *m_out = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * ((p->n_ / 2) + 1));
assert(m_out);
fftwf_execute_dft_r2c(m_plan, m_in, m_out);
std::vector<std::complex<float>> out(nbins);
for (int bi = 0; bi < nbins; bi++)
{
float re = m_out[bi][0];
float im = m_out[bi][1];
out[bi] = std::complex<float>(re, im);
}
if (m_in_allocated)
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return out;
}
//
// do a full set of FFTs, one per symbol-time.
// bins[time][frequency]
//
ffts_t ffts(const std::vector<float> &samples, int i0, int block, const char *why)
{
assert(i0 >= 0);
assert(block > 1 && (block % 2) == 0);
int nsamples = samples.size();
int nbins = (block / 2) + 1;
int nblocks = (nsamples - i0) / block;
ffts_t bins(nblocks);
for (int si = 0; si < nblocks; si++)
{
bins[si].resize(nbins);
}
Plan *p = get_plan(block, why);
fftwf_plan m_plan = p->fwd_;
#if TIMING
float t0 = now();
#endif
// allocate our own b/c using p->m_in and p->m_out isn't thread-safe.
float *m_in = (float *)fftwf_malloc(sizeof(float) * p->n_);
fftwf_complex *m_out = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * ((p->n_ / 2) + 1));
assert(m_in && m_out);
// float *m_in = p->r_;
// fftw_complex *m_out = p->c_;
for (int si = 0; si < nblocks; si++)
{
int off = i0 + si * block;
for (int i = 0; i < block; i++)
{
if (off + i < nsamples)
{
float x = samples[off + i];
m_in[i] = x;
}
else
{
m_in[i] = 0;
}
}
fftwf_execute_dft_r2c(m_plan, m_in, m_out);
for (int bi = 0; bi < nbins; bi++)
{
float re = m_out[bi][0];
float im = m_out[bi][1];
std::complex<float> c(re, im);
bins[si][bi] = c;
}
}
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return bins;
}
//
// do just one FFT on samples[i0..i0+block]
// real inputs, complex outputs.
// output has block points.
//
std::vector<std::complex<float>> one_fft_c(
const std::vector<float> &samples,
int i0,
int block,
const char *why
)
{
assert(i0 >= 0);
assert(block > 1);
int nsamples = samples.size();
Plan *p = get_plan(block, why);
fftwf_plan m_plan = p->cfwd_;
#if TIMING
float t0 = now();
#endif
fftwf_complex *m_in = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
fftwf_complex *m_out = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
assert(m_in && m_out);
for (int i = 0; i < block; i++)
{
if (i0 + i < nsamples)
{
m_in[i][0] = samples[i0 + i]; // real
}
else
{
m_in[i][0] = 0;
}
m_in[i][1] = 0; // imaginary
}
fftwf_execute_dft(m_plan, m_in, m_out);
std::vector<std::complex<float>> out(block);
float norm = 1.0 / sqrt(block);
for (int bi = 0; bi < block; bi++)
{
float re = m_out[bi][0];
float im = m_out[bi][1];
std::complex<float> c(re, im);
c *= norm;
out[bi] = c;
}
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return out;
}
std::vector<std::complex<float>> one_fft_cc(
const std::vector<std::complex<float>> &samples,
int i0,
int block,
const char *why
)
{
assert(i0 >= 0);
assert(block > 1);
int nsamples = samples.size();
Plan *p = get_plan(block, why);
fftwf_plan m_plan = p->cfwd_;
#if TIMING
float t0 = now();
#endif
fftwf_complex *m_in = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
fftwf_complex *m_out = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
assert(m_in && m_out);
for (int i = 0; i < block; i++)
{
if (i0 + i < nsamples)
{
m_in[i][0] = samples[i0 + i].real();
m_in[i][1] = samples[i0 + i].imag();
}
else
{
m_in[i][0] = 0;
m_in[i][1] = 0;
}
}
fftwf_execute_dft(m_plan, m_in, m_out);
std::vector<std::complex<float>> out(block);
// float norm = 1.0 / sqrt(block);
for (int bi = 0; bi < block; bi++)
{
float re = m_out[bi][0];
float im = m_out[bi][1];
std::complex<float> c(re, im);
// c *= norm;
out[bi] = c;
}
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return out;
}
std::vector<std::complex<float>> one_ifft_cc(
const std::vector<std::complex<float>> &bins,
const char *why
)
{
int block = bins.size();
Plan *p = get_plan(block, why);
fftwf_plan m_plan = p->crev_;
#if TIMING
float t0 = now();
#endif
fftwf_complex *m_in = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
fftwf_complex *m_out = (fftwf_complex *)fftwf_malloc(block * sizeof(fftwf_complex));
assert(m_in && m_out);
for (int bi = 0; bi < block; bi++)
{
float re = bins[bi].real();
float im = bins[bi].imag();
m_in[bi][0] = re;
m_in[bi][1] = im;
}
fftwf_execute_dft(m_plan, m_in, m_out);
std::vector<std::complex<float>> out(block);
float norm = 1.0 / sqrt(block);
for (int i = 0; i < block; i++)
{
float re = m_out[i][0];
float im = m_out[i][1];
std::complex<float> c(re, im);
c *= norm;
out[i] = c;
}
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return out;
}
std::vector<float> one_ifft(const std::vector<std::complex<float>> &bins, const char *why)
{
int nbins = bins.size();
int block = (nbins - 1) * 2;
Plan *p = get_plan(block, why);
fftwf_plan m_plan = p->rev_;
#if TIMING
float t0 = now();
#endif
fftwf_complex *m_in = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * ((p->n_ / 2) + 1));
float *m_out = (float *)fftwf_malloc(sizeof(float) * p->n_);
for (int bi = 0; bi < nbins; bi++)
{
float re = bins[bi].real();
float im = bins[bi].imag();
m_in[bi][0] = re;
m_in[bi][1] = im;
}
fftwf_execute_dft_c2r(m_plan, m_in, m_out);
std::vector<float> out(block);
for (int i = 0; i < block; i++)
{
out[i] = m_out[i];
}
fftwf_free(m_in);
fftwf_free(m_out);
#if TIMING
p->time_ += now() - t0;
#endif
return out;
}
//
// return the analytic signal for signal x,
// just like scipy.signal.hilbert(), from which
// this code is copied.
//
// the return value is x + iy, where y is the hilbert transform of x.
//
std::vector<std::complex<float>> analytic(const std::vector<float> &x, const char *why)
{
ulong n = x.size();
std::vector<std::complex<float>> y = one_fft_c(x, 0, n, why);
assert(y.size() == n);
// leave y[0] alone.
// float the first (positive) half of the spectrum.
// zero out the second (negative) half of the spectrum.
// y[n/2] is the nyquist bucket if n is even; leave it alone.
if ((n % 2) == 0)
{
for (ulong i = 1; i < n / 2; i++)
y[i] *= 2;
for (ulong i = n / 2 + 1; i < n; i++)
y[i] = 0;
}
else
{
for (ulong i = 1; i < (n + 1) / 2; i++)
y[i] *= 2;
for (ulong i = (n + 1) / 2; i < n; i++)
y[i] = 0;
}
std::vector<std::complex<float>> z = one_ifft_cc(y, why);
return z;
}
//
// general-purpose shift x in frequency by hz.
// uses hilbert transform to avoid sidebands.
// but it does wrap around at 0 hz and the nyquist frequency.
//
// note analytic() does an FFT over the whole signal, which
// is expensive, and often re-used, but it turns out it
// isn't a big factor in overall run-time.
//
// like weakutil.py's freq_shift().
//
std::vector<float> hilbert_shift(const std::vector<float> &x, float hz0, float hz1, int rate)
{
// y = scipy.signal.hilbert(x)
std::vector<std::complex<float>> y = analytic(x, "hilbert_shift");
assert(y.size() == x.size());
float dt = 1.0 / rate;
int n = x.size();
std::vector<float> ret(n);
for (int i = 0; i < n; i++)
{
// complex "local oscillator" at hz.
float hz = hz0 + (i / (float)n) * (hz1 - hz0);
std::complex<float> lo = std::exp(std::complex<float>(0.0, 2 * M_PI * hz * dt * i));
ret[i] = (lo * y[i]).real();
}
return ret;
}
void
fft_stats()
{
for (int i = 0; i < nplans; i++)
{
Plan *p = plans[i];
printf("%-13s %6d %9d %6.3f\n",
p->why_,
p->n_,
p->uses_,
#if TIMING
p->time_
#else
0.0
#endif
);
}
}
} // namespace FT8

46
ft8/fft.h Normal file
View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef FFT_H
#define FFT_H
#include <vector>
#include <complex>
#include <fftw3.h>
namespace FT8
{
class Plan;
Plan *get_plan(int n, const char *why);
std::vector<std::complex<float>> one_fft(const std::vector<float> &samples, int i0, int block, const char *why, Plan *p);
std::vector<float> one_ifft(const std::vector<std::complex<float>> &bins, const char *why);
typedef std::vector<std::vector<std::complex<float>>> ffts_t;
ffts_t ffts(const std::vector<float> &samples, int i0, int block, const char *why);
std::vector<std::complex<float>> one_fft_c(const std::vector<float> &samples, int i0, int block, const char *why);
std::vector<std::complex<float>> one_fft_cc(const std::vector<std::complex<float>> &samples, int i0, int block, const char *why);
std::vector<std::complex<float>> one_ifft_cc(const std::vector<std::complex<float>> &bins, const char *why);
std::vector<std::complex<float>> analytic(const std::vector<float> &x, const char *why);
std::vector<float> hilbert_shift(const std::vector<float> &x, float hz0, float hz1, int rate);
} // namespace FT8
#endif

3914
ft8/ft8.cpp Normal file

File diff suppressed because it is too large Load Diff

65
ft8/ft8.h Normal file
View File

@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef ft8_h
#define ft8_h
namespace FT8 {
// Callback function to get the results
typedef int (*cb_t)(
int *a91,
float hz0,
float hz1,
float off,
const char *,
float snr,
int pass,
int correct_bits
);
// same as Python class CDECODE
//
struct cdecode
{
float hz0;
float hz1;
float off;
int *bits; // 174
};
void entry(
float xsamples[],
int nsamples,
int start,
int rate,
float min_hz,
float max_hz,
int hints1[],
int hints2[],
float time_left,
float total_time_left,
cb_t cb,
int,
struct cdecode *
);
float set(char *param, char *val);
} // namespace FT8
#endif

747
ft8/libldpc.cpp Normal file
View File

@ -0,0 +1,747 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
//
// Low Density Parity Check (LDPC) decoder for new FT8.
//
// given a 174-bit codeword as an array of log-likelihood of zero,
// return a 174-bit corrected codeword, or zero-length array.
// first 91 bits are the (systematic) plain-text.
// codeword[i] = log ( P(x=0) / P(x=1) )
//
// this is an implementation of the sum-product algorithm
// from Sarah Johnson's Iterative Error Correction book, and
// Bernhard Leiner's http://www.bernh.net/media/download/papers/ldpc.pdf
//
// cc -O3 libldpc.c -shared -fPIC -o libldpc.so
//
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include "arrays.h"
// float, long float, __float128
#define REAL float
namespace FT8
{
//
// does a 174-bit codeword pass the FT8's LDPC parity checks?
// returns the number of parity checks that passed.
// 83 means total success.
//
int ldpc_check(int codeword[])
{
int score = 0;
// Nm[83][7]
for (int j = 0; j < 83; j++)
{
int x = 0;
for (int ii1 = 0; ii1 < 7; ii1++)
{
int i1 = Nm[j][ii1] - 1;
if (i1 >= 0)
{
x ^= codeword[i1];
}
}
if (x == 0)
score++;
}
return score;
}
// llcodeword is 174 log-likelihoods.
// plain is a return value, 174 ints, to be 0 or 1.
// iters is how hard to try.
// ok is the number of parity checks that worked out,
// ok == 83 means success.
void ldpc_decode(float llcodeword[], int iters, int plain[], int *ok)
{
REAL m[83][174];
REAL e[83][174];
REAL codeword[174];
int best_score = -1;
int best_cw[174];
// to translate from log-likelihood x to probability p,
// p = e**x / (1 + e**x)
// it's P(zero), not P(one).
for (int i = 0; i < 174; i++)
{
REAL ex = expl(llcodeword[i]);
REAL p = ex / (1.0 + ex);
codeword[i] = p;
}
// m[j][i] tells the j'th check bit the P(zero) of
// each of its codeword inputs, based on check
// bits other than j.
for (int i = 0; i < 174; i++)
for (int j = 0; j < 83; j++)
m[j][i] = codeword[i];
// e[j][i]: each check j tells each codeword bit i the
// probability of the bit being zero based
// on the *other* bits contributing to that check.
for (int i = 0; i < 174; i++)
for (int j = 0; j < 83; j++)
e[j][i] = 0.0;
for (int iter = 0; iter < iters; iter++)
{
for (int j = 0; j < 83; j++)
{
for (int ii1 = 0; ii1 < 7; ii1++)
{
int i1 = Nm[j][ii1] - 1;
if (i1 < 0)
continue;
REAL a = 1.0;
for (int ii2 = 0; ii2 < 7; ii2++)
{
int i2 = Nm[j][ii2] - 1;
if (i2 >= 0 && i2 != i1)
{
// tmp ranges from 1.0 to -1.0, for
// definitely zero to definitely one.
float tmp = 1.0 - 2.0 * (1.0 - m[j][i2]);
a *= tmp;
}
}
// a ranges from 1.0 to -1.0, meaning
// bit i1 should be zero .. one.
// so e[j][i1] will be 0.0 .. 1.0 meaning
// bit i1 is one .. zero.
REAL tmp = 0.5 + 0.5 * a;
e[j][i1] = tmp;
}
}
int cw[174];
for (int i = 0; i < 174; i++)
{
REAL q0 = codeword[i];
REAL q1 = 1.0 - q0;
for (int j = 0; j < 3; j++)
{
int j2 = Mn[i][j] - 1;
q0 *= e[j2][i];
q1 *= 1.0 - e[j2][i];
}
// REAL p = q0 / (q0 + q1);
REAL p;
if (q0 == 0.0)
{
p = 1.0;
}
else
{
p = 1.0 / (1.0 + (q1 / q0));
}
cw[i] = (p <= 0.5);
}
int score = ldpc_check(cw);
if (score == 83)
{
for (int i = 0; i < 174; i++)
plain[i] = cw[i];
*ok = 83;
return;
}
if (score > best_score)
{
for (int i = 0; i < 174; i++)
best_cw[i] = cw[i];
best_score = score;
}
for (int i = 0; i < 174; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
int j1 = Mn[i][ji1] - 1;
REAL q0 = codeword[i];
REAL q1 = 1.0 - q0;
for (int ji2 = 0; ji2 < 3; ji2++)
{
int j2 = Mn[i][ji2] - 1;
if (j1 != j2)
{
q0 *= e[j2][i];
q1 *= 1.0 - e[j2][i];
}
}
// REAL p = q0 / (q0 + q1);
REAL p;
if (q0 == 0.0)
{
p = 1.0;
}
else
{
p = 1.0 / (1.0 + (q1 / q0));
}
m[j1][i] = p;
}
}
}
// decode didn't work, return best guess.
for (int i = 0; i < 174; i++)
plain[i] = best_cw[i];
*ok = best_score;
}
// thank you Douglas Bagnall
// https://math.stackexchange.com/a/446411
float fast_tanh(float x)
{
if (x < -7.6)
{
return -0.999;
}
if (x > 7.6)
{
return 0.999;
}
float x2 = x * x;
float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2)));
float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f));
return a / b;
}
#if 0
#define TANGRAN 0.01
static float tanhtable[];
float
table_tanh(float x)
{
int ind = (x - (-5.0)) / TANGRAN;
if(ind < 0){
return -1.0;
}
if(ind >= 1000){
return 1.0;
}
return tanhtable[ind];
}
#endif
// codeword is 174 log-likelihoods.
// plain is a return value, 174 ints, to be 0 or 1.
// iters is how hard to try.
// ok is the number of parity checks that worked out,
// ok == 83 means success.
void ldpc_decode_log(float codeword[], int iters, int plain[], int *ok)
{
REAL m[83][174];
REAL e[83][174];
int best_score = -1;
int best_cw[174];
for (int i = 0; i < 174; i++)
for (int j = 0; j < 83; j++)
m[j][i] = codeword[i];
for (int i = 0; i < 174; i++)
for (int j = 0; j < 83; j++)
e[j][i] = 0.0;
for (int iter = 0; iter < iters; iter++)
{
for (int j = 0; j < 83; j++)
{
for (int ii1 = 0; ii1 < 7; ii1++)
{
int i1 = Nm[j][ii1] - 1;
if (i1 < 0)
continue;
REAL a = 1.0;
for (int ii2 = 0; ii2 < 7; ii2++)
{
int i2 = Nm[j][ii2] - 1;
if (i2 >= 0 && i2 != i1)
{
// a *= table_tanh(m[j][i2] / 2.0);
a *= fast_tanh(m[j][i2] / 2.0);
}
}
REAL tmp;
if (a >= 0.999)
{
tmp = 7.6;
}
else if (a <= -0.999)
{
tmp = -7.6;
}
else
{
tmp = log((1 + a) / (1 - a));
}
e[j][i1] = tmp;
}
}
int cw[174];
for (int i = 0; i < 174; i++)
{
REAL l = codeword[i];
for (int j = 0; j < 3; j++)
l += e[Mn[i][j] - 1][i];
cw[i] = (l <= 0.0);
}
int score = ldpc_check(cw);
if (score == 83)
{
for (int i = 0; i < 174; i++)
plain[i] = cw[i];
*ok = 83;
return;
}
if (score > best_score)
{
for (int i = 0; i < 174; i++)
best_cw[i] = cw[i];
best_score = score;
}
for (int i = 0; i < 174; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
int j1 = Mn[i][ji1] - 1;
REAL l = codeword[i];
for (int ji2 = 0; ji2 < 3; ji2++)
{
int j2 = Mn[i][ji2] - 1;
if (j1 != j2)
{
l += e[j2][i];
}
}
m[j1][i] = l;
}
}
}
// decode didn't work, return best guess.
for (int i = 0; i < 174; i++)
plain[i] = best_cw[i];
*ok = best_score;
}
//
// check the FT8 CRC-14
//
void ft8_crc(int msg1[], int msglen, int out[14])
{
// the old FT8 polynomial for 12-bit CRC, 0xc06.
// int div[] = { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0 };
// the new FT8 polynomial for 14-bit CRC, 0x2757,
// with leading 1 bit.
int div[] = {1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1};
// append 14 zeros.
int *msg = (int *)malloc(sizeof(int) * (msglen + 14));
for (int i = 0; i < msglen + 14; i++)
{
if (i < msglen)
{
msg[i] = msg1[i];
}
else
{
msg[i] = 0;
}
}
for (int i = 0; i < msglen; i++)
{
if (msg[i])
{
for (int j = 0; j < 15; j++)
{
msg[i + j] = (msg[i + j] + div[j]) % 2;
}
}
}
for (int i = 0; i < 14; i++)
{
out[i] = msg[msglen + i];
}
free(msg);
}
// rows is 91, cols is 174.
// m[174][2*91].
// m's right half should start out as zeros.
// m's upper-right quarter will be the desired inverse.
void gauss_jordan(int rows, int cols, int m[174][2 * 91], int which[91], int *ok)
// gauss_jordan(int rows, int cols, int m[cols][2*rows], int which[rows], int *ok)
{
*ok = 0;
assert(rows == 91);
assert(cols == 174);
for (int row = 0; row < rows; row++)
{
if (m[row][row] != 1)
{
for (int row1 = row + 1; row1 < cols; row1++)
{
if (m[row1][row] == 1)
{
// swap m[row] and m[row1]
for (int col = 0; col < 2 * rows; col++)
{
int tmp = m[row][col];
m[row][col] = m[row1][col];
m[row1][col] = tmp;
}
int tmp = which[row];
which[row] = which[row1];
which[row1] = tmp;
break;
}
}
}
if (m[row][row] != 1)
{
// could not invert
*ok = 0;
return;
}
// lazy creation of identity matrix in the upper-right quarter
m[row][rows + row] = (m[row][rows + row] + 1) % 2;
// now eliminate
for (int row1 = 0; row1 < cols; row1++)
{
if (row1 == row)
continue;
if (m[row1][row] != 0)
{
for (int col = 0; col < 2 * rows; col++)
{
m[row1][col] = (m[row1][col] + m[row][col]) % 2;
}
}
}
}
*ok = 1;
}
// # given a 174-bit codeword as an array of log-likelihood of zero,
// # return a 87-bit plain text, or zero-length array.
// # this is an implementation of the sum-product algorithm
// # from Sarah Johnson's Iterative Error Correction book.
// # codeword[i] = log ( P(x=0) / P(x=1) )
// def ldpc_decode(self, codeword):
// # 174 codeword bits
// # 87 parity checks
//
// # Mji
// # each codeword bit i tells each parity check j
// # what the bit's log-likelihood of being 0 is
// # based on information *other* than from that
// # parity check.
// m = numpy.zeros((87, 174))
//
// # Eji
// # each check j tells each codeword bit i the
// # log likelihood of the bit being zero based
// # on the *other* bits in that check.
// e = numpy.zeros((87, 174))
//
// for i in range(0, 174):
// for j in range(0, 87):
// m[j][i] = codeword[i]
//
// for iter in range(0, 50):
// # messages from checks to bits.
// # for each parity check
// for j in range(0, 87):
// # for each bit mentioned in this parity check
// for i in Nm[j]:
// if i <= 0:
// continue
// a = 1
// # for each other bit mentioned in this parity check
// for ii in Nm[j]:
// if ii != i:
// a *= math.tanh(m[j][ii-1] / 2.0)
// e[j][i-1] = math.log((1 + a) / (1 - a))
//
// # decide if we are done -- compute the corrected codeword,
// # see if the parity check succeeds.
// cw = numpy.zeros(174, dtype=numpy.int32)
// for i in range(0, 174):
// # sum the log likelihoods for codeword bit i being 0.
// l = codeword[i]
// for j in Mn[i]:
// l += e[j-1][i]
// if l > 0:
// cw[i] = 0
// else:
// cw[i] = 1
// if self.ldpc_check(cw):
// # success!
// # it's a systematic code, though the plain-text bits are scattered.
// # collect them.
// decoded = cw[colorder]
// decoded = decoded[-87:]
// return decoded
//
// # messages from bits to checks.
// for i in range(0, 174):
// for j in Mn[i]:
// l = codeword[i]
// for jj in Mn[i]:
// if jj != j:
// l += e[jj-1][i]
// m[j-1][i] = l
//
// # could not decode.
// return numpy.array([])
#if 0
static float tanhtable[] = {
-0.99990920, -0.99990737, -0.99990550, -0.99990359, -0.99990164,
-0.99989966, -0.99989763, -0.99989556, -0.99989345, -0.99989130,
-0.99988910, -0.99988686, -0.99988458, -0.99988225, -0.99987987,
-0.99987744, -0.99987496, -0.99987244, -0.99986986, -0.99986723,
-0.99986455, -0.99986182, -0.99985902, -0.99985618, -0.99985327,
-0.99985031, -0.99984728, -0.99984420, -0.99984105, -0.99983784,
-0.99983457, -0.99983122, -0.99982781, -0.99982434, -0.99982079,
-0.99981717, -0.99981348, -0.99980971, -0.99980586, -0.99980194,
-0.99979794, -0.99979386, -0.99978970, -0.99978545, -0.99978111,
-0.99977669, -0.99977218, -0.99976758, -0.99976289, -0.99975810,
-0.99975321, -0.99974823, -0.99974314, -0.99973795, -0.99973266,
-0.99972726, -0.99972175, -0.99971613, -0.99971040, -0.99970455,
-0.99969858, -0.99969249, -0.99968628, -0.99967994, -0.99967348,
-0.99966688, -0.99966016, -0.99965329, -0.99964629, -0.99963914,
-0.99963186, -0.99962442, -0.99961683, -0.99960910, -0.99960120,
-0.99959315, -0.99958493, -0.99957655, -0.99956799, -0.99955927,
-0.99955037, -0.99954129, -0.99953202, -0.99952257, -0.99951293,
-0.99950309, -0.99949305, -0.99948282, -0.99947237, -0.99946171,
-0.99945084, -0.99943975, -0.99942844, -0.99941690, -0.99940512,
-0.99939311, -0.99938085, -0.99936835, -0.99935559, -0.99934258,
-0.99932930, -0.99931576, -0.99930194, -0.99928784, -0.99927346,
-0.99925879, -0.99924382, -0.99922855, -0.99921297, -0.99919708,
-0.99918087, -0.99916432, -0.99914745, -0.99913024, -0.99911267,
-0.99909476, -0.99907648, -0.99905783, -0.99903881, -0.99901940,
-0.99899960, -0.99897940, -0.99895879, -0.99893777, -0.99891632,
-0.99889444, -0.99887212, -0.99884935, -0.99882612, -0.99880242,
-0.99877824, -0.99875358, -0.99872841, -0.99870274, -0.99867655,
-0.99864983, -0.99862258, -0.99859477, -0.99856640, -0.99853747,
-0.99850794, -0.99847782, -0.99844710, -0.99841575, -0.99838377,
-0.99835115, -0.99831787, -0.99828392, -0.99824928, -0.99821395,
-0.99817790, -0.99814112, -0.99810361, -0.99806533, -0.99802629,
-0.99798646, -0.99794582, -0.99790437, -0.99786208, -0.99781894,
-0.99777493, -0.99773003, -0.99768423, -0.99763750, -0.99758983,
-0.99754120, -0.99749159, -0.99744099, -0.99738936, -0.99733669,
-0.99728296, -0.99722815, -0.99717223, -0.99711519, -0.99705700,
-0.99699764, -0.99693708, -0.99687530, -0.99681228, -0.99674798,
-0.99668240, -0.99661549, -0.99654724, -0.99647761, -0.99640658,
-0.99633412, -0.99626020, -0.99618480, -0.99610788, -0.99602941,
-0.99594936, -0.99586770, -0.99578440, -0.99569942, -0.99561273,
-0.99552430, -0.99543409, -0.99534207, -0.99524820, -0.99515244,
-0.99505475, -0.99495511, -0.99485345, -0.99474976, -0.99464398,
-0.99453608, -0.99442601, -0.99431373, -0.99419919, -0.99408235,
-0.99396317, -0.99384159, -0.99371757, -0.99359107, -0.99346202,
-0.99333039, -0.99319611, -0.99305914, -0.99291942, -0.99277690,
-0.99263152, -0.99248323, -0.99233196, -0.99217766, -0.99202027,
-0.99185972, -0.99169596, -0.99152892, -0.99135853, -0.99118473,
-0.99100745, -0.99082663, -0.99064218, -0.99045404, -0.99026214,
-0.99006640, -0.98986674, -0.98966309, -0.98945538, -0.98924351,
-0.98902740, -0.98880698, -0.98858216, -0.98835285, -0.98811896,
-0.98788040, -0.98763708, -0.98738891, -0.98713578, -0.98687761,
-0.98661430, -0.98634574, -0.98607182, -0.98579245, -0.98550752,
-0.98521692, -0.98492053, -0.98461825, -0.98430995, -0.98399553,
-0.98367486, -0.98334781, -0.98301427, -0.98267411, -0.98232720,
-0.98197340, -0.98161259, -0.98124462, -0.98086936, -0.98048667,
-0.98009640, -0.97969840, -0.97929252, -0.97887862, -0.97845654,
-0.97802611, -0.97758719, -0.97713959, -0.97668317, -0.97621774,
-0.97574313, -0.97525917, -0.97476568, -0.97426247, -0.97374936,
-0.97322616, -0.97269268, -0.97214872, -0.97159408, -0.97102855,
-0.97045194, -0.96986402, -0.96926459, -0.96865342, -0.96803030,
-0.96739500, -0.96674729, -0.96608693, -0.96541369, -0.96472732,
-0.96402758, -0.96331422, -0.96258698, -0.96184561, -0.96108983,
-0.96031939, -0.95953401, -0.95873341, -0.95791731, -0.95708542,
-0.95623746, -0.95537312, -0.95449211, -0.95359412, -0.95267884,
-0.95174596, -0.95079514, -0.94982608, -0.94883842, -0.94783185,
-0.94680601, -0.94576057, -0.94469516, -0.94360942, -0.94250301,
-0.94137554, -0.94022664, -0.93905593, -0.93786303, -0.93664754,
-0.93540907, -0.93414721, -0.93286155, -0.93155168, -0.93021718,
-0.92885762, -0.92747257, -0.92606158, -0.92462422, -0.92316003,
-0.92166855, -0.92014933, -0.91860189, -0.91702576, -0.91542046,
-0.91378549, -0.91212037, -0.91042459, -0.90869766, -0.90693905,
-0.90514825, -0.90332474, -0.90146799, -0.89957745, -0.89765260,
-0.89569287, -0.89369773, -0.89166660, -0.88959892, -0.88749413,
-0.88535165, -0.88317089, -0.88095127, -0.87869219, -0.87639307,
-0.87405329, -0.87167225, -0.86924933, -0.86678393, -0.86427541,
-0.86172316, -0.85912654, -0.85648492, -0.85379765, -0.85106411,
-0.84828364, -0.84545560, -0.84257933, -0.83965418, -0.83667949,
-0.83365461, -0.83057887, -0.82745161, -0.82427217, -0.82103988,
-0.81775408, -0.81441409, -0.81101926, -0.80756892, -0.80406239,
-0.80049902, -0.79687814, -0.79319910, -0.78946122, -0.78566386,
-0.78180636, -0.77788807, -0.77390834, -0.76986654, -0.76576202,
-0.76159416, -0.75736232, -0.75306590, -0.74870429, -0.74427687,
-0.73978305, -0.73522225, -0.73059390, -0.72589741, -0.72113225,
-0.71629787, -0.71139373, -0.70641932, -0.70137413, -0.69625767,
-0.69106947, -0.68580906, -0.68047601, -0.67506987, -0.66959026,
-0.66403677, -0.65840904, -0.65270671, -0.64692945, -0.64107696,
-0.63514895, -0.62914516, -0.62306535, -0.61690930, -0.61067683,
-0.60436778, -0.59798200, -0.59151940, -0.58497988, -0.57836341,
-0.57166997, -0.56489955, -0.55805222, -0.55112803, -0.54412710,
-0.53704957, -0.52989561, -0.52266543, -0.51535928, -0.50797743,
-0.50052021, -0.49298797, -0.48538109, -0.47770001, -0.46994520,
-0.46211716, -0.45421643, -0.44624361, -0.43819931, -0.43008421,
-0.42189901, -0.41364444, -0.40532131, -0.39693043, -0.38847268,
-0.37994896, -0.37136023, -0.36270747, -0.35399171, -0.34521403,
-0.33637554, -0.32747739, -0.31852078, -0.30950692, -0.30043710,
-0.29131261, -0.28213481, -0.27290508, -0.26362484, -0.25429553,
-0.24491866, -0.23549575, -0.22602835, -0.21651806, -0.20696650,
-0.19737532, -0.18774621, -0.17808087, -0.16838105, -0.15864850,
-0.14888503, -0.13909245, -0.12927258, -0.11942730, -0.10955847,
-0.09966799, -0.08975778, -0.07982977, -0.06988589, -0.05992810,
-0.04995837, -0.03997868, -0.02999100, -0.01999733, -0.00999967,
-0.00000000, 0.00999967, 0.01999733, 0.02999100, 0.03997868,
0.04995837, 0.05992810, 0.06988589, 0.07982977, 0.08975778,
0.09966799, 0.10955847, 0.11942730, 0.12927258, 0.13909245,
0.14888503, 0.15864850, 0.16838105, 0.17808087, 0.18774621,
0.19737532, 0.20696650, 0.21651806, 0.22602835, 0.23549575,
0.24491866, 0.25429553, 0.26362484, 0.27290508, 0.28213481,
0.29131261, 0.30043710, 0.30950692, 0.31852078, 0.32747739,
0.33637554, 0.34521403, 0.35399171, 0.36270747, 0.37136023,
0.37994896, 0.38847268, 0.39693043, 0.40532131, 0.41364444,
0.42189901, 0.43008421, 0.43819931, 0.44624361, 0.45421643,
0.46211716, 0.46994520, 0.47770001, 0.48538109, 0.49298797,
0.50052021, 0.50797743, 0.51535928, 0.52266543, 0.52989561,
0.53704957, 0.54412710, 0.55112803, 0.55805222, 0.56489955,
0.57166997, 0.57836341, 0.58497988, 0.59151940, 0.59798200,
0.60436778, 0.61067683, 0.61690930, 0.62306535, 0.62914516,
0.63514895, 0.64107696, 0.64692945, 0.65270671, 0.65840904,
0.66403677, 0.66959026, 0.67506987, 0.68047601, 0.68580906,
0.69106947, 0.69625767, 0.70137413, 0.70641932, 0.71139373,
0.71629787, 0.72113225, 0.72589741, 0.73059390, 0.73522225,
0.73978305, 0.74427687, 0.74870429, 0.75306590, 0.75736232,
0.76159416, 0.76576202, 0.76986654, 0.77390834, 0.77788807,
0.78180636, 0.78566386, 0.78946122, 0.79319910, 0.79687814,
0.80049902, 0.80406239, 0.80756892, 0.81101926, 0.81441409,
0.81775408, 0.82103988, 0.82427217, 0.82745161, 0.83057887,
0.83365461, 0.83667949, 0.83965418, 0.84257933, 0.84545560,
0.84828364, 0.85106411, 0.85379765, 0.85648492, 0.85912654,
0.86172316, 0.86427541, 0.86678393, 0.86924933, 0.87167225,
0.87405329, 0.87639307, 0.87869219, 0.88095127, 0.88317089,
0.88535165, 0.88749413, 0.88959892, 0.89166660, 0.89369773,
0.89569287, 0.89765260, 0.89957745, 0.90146799, 0.90332474,
0.90514825, 0.90693905, 0.90869766, 0.91042459, 0.91212037,
0.91378549, 0.91542046, 0.91702576, 0.91860189, 0.92014933,
0.92166855, 0.92316003, 0.92462422, 0.92606158, 0.92747257,
0.92885762, 0.93021718, 0.93155168, 0.93286155, 0.93414721,
0.93540907, 0.93664754, 0.93786303, 0.93905593, 0.94022664,
0.94137554, 0.94250301, 0.94360942, 0.94469516, 0.94576057,
0.94680601, 0.94783185, 0.94883842, 0.94982608, 0.95079514,
0.95174596, 0.95267884, 0.95359412, 0.95449211, 0.95537312,
0.95623746, 0.95708542, 0.95791731, 0.95873341, 0.95953401,
0.96031939, 0.96108983, 0.96184561, 0.96258698, 0.96331422,
0.96402758, 0.96472732, 0.96541369, 0.96608693, 0.96674729,
0.96739500, 0.96803030, 0.96865342, 0.96926459, 0.96986402,
0.97045194, 0.97102855, 0.97159408, 0.97214872, 0.97269268,
0.97322616, 0.97374936, 0.97426247, 0.97476568, 0.97525917,
0.97574313, 0.97621774, 0.97668317, 0.97713959, 0.97758719,
0.97802611, 0.97845654, 0.97887862, 0.97929252, 0.97969840,
0.98009640, 0.98048667, 0.98086936, 0.98124462, 0.98161259,
0.98197340, 0.98232720, 0.98267411, 0.98301427, 0.98334781,
0.98367486, 0.98399553, 0.98430995, 0.98461825, 0.98492053,
0.98521692, 0.98550752, 0.98579245, 0.98607182, 0.98634574,
0.98661430, 0.98687761, 0.98713578, 0.98738891, 0.98763708,
0.98788040, 0.98811896, 0.98835285, 0.98858216, 0.98880698,
0.98902740, 0.98924351, 0.98945538, 0.98966309, 0.98986674,
0.99006640, 0.99026214, 0.99045404, 0.99064218, 0.99082663,
0.99100745, 0.99118473, 0.99135853, 0.99152892, 0.99169596,
0.99185972, 0.99202027, 0.99217766, 0.99233196, 0.99248323,
0.99263152, 0.99277690, 0.99291942, 0.99305914, 0.99319611,
0.99333039, 0.99346202, 0.99359107, 0.99371757, 0.99384159,
0.99396317, 0.99408235, 0.99419919, 0.99431373, 0.99442601,
0.99453608, 0.99464398, 0.99474976, 0.99485345, 0.99495511,
0.99505475, 0.99515244, 0.99524820, 0.99534207, 0.99543409,
0.99552430, 0.99561273, 0.99569942, 0.99578440, 0.99586770,
0.99594936, 0.99602941, 0.99610788, 0.99618480, 0.99626020,
0.99633412, 0.99640658, 0.99647761, 0.99654724, 0.99661549,
0.99668240, 0.99674798, 0.99681228, 0.99687530, 0.99693708,
0.99699764, 0.99705700, 0.99711519, 0.99717223, 0.99722815,
0.99728296, 0.99733669, 0.99738936, 0.99744099, 0.99749159,
0.99754120, 0.99758983, 0.99763750, 0.99768423, 0.99773003,
0.99777493, 0.99781894, 0.99786208, 0.99790437, 0.99794582,
0.99798646, 0.99802629, 0.99806533, 0.99810361, 0.99814112,
0.99817790, 0.99821395, 0.99824928, 0.99828392, 0.99831787,
0.99835115, 0.99838377, 0.99841575, 0.99844710, 0.99847782,
0.99850794, 0.99853747, 0.99856640, 0.99859477, 0.99862258,
0.99864983, 0.99867655, 0.99870274, 0.99872841, 0.99875358,
0.99877824, 0.99880242, 0.99882612, 0.99884935, 0.99887212,
0.99889444, 0.99891632, 0.99893777, 0.99895879, 0.99897940,
0.99899960, 0.99901940, 0.99903881, 0.99905783, 0.99907648,
0.99909476, 0.99911267, 0.99913024, 0.99914745, 0.99916432,
0.99918087, 0.99919708, 0.99921297, 0.99922855, 0.99924382,
0.99925879, 0.99927346, 0.99928784, 0.99930194, 0.99931576,
0.99932930, 0.99934258, 0.99935559, 0.99936835, 0.99938085,
0.99939311, 0.99940512, 0.99941690, 0.99942844, 0.99943975,
0.99945084, 0.99946171, 0.99947237, 0.99948282, 0.99949305,
0.99950309, 0.99951293, 0.99952257, 0.99953202, 0.99954129,
0.99955037, 0.99955927, 0.99956799, 0.99957655, 0.99958493,
0.99959315, 0.99960120, 0.99960910, 0.99961683, 0.99962442,
0.99963186, 0.99963914, 0.99964629, 0.99965329, 0.99966016,
0.99966688, 0.99967348, 0.99967994, 0.99968628, 0.99969249,
0.99969858, 0.99970455, 0.99971040, 0.99971613, 0.99972175,
0.99972726, 0.99973266, 0.99973795, 0.99974314, 0.99974823,
0.99975321, 0.99975810, 0.99976289, 0.99976758, 0.99977218,
0.99977669, 0.99978111, 0.99978545, 0.99978970, 0.99979386,
0.99979794, 0.99980194, 0.99980586, 0.99980971, 0.99981348,
0.99981717, 0.99982079, 0.99982434, 0.99982781, 0.99983122,
0.99983457, 0.99983784, 0.99984105, 0.99984420, 0.99984728,
0.99985031, 0.99985327, 0.99985618, 0.99985902, 0.99986182,
0.99986455, 0.99986723, 0.99986986, 0.99987244, 0.99987496,
0.99987744, 0.99987987, 0.99988225, 0.99988458, 0.99988686,
0.99988910, 0.99989130, 0.99989345, 0.99989556, 0.99989763,
0.99989966, 0.99990164, 0.99990359, 0.99990550, 0.99990737,
0.99990920,
};
#endif
} // namespace FT8

36
ft8/libldpc.h Normal file
View File

@ -0,0 +1,36 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef libldpc_h
#define libldpc_h
namespace FT8 {
int ldpc_check(int codeword[]);
void ldpc_decode(float llcodeword[], int iters, int plain[], int *ok);
float fast_tanh(float x);
void ldpc_decode_log(float codeword[], int iters, int plain[], int *ok);
void ft8_crc(int msg1[], int msglen, int out[14]);
void gauss_jordan(int rows, int cols, int m[174][2 * 91], int which[91], int *ok);
} // namespace FT8
#endif // libldpc_h

489
ft8/osd.cpp Normal file
View File

@ -0,0 +1,489 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
//
// ordered statistics decoder for LDPC and new FT8.
// idea from wsjt-x.
//
#include <vector>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include "libldpc.h"
#include "osd.h"
namespace FT8 {
//
// check the FT8 CRC-14
//
int check_crc(const int a91[91])
{
int aa[91];
int non_zero = 0;
for (int i = 0; i < 91; i++)
{
if (i < 77)
{
aa[i] = a91[i];
}
else
{
aa[i] = 0;
}
if (aa[i])
non_zero++;
}
int out1[14];
// don't bother with all-zero messages.
if (non_zero == 0)
return 0;
// why 82? why not 77?
ft8_crc(aa, 82, out1);
for (int i = 0; i < 14; i++)
{
if (out1[i] != a91[91 - 14 + i])
{
return 0;
}
}
return 1;
}
// plain is 91 bits of plain-text.
// returns a 174-bit codeword.
// mimics wsjt-x's encode174_91.f90.
void ldpc_encode(int plain[91], int codeword[174])
{
// the systematic 91 bits.
for (int i = 0; i < 91; i++)
{
codeword[i] = plain[i];
}
// the 174-91 bits of redundancy.
for (int i = 0; i + 91 < 174; i++)
{
int sum = 0;
for (int j = 0; j < 91; j++)
{
sum += gen_sys[i + 91][j] * plain[j];
codeword[i + 91] = sum % 2;
}
}
}
// xplain is a possible original codeword.
// ll174 is what was received.
// ldpc-encode xplain; how close is the
// result to what we received?
float osd_score(int xplain[91], float ll174[174])
{
int xcode[174];
ldpc_encode(xplain, xcode);
float score = 0;
for (int i = 0; i < 174; i++)
{
if (xcode[i])
{
// one-bit, expect ll to be negative.
score -= ll174[i] * 4.6;
}
else
{
// zero-bit, expect ll to be positive.
score += ll174[i] * 4.6;
}
}
return -score;
}
// does a decode look plausible?
int osd_check(const int plain[91])
{
int allzero = 1;
for (int i = 0; i < 91; i++)
{
if (plain[i] != 0)
{
allzero = 0;
}
}
if (allzero)
{
return 0;
}
if (check_crc(plain) == 0)
{
return 0;
}
return 1;
}
void matmul(int a[91][91], int b[91], int c[91])
{
for (int i = 0; i < 91; i++)
{
int sum = 0;
for (int j = 0; j < 91; j++)
{
sum += a[i][j] * b[j];
}
c[i] = sum % 2;
}
}
// ordered statistics decoder for LDPC and new FT8.
// idea from wsjt-x.
// codeword[i] = log ( P(x=0) / P(x=1) )
// codeword has 174 bits.
// first 91 bits are plaintext, remaining 83 are parity.
// returns 0 or 1, with decoded plain bits in out91[].
// and actual depth used in *out_depth.
int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
{
// strength = abs(codeword)
float strength[174];
for (int i = 0; i < 174; i++)
{
float x = codeword[i];
strength[i] = (x < 0 ? -x : x);
}
// sort, strongest first; we'll use strongest 91.
std::vector<int> which(174);
for (int i = 0; i < 174; i++)
which[i] = i;
std::sort(which.begin(),
which.end(),
[=](int a, int b)
{
return strength[a] > strength[b];
});
// gen_sys[174 rows][91 cols] has a row per each of the 174 codeword bits,
// indicating how to generate it by xor with each of the 91 plain bits.
// generator matrix, reordered strongest codeword bit first.
int b[174][91 * 2];
for (int i = 0; i < 174; i++)
{
int ii = which[i];
for (int j = 0; j < 91 * 2; j++)
{
if (j < 91)
{
b[i][j] = gen_sys[ii][j];
}
else
{
b[i][j] = 0;
}
}
}
int xwhich[174];
for (int i = 0; i < 174; i++)
xwhich[i] = which[i];
int ok = 0;
gauss_jordan(91, 174, b, xwhich, &ok);
if (ok == 0)
{
fprintf(stderr, "gauss_jordan failed\n");
}
int gen1_inv[91][91];
for (int i = 0; i < 91; i++)
{
for (int j = 0; j < 91; j++)
{
gen1_inv[i][j] = b[i][91 + j];
}
}
for (int i = 0; i < 174; i++)
{
which[i] = xwhich[i];
}
// y1 is the received bits, same order as gen1_inv,
// more or less strongest-first, converted from
// log-likihood to 0/1.
int y1[91];
for (int i = 0; i < 91; i++)
{
int j = which[i];
y1[i] = (codeword[j] < 0 ? 1 : 0);
}
int best_plain[91];
float best_score = 0;
int got_a_best = 0;
int best_depth = -1;
// can we decode without flipping any bits?
int xplain[91];
matmul(gen1_inv, y1, xplain); // also does mod 2
int osd_thresh = -500;
float xscore = osd_score(xplain, codeword);
int ch = osd_check(xplain);
if (xscore < osd_thresh && ch)
{
if (got_a_best == 0 || xscore < best_score)
{
if (1)
{
// just accept this, since no bits had to be flipped.
memcpy(out, xplain, sizeof(xplain));
*out_depth = 0;
return 1;
}
else
{
got_a_best = 1;
memcpy(best_plain, xplain, sizeof(best_plain));
best_score = xscore;
best_depth = 0;
}
}
}
// flip a few bits, see if decode works.
for (int ii = 0; ii < depth; ii++)
{
int i = 91 - 1 - ii;
y1[i] ^= 1;
matmul(gen1_inv, y1, xplain);
y1[i] ^= 1;
float xscore = osd_score(xplain, codeword);
int ch = osd_check(xplain);
if (xscore < osd_thresh && ch)
{
if (got_a_best == 0 || xscore < best_score)
{
got_a_best = 1;
memcpy(best_plain, xplain, sizeof(best_plain));
best_score = xscore;
best_depth = ii;
}
}
}
if (got_a_best)
{
memcpy(out, best_plain, sizeof(best_plain));
*out_depth = best_depth;
return 1;
}
else
{
return 0;
}
}
int gen_sys[174][91] = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, },
{ 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, },
{ 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, },
{ 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, },
{ 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, },
{ 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, },
{ 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, },
{ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, },
{ 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, },
{ 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, },
{ 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, },
{ 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, },
{ 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, },
{ 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, },
{ 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, },
{ 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, },
{ 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, },
{ 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, },
{ 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, },
{ 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, },
{ 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, },
{ 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, },
{ 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, },
{ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, },
{ 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, },
{ 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, },
{ 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, },
{ 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, },
{ 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, },
{ 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, },
{ 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, },
{ 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, },
{ 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, },
{ 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, },
{ 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, },
{ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, },
{ 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, },
{ 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, },
{ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, },
{ 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, },
{ 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, },
{ 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, },
{ 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, },
{ 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, },
{ 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, },
{ 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, },
{ 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, },
{ 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, },
{ 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, },
{ 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, },
{ 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
{ 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, },
{ 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, },
{ 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, },
{ 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, },
{ 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, },
{ 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, },
{ 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, },
{ 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, },
{ 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, },
{ 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, },
{ 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, },
{ 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, },
{ 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, },
{ 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, },
{ 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, },
{ 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, },
{ 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, },
{ 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, },
{ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, },
{ 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },
{ 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, },
{ 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, },
{ 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, },
{ 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, },
{ 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, },
{ 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, },
{ 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, },
{ 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, },
{ 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, },
};
} // namespace FT8

37
ft8/osd.h Normal file
View File

@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef osd_h
#define osd_h
namespace FT8 {
extern int gen_sys[174][91];
int check_crc(const int a91[91]);
void ldpc_encode(int plain[91], int codeword[174]);
float osd_score(int xplain[91], float ll174[174]);
int osd_check(const int plain[91]);
void matmul(int a[91][91], int b[91], int c[91]);
int osd_decode(float codeword[174], int depth, int out[91], int *out_depth);
} // namepsace FT8
#endif // osd_h

551
ft8/unpack.cpp Normal file
View File

@ -0,0 +1,551 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <string>
#include <mutex>
#include <map>
#include <string.h>
#include <assert.h>
#include "unpack.h"
#include "util.h"
namespace FT8 {
//
// turn bits into a 128-bit integer.
// most significant bit first.
//
__int128 un(int a77[], int start, int len)
{
__int128 x = 0;
assert(len < (int)sizeof(x) * 8 && start >= 0 && start + len <= 77);
for (int i = 0; i < len; i++)
{
x <<= 1;
x |= a77[start + i];
}
return x;
}
std::mutex hashes_mu;
std::map<int, std::string> hashes12;
std::map<int, std::string> hashes22;
int ihashcall(std::string call, int m)
{
while (call.size() > 0 && call[0] == ' ')
call.erase(0, 1);
while (call.size() > 0 && call[call.size() - 1] == ' ')
call.erase(call.end() - 1);
const char *chars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/";
while (call.size() < 11)
call += " ";
unsigned long long x = 0;
for (int i = 0; i < 11; i++)
{
int c = call[i];
const char *p = strchr(chars, c);
assert(p);
int j = p - chars;
x = 38 * x + j;
}
x = x * 47055833459LL;
x = x >> (64 - m);
return x;
}
#define NGBASE (180 * 180)
#define NTOKENS 2063592
#define MAX22 4194304
//
// turn 28 bits of packed call into the call
//
std::string unpackcall(int x)
{
char tmp[64];
const char *c1 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *c2 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *c3 = "0123456789";
const char *c4 = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (x == 0)
return "DE";
if (x == 1)
return "QRZ";
if (x == 2)
return "CQ";
if (x <= 1002)
{
sprintf(tmp, "CQ %d", x - 3);
return tmp;
}
if (x <= 532443)
{
x -= 1003;
int ci1 = x / (27 * 27 * 27);
x %= 27 * 27 * 27;
int ci2 = x / (27 * 27);
x %= 27 * 27;
int ci3 = x / 27;
x %= 27;
int ci4 = x;
sprintf(tmp, "CQ %c%c%c%c", c4[ci1], c4[ci2], c4[ci3], c4[ci4]);
return tmp;
}
if (x < NTOKENS)
{
return "<TOKEN>";
}
x -= NTOKENS;
if (x < MAX22)
{
// 22-bit hash...
std::string s;
hashes_mu.lock();
if (hashes22.count(x) > 0)
{
s = hashes22[x];
}
else
{
s = "<...22>";
}
hashes_mu.unlock();
return s;
}
x -= MAX22;
char a[7];
a[5] = c4[x % 27];
x = x / 27;
a[4] = c4[x % 27];
x = x / 27;
a[3] = c4[x % 27];
x = x / 27;
a[2] = c3[x % 10];
x = x / 10;
a[1] = c2[x % 36];
x = x / 36;
a[0] = c1[x];
a[6] = '\0';
return a;
}
// unpack a 15-bit grid square &c.
// 77-bit version, from inspection of packjt77.f90.
// ir is the bit after the two 28+1-bit callee/caller.
// i3 is the message type, usually 1.
std::string unpackgrid(int ng, int ir, int i3)
{
(void) i3;
if (ng < NGBASE)
{
// maidenhead grid system:
// latitude from south pole to north pole.
// longitude eastward from anti-meridian.
// first: 20 degrees longitude.
// second: 10 degrees latitude.
// third: 2 degrees longitude.
// fourth: 1 degree latitude.
// so there are 18*18*10*10 possibilities.
int x1 = ng / (18 * 10 * 10);
ng %= 18 * 10 * 10;
int x2 = ng / (10 * 10);
ng %= 10 * 10;
int x3 = ng / 10;
ng %= 10;
int x4 = ng;
char tmp[5];
tmp[0] = 'A' + x1;
tmp[1] = 'A' + x2;
tmp[2] = '0' + x3;
tmp[3] = '0' + x4;
tmp[4] = '\0';
return tmp;
}
ng -= NGBASE;
if (ng == 1)
{
return " "; // ???
}
if (ng == 2)
{
return "RRR ";
}
if (ng == 3)
{
return "RR73";
}
if (ng == 4)
{
return "73 ";
}
int db = ng - 35;
char tmp[16];
if (db >= 0)
{
sprintf(tmp, "%s+%02d", ir ? "R" : "", db);
}
else
{
sprintf(tmp, "%s-%02d", ir ? "R" : "", 0 - db);
}
return tmp;
}
void remember_call(std::string call)
{
hashes_mu.lock();
if (call.size() >= 3 && call[0] != '<')
{
hashes22[ihashcall(call, 22)] = call;
hashes12[ihashcall(call, 12)] = call;
}
hashes_mu.unlock();
}
//
// i3 == 4
// a call that doesn't fit in 28 bits.
// 12 bits: hash of a previous call
// 58 bits: 11 characters
// 1 bit: swap
// 2 bits: 1 RRR, 2 RR73, 3 73
// 1 bit: 1 means CQ
std::string unpack_4(int a77[])
{
// 38 possible characters:
const char *chars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/";
long long n58 = un(a77, 12, 58);
char call[16];
for (int i = 0; i < 11; i++)
{
call[10 - i] = chars[n58 % 38];
n58 = n58 / 38;
}
call[11] = '\0';
remember_call(call);
if (un(a77, 73, 1) == 1)
{
return std::string("CQ ") + call;
}
int x12 = un(a77, 0, 12);
// 12-bit hash
hashes_mu.lock();
std::string ocall;
if (hashes12.count(x12) > 0)
{
ocall = hashes12[x12];
}
else
{
ocall = "<...12>";
}
hashes_mu.unlock();
int swap = un(a77, 70, 1);
std::string msg;
if (swap)
{
msg = std::string(call) + " " + ocall;
}
else
{
msg = std::string(ocall) + " " + call;
}
int suffix = un(a77, 71, 2);
if (suffix == 1)
{
msg += " RRR";
}
else if (suffix == 2)
{
msg += " RR73";
}
else if (suffix == 3)
{
msg += " 73";
}
return msg;
}
//
// i3=1
//
std::string unpack_1(int a77[])
{
// type 1:
// 28 call1
// 1 P/R
// 28 call2
// 1 P/R
// 1 ???
// 15 grid
// 3 type
int i = 0;
int call1 = un(a77, i, 28);
i += 28;
int rover1 = a77[i];
i += 1;
int call2 = un(a77, i, 28);
i += 28;
int rover2 = a77[i];
i += 1;
int ir = a77[i];
i += 1;
int grid = un(a77, i, 15);
i += 15;
int i3 = un(a77, i, 3);
i += 3;
assert((i3 == 1 || i3 == 2) && i == 77);
std::string call1text = trim(unpackcall(call1));
std::string call2text = trim(unpackcall(call2));
std::string gridtext = unpackgrid(grid, ir, i3);
remember_call(call1text);
remember_call(call2text);
const char *pr = (i3 == 1 ? "/R" : "/P");
return call1text + (rover1 ? pr : "") + " " + call2text + (rover2 ? pr : "") + " " + gridtext;
}
// free text
// 71 bits, 13 characters, each one of 42 choices.
// reversed.
// details from wsjt-x's packjt77.f90
std::string unpack_0_0(int a77[])
{
// the 42 possible characters.
const char *cc = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
__int128 x = un(a77, 0, 71);
std::string msg = "0123456789123";
for (int i = 0; i < 13; i++)
{
msg[13 - 1 - i] = cc[x % 42];
x = x / 42;
}
return msg;
}
// ARRL RTTY Round-Up states/provinces
const char *ru_states[] = {
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA",
"HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD",
"MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ",
"NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC",
"SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY",
"NB", "NS", "QC", "ON", "MB", "SK", "AB", "BC", "NWT", "NF",
"LB", "NU", "YT", "PEI", "DC"};
// i3=3
// 3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup
// 1 TU
// 28 call1
// 28 call2
// 1 R
// 3 RST 529 to 599
// 13 state/province/serialnumber
std::string unpack_3(int a77[])
{
int i = 0;
int tu = a77[i];
i += 1;
int call1 = un(a77, i, 28);
i += 28;
int call2 = un(a77, i, 28);
i += 28;
int r = a77[i];
i += 1;
int rst = un(a77, i, 3);
i += 3;
int serial = un(a77, i, 13);
i += 13;
std::string call1text = unpackcall(call1);
std::string call2text = unpackcall(call2);
rst = 529 + 10 * rst;
int statei = serial - 8001;
std::string serialstr;
int nstates = sizeof(ru_states) / sizeof(ru_states[0]);
if (serial > 8000 && statei < nstates)
{
serialstr = ru_states[statei];
}
else
{
char tmp[32];
sprintf(tmp, "%04d", serial);
serialstr = tmp;
}
std::string msg;
if (tu)
{
msg += "TU; ";
}
msg += call1text + " " + call2text + " ";
if (r)
{
msg += "R ";
}
{
char tmp[16];
sprintf(tmp, "%d ", rst);
msg += tmp;
}
msg += serialstr;
remember_call(call1text);
remember_call(call2text);
return msg;
}
// ARRL Field Day sections
const char *sections[] = {
"AB ", "AK ", "AL ", "AR ", "AZ ", "BC ", "CO ", "CT ", "DE ", "EB ",
"EMA", "ENY", "EPA", "EWA", "GA ", "GTA", "IA ", "ID ", "IL ", "IN ",
"KS ", "KY ", "LA ", "LAX", "MAR", "MB ", "MDC", "ME ", "MI ", "MN ",
"MO ", "MS ", "MT ", "NC ", "ND ", "NE ", "NFL", "NH ", "NL ", "NLI",
"NM ", "NNJ", "NNY", "NT ", "NTX", "NV ", "OH ", "OK ", "ONE", "ONN",
"ONS", "OR ", "ORG", "PAC", "PR ", "QC ", "RI ", "SB ", "SC ", "SCV",
"SD ", "SDG", "SF ", "SFL", "SJV", "SK ", "SNJ", "STX", "SV ", "TN ",
"UT ", "VA ", "VI ", "VT ", "WCF", "WI ", "WMA", "WNY", "WPA", "WTX",
"WV ", "WWA", "WY ", "DX "};
// i3 = 0, n3 = 3 or 4: ARRL Field Day
// 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day
// 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day
std::string unpack_0_3(int a77[], int n3)
{
int i = 0;
int call1 = un(a77, i, 28);
i += 28;
int call2 = un(a77, i, 28);
i += 28;
int R = un(a77, i, 1);
i += 1;
int n_transmitters = un(a77, i, 4);
if (n3 == 4)
n_transmitters += 16;
i += 4;
int clss = un(a77, i, 3); // class
i += 3;
int section = un(a77, i, 7); // ARRL section
i += 7;
std::string msg;
msg += unpackcall(call1);
msg += " ";
msg += unpackcall(call2);
msg += " ";
if (R)
msg += "R ";
{
char tmp[16];
sprintf(tmp, "%d%c ", n_transmitters + 1, clss + 'A');
msg += tmp;
}
if (section - 1 >= 0 && section - 1 < (int)(sizeof(sections) / sizeof(sections[0])))
{
msg += sections[section - 1];
}
return msg;
}
//
// unpack an FT8 message.
// a77 is 91 bits -- 77 plus the 14-bit CRC.
// CRC and LDPC have already been checked.
// details from wsjt-x's packjt77.f90 and 77bit.txt.
//
std::string unpack(int a77[])
{
int i3 = un(a77, 74, 3);
int n3 = un(a77, 71, 3);
if (i3 == 0 && n3 == 0)
{
// free text
return unpack_0_0(a77);
}
if (i3 == 0 && (n3 == 3 || n3 == 4))
{
// ARRL Field Day
return unpack_0_3(a77, n3);
}
if (i3 == 1 || i3 == 2)
{
// ordinary message
return unpack_1(a77);
}
if (i3 == 3)
{
// RTTY Round-Up
return unpack_3(a77);
}
if (i3 == 4)
{
// call that doesn't fit in 28 bits
return unpack_4(a77);
}
char tmp[64];
sprintf(tmp, "UNK i3=%d n3=%d", i3, n3);
return tmp;
}
} // namespace FT8

30
ft8/unpack.h Normal file
View File

@ -0,0 +1,30 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef unpack_h
#define unpack_h
namespace FT8 {
std::string unpack(int a91[]);
} // namespace FT8
#endif

408
ft8/util.cpp Normal file
View File

@ -0,0 +1,408 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <sndfile.h>
#include <sys/time.h>
#include <assert.h>
#include <math.h>
#include <string.h>
#include <complex>
#include <string>
#include <algorithm>
#include "util.h"
namespace FT8 {
float now()
{
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
void writewav(const std::vector<float> &samples, const char *filename, int rate)
{
float mx = 0;
for (ulong i = 0; i < samples.size(); i++)
{
mx = std::max(mx, std::abs(samples[i]));
}
std::vector<float> v(samples.size());
for (ulong i = 0; i < samples.size(); i++)
{
v[i] = (samples[i] / mx) * 0.95;
}
SF_INFO sf;
sf.channels = 1;
sf.samplerate = rate;
sf.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
SNDFILE *f = sf_open(filename, SFM_WRITE, &sf);
assert(f);
sf_write_float(f, v.data(), v.size());
sf_write_sync(f);
sf_close(f);
}
std::vector<float> readwav(const char *filename, int &rate_out)
{
SF_INFO info;
memset(&info, 0, sizeof(info));
SNDFILE *sf = sf_open(filename, SFM_READ, &info);
if (sf == 0)
{
fprintf(stderr, "cannot open %s\n", filename);
exit(1); // XXX
}
rate_out = info.samplerate;
std::vector<float> out;
while (1)
{
float buf[512];
int n = sf_read_float(sf, buf, 512);
if (n <= 0)
break;
for (int i = 0; i < n; i++)
{
out.push_back(buf[i]);
}
}
sf_close(sf);
return out;
}
void writetxt(std::vector<float> v, const char *filename)
{
FILE *fp = fopen(filename, "w");
if (fp == 0)
{
fprintf(stderr, "could not write %s\n", filename);
exit(1);
}
for (ulong i = 0; i < v.size(); i++)
{
fprintf(fp, "%f\n", v[i]);
}
fclose(fp);
}
//
// Goertzel Algorithm for a Non-integer Frequency Index, Rick Lyons
// https://www.dsprelated.com/showarticle/495.php
//
std::complex<float> goertzel(std::vector<float> v, int rate, int i0, int n, float hz)
{
// float radians_per_sample = (hz * 2 * M_PI) / rate;
// float k = radians_per_sample * n;
float bin_hz = rate / (float)n;
float k = hz / bin_hz;
float alpha = 2 * M_PI * k / n;
float beta = 2 * M_PI * k * (n - 1.0) / n;
float two_cos_alpha = 2 * cos(alpha);
float a = cos(beta);
float b = -sin(beta);
float c = sin(alpha) * sin(beta) - cos(alpha) * cos(beta);
float d = sin(2 * M_PI * k);
float w1 = 0;
float w2 = 0;
for (int i = 0; i < n; i++)
{
float w0 = v[i0 + i] + two_cos_alpha * w1 - w2;
w2 = w1;
w1 = w0;
}
float re = w1 * a + w2 * c;
float im = w1 * b + w2 * d;
return std::complex<float>(re, im);
}
float vmax(const std::vector<float> &v)
{
float mx = 0;
int got = 0;
for (int i = 0; i < (int)v.size(); i++)
{
if (got == 0 || v[i] > mx)
{
got = 1;
mx = v[i];
}
}
return mx;
}
std::vector<float> vreal(const std::vector<std::complex<float>> &a)
{
std::vector<float> b(a.size());
for (int i = 0; i < (int)a.size(); i++)
{
b[i] = a[i].real();
}
return b;
}
std::vector<float> vimag(const std::vector<std::complex<float>> &a)
{
std::vector<float> b(a.size());
for (int i = 0; i < (int)a.size(); i++)
{
b[i] = a[i].imag();
}
return b;
}
// generate 8-FSK, at 25 hz, bin size 6.25 hz,
// 200 samples/second, 32 samples/symbol.
// used as reference to detect pairs of symbols.
// superseded by gfsk().
std::vector<std::complex<float>> fsk_c(const std::vector<int> &syms)
{
int n = syms.size();
std::vector<std::complex<float>> v(n * 32);
float theta = 0;
for (int si = 0; si < n; si++)
{
float hz = 25 + syms[si] * 6.25;
for (int i = 0; i < 32; i++)
{
v[si * 32 + i] = std::complex<float>(cos(theta), sin(theta));
theta += 2 * M_PI / (200 / hz);
}
}
return v;
}
// copied from wsjt-x ft2/gfsk_pulse.f90.
// b is 1.0 for FT4; 2.0 for FT8.
float gfsk_point(float b, float t)
{
float c = M_PI * sqrt(2.0 / log(2.0));
float x = 0.5 * (erf(c * b * (t + 0.5)) - erf(c * b * (t - 0.5)));
return x;
}
// the smoothing window for gfsk.
// run the window over impulses of symbol frequencies,
// each impulse at the center of its symbol time.
// three symbols wide.
// most of the pulse is in the center symbol.
// b is 1.0 for FT4; 2.0 for FT8.
std::vector<float> gfsk_window(int samples_per_symbol, float b)
{
std::vector<float> v(3 * samples_per_symbol);
float sum = 0;
for (int i = 0; i < (int)v.size(); i++)
{
float x = i / (float)samples_per_symbol;
x -= 1.5;
float y = gfsk_point(b, x);
v[i] = y;
sum += y;
}
for (int i = 0; i < (int)v.size(); i++)
{
v[i] /= sum;
}
return v;
}
// gaussian-smoothed fsk.
// the gaussian smooths the instantaneous frequencies,
// so that the transitions between symbols don't
// cause clicks.
// gwin is gfsk_window(32, 2.0)
std::vector<std::complex<float>> gfsk_c(
const std::vector<int> &symbols,
float hz0, float hz1,
float spacing, int rate, int symsamples,
float phase0,
const std::vector<float> &gwin
)
{
assert((gwin.size() % 2) == 0);
// compute frequency for each symbol.
// generate a spike in the middle of each symbol time;
// the gaussian filter will turn it into a waveform.
std::vector<float> hzv(symsamples * (symbols.size() + 2), 0.0);
for (int bi = 0; bi < (int)symbols.size(); bi++)
{
float base_hz = hz0 + (hz1 - hz0) * (bi / (float)symbols.size());
float fr = base_hz + (symbols[bi] * spacing);
int mid = symsamples * (bi + 1) + symsamples / 2;
// the window has even size, so split the impulse over
// the two middle samples to be symmetric.
hzv[mid] = fr * symsamples / 2.0;
hzv[mid - 1] = fr * symsamples / 2.0;
}
// repeat first and last symbols
for (int i = 0; i < symsamples; i++)
{
hzv[i] = hzv[i + symsamples];
hzv[symsamples * (symbols.size() + 1) + i] = hzv[symsamples * symbols.size() + i];
}
// run the per-sample frequency vector through
// the gaussian filter.
int half = gwin.size() / 2;
std::vector<float> o(hzv.size());
for (int i = 0; i < (int)o.size(); i++)
{
float sum = 0;
for (int j = 0; j < (int)gwin.size(); j++)
{
int k = i - half + j;
if (k >= 0 && k < (int)hzv.size())
{
sum += hzv[k] * gwin[j];
}
}
o[i] = sum;
}
// drop repeated first and last symbols
std::vector<float> oo(symsamples * symbols.size());
for (int i = 0; i < (int)oo.size(); i++)
{
oo[i] = o[i + symsamples];
}
// now oo[i] contains the frequency for the i'th sample.
std::vector<std::complex<float>> v(symsamples * symbols.size());
float theta = phase0;
for (int i = 0; i < (int)v.size(); i++)
{
v[i] = std::complex<float>(cos(theta), sin(theta));
float hz = oo[i];
theta += 2 * M_PI / (rate / hz);
}
return v;
}
// gaussian-smoothed fsk.
// the gaussian smooths the instantaneous frequencies,
// so that the transitions between symbols don't
// cause clicks.
// gwin is gfsk_window(32, 2.0)
std::vector<float> gfsk_r(
const std::vector<int> &symbols,
float hz0, float hz1,
float spacing, int rate, int symsamples,
float phase0,
const std::vector<float> &gwin
)
{
assert((gwin.size() % 2) == 0);
// compute frequency for each symbol.
// generate a spike in the middle of each symbol time;
// the gaussian filter will turn it into a waveform.
std::vector<float> hzv(symsamples * (symbols.size() + 2), 0.0);
for (int bi = 0; bi < (int)symbols.size(); bi++)
{
float base_hz = hz0 + (hz1 - hz0) * (bi / (float)symbols.size());
float fr = base_hz + (symbols[bi] * spacing);
int mid = symsamples * (bi + 1) + symsamples / 2;
// the window has even size, so split the impulse over
// the two middle samples to be symmetric.
hzv[mid] = fr * symsamples / 2.0;
hzv[mid - 1] = fr * symsamples / 2.0;
}
// repeat first and last symbols
for (int i = 0; i < symsamples; i++)
{
hzv[i] = hzv[i + symsamples];
hzv[symsamples * (symbols.size() + 1) + i] = hzv[symsamples * symbols.size() + i];
}
// run the per-sample frequency vector through
// the gaussian filter.
int half = gwin.size() / 2;
std::vector<float> o(hzv.size());
for (int i = 0; i < (int)o.size(); i++)
{
float sum = 0;
for (int j = 0; j < (int)gwin.size(); j++)
{
int k = i - half + j;
if (k >= 0 && k < (int)hzv.size())
{
sum += hzv[k] * gwin[j];
}
}
o[i] = sum;
}
// drop repeated first and last symbols
std::vector<float> oo(symsamples * symbols.size());
for (int i = 0; i < (int)oo.size(); i++)
{
oo[i] = o[i + symsamples];
}
// now oo[i] contains the frequency for the i'th sample.
std::vector<float> v(symsamples * symbols.size());
float theta = phase0;
for (int i = 0; i < (int)v.size(); i++)
{
v[i] = cos(theta);
float hz = oo[i];
theta += 2 * M_PI / (rate / hz);
}
return v;
}
const std::string WHITESPACE = " \n\r\t\f\v";
std::string ltrim(const std::string &s)
{
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
std::string rtrim(const std::string &s)
{
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
std::string trim(const std::string &s) {
return rtrim(ltrim(s));
}
} // namespace FT8

58
ft8/util.h Normal file
View File

@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
// written by Robert Morris, AB1HL //
// reformatted and adapted to Qt and SDRangel context //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef UTIL_H
#define UTIL_H
#include <vector>
#include <complex>
namespace FT8
{
float now();
void writewav(const std::vector<float> &samples, const char *filename, int rate);
std::vector<float> readwav(const char *filename, int &rate_out);
void writetxt(std::vector<float> v, const char *filename);
std::complex<float> goertzel(std::vector<float> v, int rate, int i0, int n, float hz);
float vmax(const std::vector<float> &v);
std::vector<float> vreal(const std::vector<std::complex<float>> &a);
std::vector<float> vimag(const std::vector<std::complex<float>> &a);
std::vector<std::complex<float>> gfsk_c(
const std::vector<int> &symbols,
float hz0, float hz1,
float spacing, int rate, int symsamples,
float phase0,
const std::vector<float> &gwin
);
std::vector<float> gfsk_r(
const std::vector<int> &symbols,
float hz0, float hz1,
float spacing, int rate, int symsamples,
float phase0,
const std::vector<float> &gwin
);
std::vector<float> gfsk_window(int samples_per_symbol, float b);
std::string trim(const std::string &s);
typedef unsigned long ulong;
typedef unsigned int uint;
} // namespace FT8
#endif

View File

@ -4,6 +4,7 @@ set(sdrbench_SOURCES
mainbench.cpp
parserbench.cpp
test_golay2312.cpp
test_ft8.cpp
)
set(sdrbench_HEADERS
@ -16,16 +17,20 @@ add_library(sdrbench SHARED
)
include_directories(
${FFTW3F_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/exports
${CMAKE_SOURCE_DIR}/sdrbase
${CMAKE_SOURCE_DIR}/logging
${CMAKE_SOURCE_DIR}
)
target_link_libraries(sdrbench
${FFTW3F_LIBRARIES}
Qt::Core
Qt::Gui
sdrbase
logging
ft8
)
install(TARGETS sdrbench DESTINATION ${INSTALL_LIB_DIR})

View File

@ -62,6 +62,8 @@ void MainBench::run()
testDecimateFF();
} else if (m_parser.getTestType() == ParserBench::TestGolay2312) {
testGolay2312();
} else if (m_parser.getTestType() == ParserBench::TestFT8) {
testFT8();
} else {
qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType();
}

View File

@ -54,6 +54,7 @@ private:
void testDecimateFI();
void testDecimateFF();
void testGolay2312();
void testFT8();
void decimateII(const qint16 *buf, int len);
void decimateInfII(const qint16 *buf, int len);
void decimateSupII(const qint16 *buf, int len);
@ -62,6 +63,7 @@ private:
void decimateFF(const float *buf, int len);
void printResults(const QString& prefix, qint64 nsecs);
static MainBench *m_instance;
qtwebapp::LoggerWithFile *m_logger;
const ParserBench& m_parser;

View File

@ -24,7 +24,7 @@
ParserBench::ParserBench() :
m_testOption(QStringList() << "t" << "test",
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312",
"Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312, ft8"
"test",
"decimateii"),
m_nbSamplesOption(QStringList() << "n" << "nb-samples",
@ -127,6 +127,8 @@ ParserBench::TestType ParserBench::getTestType() const
return TestDecimatorsSupII;
} else if (m_testStr == "golay2312") {
return TestGolay2312;
} else if (m_testStr == "ft8") {
return TestFT8;
} else {
return TestDecimatorsII;
}

View File

@ -35,7 +35,8 @@ public:
TestDecimatorsFF,
TestDecimatorsInfII,
TestDecimatorsSupII,
TestGolay2312
TestGolay2312,
TestFT8
} TestType;
ParserBench();

111
sdrbench/test_ft8.cpp Normal file
View File

@ -0,0 +1,111 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "mainbench.h"
#ifdef LINUX
#include "ft8/ft8.h"
#include "ft8/util.h"
#include "ft8/unpack.h"
#include <QMutex>
#endif
#ifndef LINUX
void MainBench::testFT8()
{
qDebug("Implemented in Linux only");
}
#else
QMutex cycle_mu;
volatile int cycle_count;
time_t saved_cycle_start;
std::map<std::string, bool> cycle_already;
int hcb(
int *a91,
float hz0,
float hz1,
float off,
const char *comment,
float snr,
int pass,
int correct_bits)
{
(void) hz1;
(void) comment;
(void) pass;
std::string msg = FT8::unpack(a91);
cycle_mu.lock();
if (cycle_already.count(msg) > 0)
{
// already decoded this message on this cycle
cycle_mu.unlock();
return 1; // 1 => already seen, don't subtract.
}
cycle_already[msg] = true;
cycle_count += 1;
cycle_mu.unlock();
struct tm result;
gmtime_r(&saved_cycle_start, &result);
printf("%02d%02d%02d %3d %3d %5.2f %6.1f %s\n",
result.tm_hour,
result.tm_min,
result.tm_sec,
(int)snr,
correct_bits,
off - 0.5,
hz0,
msg.c_str());
fflush(stdout);
return 2; // 2 => new decode, do subtract.
}
void MainBench::testFT8()
{
qDebug("MainBench::testFT8: start");
int hints[2] = { 2, 0 }; // CQ
double budget = 5; // compute for this many seconds per cycle
int rate;
std::vector<float> s = FT8::readwav("/home/f4exb/.local/share/WSJT-X/save/230105_091630.wav", rate); // FIXME: download file
FT8::entry(
s.data(),
s.size(),
0.5 * rate,
rate,
150,
3600, // 2900,
hints,
hints,
budget,
budget,
hcb,
0,
(struct FT8::cdecode *) 0
);
qDebug("MainBench::testFT8: end");
}
#endif