From a344cfddfbd1b256207d426daf45c1bd4e3f35be Mon Sep 17 00:00:00 2001 From: David Protzman Date: Thu, 22 Sep 2022 21:26:22 -0400 Subject: [PATCH] Added demodulation, lte_decode, and decode modules --- gnuradio/gr-droneid/grc/CMakeLists.txt | 3 +- .../grc/droneid_demodulation.block.yml | 42 ++ .../include/gnuradio/droneid/CMakeLists.txt | 4 +- .../include/gnuradio/droneid/demodulation.h | 41 ++ .../include/gnuradio/droneid/lte_decode.h | 62 +++ gnuradio/gr-droneid/lib/CMakeLists.txt | 3 + gnuradio/gr-droneid/lib/demodulation_impl.cc | 479 ++++++++++++++++++ gnuradio/gr-droneid/lib/demodulation_impl.h | 59 +++ gnuradio/gr-droneid/lib/lte_decode.cc | 81 +++ gnuradio/gr-droneid/lib/qa_demodulation.cc | 21 + .../python/droneid/bindings/CMakeLists.txt | 4 +- .../droneid/bindings/demodulation_python.cc | 49 ++ .../docstrings/demodulation_pydoc_template.h | 24 + .../docstrings/lte_decode_pydoc_template.h | 27 + .../droneid/bindings/lte_decode_python.cc | 47 ++ .../droneid/bindings/python_bindings.cc | 4 + 16 files changed, 947 insertions(+), 3 deletions(-) create mode 100644 gnuradio/gr-droneid/grc/droneid_demodulation.block.yml create mode 100644 gnuradio/gr-droneid/include/gnuradio/droneid/demodulation.h create mode 100644 gnuradio/gr-droneid/include/gnuradio/droneid/lte_decode.h create mode 100644 gnuradio/gr-droneid/lib/demodulation_impl.cc create mode 100644 gnuradio/gr-droneid/lib/demodulation_impl.h create mode 100644 gnuradio/gr-droneid/lib/lte_decode.cc create mode 100644 gnuradio/gr-droneid/lib/qa_demodulation.cc create mode 100644 gnuradio/gr-droneid/python/droneid/bindings/demodulation_python.cc create mode 100644 gnuradio/gr-droneid/python/droneid/bindings/docstrings/demodulation_pydoc_template.h create mode 100644 gnuradio/gr-droneid/python/droneid/bindings/docstrings/lte_decode_pydoc_template.h create mode 100644 gnuradio/gr-droneid/python/droneid/bindings/lte_decode_python.cc diff --git a/gnuradio/gr-droneid/grc/CMakeLists.txt b/gnuradio/gr-droneid/grc/CMakeLists.txt index 37d34cb..8393945 100644 --- a/gnuradio/gr-droneid/grc/CMakeLists.txt +++ b/gnuradio/gr-droneid/grc/CMakeLists.txt @@ -7,5 +7,6 @@ # install(FILES - droneid_decode.block.yml DESTINATION share/gnuradio/grc/blocks + droneid_decode.block.yml + droneid_demodulation.block.yml DESTINATION share/gnuradio/grc/blocks ) diff --git a/gnuradio/gr-droneid/grc/droneid_demodulation.block.yml b/gnuradio/gr-droneid/grc/droneid_demodulation.block.yml new file mode 100644 index 0000000..7fe97f4 --- /dev/null +++ b/gnuradio/gr-droneid/grc/droneid_demodulation.block.yml @@ -0,0 +1,42 @@ +id: droneid_demodulation +label: demodulation +category: '[droneid]' + +templates: + imports: from gnuradio import droneid + make: droneid.demodulation(${sample_rate}, ${debug_path}) + +# Make one 'parameters' list entry for every parameter you want settable from the GUI. +# Keys include: +# * id (makes the value accessible as keyname, e.g. in the make entry) +# * label (label shown in the GUI) +# * dtype (e.g. int, float, complex, byte, short, xxx_vector, ...) +# * default +parameters: + - id: sample_rate + label: Sample Rate + dtype: float + - id: debug_path + label: Debug path + dtype: string + +# Make one 'inputs' list entry per input and one 'outputs' list entry per output. +# Keys include: +# * label (an identifier for the GUI) +# * domain (optional - stream or message. Default is stream) +# * dtype (e.g. int, float, complex, byte, short, xxx_vector, ...) +# * vlen (optional - data stream vector length. Default is 1) +# * optional (optional - set to 1 for optional inputs. Default is 0) +inputs: + - label: pdus + domain: message + optional: 0 + +outputs: + - label: pdus + domain: message + optional: 1 + +# 'file_format' specifies the version of the GRC yml format used in the file +# and should usually not be changed. +file_format: 1 diff --git a/gnuradio/gr-droneid/include/gnuradio/droneid/CMakeLists.txt b/gnuradio/gr-droneid/include/gnuradio/droneid/CMakeLists.txt index 93b63b5..720e3df 100644 --- a/gnuradio/gr-droneid/include/gnuradio/droneid/CMakeLists.txt +++ b/gnuradio/gr-droneid/include/gnuradio/droneid/CMakeLists.txt @@ -12,5 +12,7 @@ install(FILES api.h misc_utils.h - decode.h DESTINATION include/gnuradio/droneid + decode.h + demodulation.h + lte_decode.h DESTINATION include/gnuradio/droneid ) diff --git a/gnuradio/gr-droneid/include/gnuradio/droneid/demodulation.h b/gnuradio/gr-droneid/include/gnuradio/droneid/demodulation.h new file mode 100644 index 0000000..78224f9 --- /dev/null +++ b/gnuradio/gr-droneid/include/gnuradio/droneid/demodulation.h @@ -0,0 +1,41 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_DRONEID_DEMODULATION_H +#define INCLUDED_DRONEID_DEMODULATION_H + +#include +#include + +namespace gr { +namespace droneid { + +/*! + * \brief <+description of block+> + * \ingroup droneid + * + */ +class DRONEID_API demodulation : virtual public gr::sync_block +{ +public: + typedef std::shared_ptr sptr; + + /*! + * \brief Return a shared_ptr to a new instance of droneid::demodulation. + * + * To avoid accidental use of raw pointers, droneid::demodulation's + * constructor is in a private implementation + * class. droneid::demodulation::make is the public interface for + * creating new instances. + */ + static sptr make(double sample_rate, const std::string & debug_path); +}; + +} // namespace droneid +} // namespace gr + +#endif /* INCLUDED_DRONEID_DEMODULATION_H */ diff --git a/gnuradio/gr-droneid/include/gnuradio/droneid/lte_decode.h b/gnuradio/gr-droneid/include/gnuradio/droneid/lte_decode.h new file mode 100644 index 0000000..babac72 --- /dev/null +++ b/gnuradio/gr-droneid/include/gnuradio/droneid/lte_decode.h @@ -0,0 +1,62 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_DRONEID_LTE_DECODE_H +#define INCLUDED_DRONEID_LTE_DECODE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef __cplusplus +} +#endif + +// This line is required before the include of CRC.h so that it actually loads the CRC_24_LTEA definition +#define CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS +#include + +namespace gr { +namespace droneid { + +/*! + * \brief <+description+> + * + */ +class DRONEID_API lte_decode +{ +protected: + static constexpr uint32_t TURBO_ITERATIONS = 4; + static constexpr uint32_t TURBO_DECODER_BIT_COUNT = 1412; + static constexpr uint32_t TURBO_DECODER_INPUT_BIT_COUNT = 7200; + static constexpr uint32_t EXPECTED_PAYLOAD_BYTES = 176; + static constexpr uint32_t EXPECTED_PAYLOAD_BITS = EXPECTED_PAYLOAD_BYTES * 8; + + std::vector d1_, d2_, d3_; + std::vector turbo_decoder_input_; + std::vector decoded_bytes_; + + struct lte_rate_matcher * rate_matcher_ = nullptr; + struct tdecoder * turbo_decoder_ = nullptr; + struct lte_rate_matcher_io rate_matcher_io_; +public: + lte_decode(); + ~lte_decode(); + + std::vector decode(const std::vector & bits); +}; + +} // namespace droneid +} // namespace gr + +#endif /* INCLUDED_DRONEID_LTE_DECODE_H */ diff --git a/gnuradio/gr-droneid/lib/CMakeLists.txt b/gnuradio/gr-droneid/lib/CMakeLists.txt index 093a256..e4a13cd 100644 --- a/gnuradio/gr-droneid/lib/CMakeLists.txt +++ b/gnuradio/gr-droneid/lib/CMakeLists.txt @@ -14,6 +14,8 @@ include(GrPlatform) #define LIB_SUFFIX list(APPEND droneid_sources misc_utils.cc decode_impl.cc + demodulation_impl.cc + lte_decode.cc ) set(droneid_sources "${droneid_sources}" PARENT_SCOPE) @@ -58,6 +60,7 @@ include(GrTest) #include_directories() # List all files that contain Boost.UTF unit tests here list(APPEND test_droneid_sources +qa_demodulation.cc qa_decode.cc ) # Anything we need to link to for the unit tests go here diff --git a/gnuradio/gr-droneid/lib/demodulation_impl.cc b/gnuradio/gr-droneid/lib/demodulation_impl.cc new file mode 100644 index 0000000..1fa5691 --- /dev/null +++ b/gnuradio/gr-droneid/lib/demodulation_impl.cc @@ -0,0 +1,479 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "demodulation_impl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gr { +namespace droneid { + +using path = boost::filesystem::path; +using utils = misc_utils; + +demodulation::sptr +demodulation::make(double sample_rate, const std::string & debug_path) { + return gnuradio::get_initial_sptr + (new demodulation_impl(sample_rate, debug_path)); +} + + +/* + * The private constructor + */ +demodulation_impl::demodulation_impl(const double sample_rate, const std::string & debug_path) + : gr::sync_block("demodulation", + gr::io_signature::make(0, 0, 0), + gr::io_signature::make(0, 0, 0)), + sample_rate_(sample_rate), fft_size_(misc_utils::get_fft_size(sample_rate)), long_cp_len_( + misc_utils::get_long_cp_len(sample_rate)), short_cp_len_(misc_utils::get_short_cp_len(sample_rate)), + debug_path_(debug_path){ + if (! debug_path_.empty()) { + const auto p = path(debug_path_); + if (! boost::filesystem::is_directory(p)) { + boost::filesystem::create_directories(p); + } + } + + message_port_register_in(pmt::mp("pdus")); + message_port_register_out(pmt::mp("pdus")); + set_msg_handler(pmt::mp("pdus"), [this](pmt::pmt_t pdu){handle_msg(pdu);}); + cfo_cp_len_ = short_cp_len_; + burst_counter_ = 0; + cfo_buffer_.resize(cfo_cp_len_ * 2); + // sample_buffer_.resize((fft_size_ * 9) + (long_cp_len_ * 2) + (short_cp_len_ * 7)); + + cp_lengths_ = { + long_cp_len_, + short_cp_len_, + short_cp_len_, + short_cp_len_, + short_cp_len_, + short_cp_len_, + short_cp_len_, + short_cp_len_, + long_cp_len_ + }; + + symbols_.resize(cp_lengths_.size()); + for (auto & vec : symbols_) { + vec.resize(fft_size_); + } + + std::cout << "FFT SIZE: " << fft_size_ << ", sample rate: " << sample_rate_ << "\n"; + + zc_ = misc_utils::create_zc_sequence(sample_rate_, 600); + fft_ = std::make_unique(static_cast(fft_size_), 1); + fft_shift_ = std::make_unique>>(fft_size_); + + std::copy(zc_.begin(), zc_.end(), fft_->get_inbuf()); + fft_->execute(); + std::copy(fft_->get_outbuf(), fft_->get_outbuf() + fft_size_, zc_.begin()); + std::for_each(zc_.begin(), zc_.end(), [this](std::complex & sample){ sample /= static_cast(fft_size_);}); + fft_shift_->shift(zc_); + channel_.resize(fft_size_); + + if (! debug_path_.empty()) { + misc_utils::write_samples_vec((path(debug_path_) / "zc").string(), zc_); + } +} + +/* + * Our virtual destructor. + */ +demodulation_impl::~demodulation_impl() { +} + +void demodulation_impl::handle_msg(pmt::pmt_t pdu) { + burst_counter_++; + + const auto meta = pmt::car(pdu); + const auto vec = pmt::cdr(pdu); + const std::complex I = {0, 1}; + + std::vector> samples(pmt::c32vector_elements(vec)); + + if (! debug_path_.empty()) { + misc_utils::write_samples_vec((path(debug_path_) / ("burst_" + std::to_string(burst_counter_))).string(), samples); + } + + if (sample_buffer_.size() < samples.size()) { + sample_buffer_.resize(samples.size()); + } + + + const auto start_idx_pmt = pmt::dict_ref(meta, pmt::mp("start_idx"), pmt::from_uint64(0)); + const auto start_idx = pmt::to_uint64(start_idx_pmt); + ///////////////////////////////////////////////////////////////////////////////////// + /// Find the ZC sequence + ///////////////////////////////////////////////////////////////////////////////////// + const auto zc_start_idx = utils::find_zc_seq_start_idx(samples, sample_rate_); + const auto est_start_index = zc_start_idx - (fft_size_ * 3) - long_cp_len_ - (short_cp_len_ * 3); + std::cout << "ZC start index: " << zc_start_idx << ", est start index: " << est_start_index << "\n"; + + const auto total_sample_count = est_start_index + (fft_size_ * 9) + (long_cp_len_ * 2) + (short_cp_len_ * 7); + if (est_start_index > samples.size() || total_sample_count > samples.size()) { + std::cout << "Did not receive enough samples! Skipping burst\n"; + return; + } + + ///////////////////////////////////////////////////////////////////////////////////// + /// Coarse CFO estimate + ///////////////////////////////////////////////////////////////////////////////////// + const auto cfo_backoff = 16; + const auto cfo_size = short_cp_len_ - (cfo_backoff * 2); + const std::vector> zc_seq_full_symbol( + &samples[zc_start_idx - short_cp_len_], &samples[zc_start_idx + fft_size_]); + const std::vector> cyclic_prefix( + zc_seq_full_symbol.begin() + cfo_backoff, zc_seq_full_symbol.begin() + cfo_backoff + cfo_size); + const std::vector> end_of_symbol( + zc_seq_full_symbol.end() - cfo_backoff - cfo_size, zc_seq_full_symbol.end() - cfo_backoff); + + if (! debug_path_.empty()) { + misc_utils::write_samples_vec((path(debug_path_) / ("received_zc_" + std::to_string(burst_counter_))).string(), zc_seq_full_symbol); + misc_utils::write_samples_vec((path(debug_path_) / ("expected_zc_" + std::to_string(burst_counter_))).string(), zc_); + misc_utils::write_samples_vec((path(debug_path_) / ("cfo_estimate_cyclic_prefix_" + std::to_string(burst_counter_))).string(), cyclic_prefix); + misc_utils::write_samples_vec((path(debug_path_) / ("cfo_estimate_end_of_symbol_" + std::to_string(burst_counter_))).string(), end_of_symbol); + } + + std::complex cfo_dot_prod {0, 0}; + for (uint32_t idx = 0; idx < cyclic_prefix.size(); idx++) { + cfo_dot_prod += cyclic_prefix[idx] * std::conj(end_of_symbol[idx]); + } + const auto cfo_angle = std::arg(cfo_dot_prod) / static_cast(fft_size_); + const auto cfo_phase_angle = std::exp(I * -cfo_angle); + + ///////////////////////////////////////////////////////////////////////////////////// + /// Coarse CFO correction + ///////////////////////////////////////////////////////////////////////////////////// + std::complex starting_cfo_phase {1, 0}; + volk_32fc_s32fc_x2_rotator_32fc(&samples[0], &samples[0], cfo_phase_angle, &starting_cfo_phase, samples.size()); + + if (! debug_path_.empty()) { + misc_utils::write_samples_vec((path(debug_path_) / ("burst_post_coarse_cfo_correction_" + std::to_string(burst_counter_))).string(), samples); + } + + ///////////////////////////////////////////////////////////////////////////////////// + /// Channel estimation + ///////////////////////////////////////////////////////////////////////////////////// + const auto symbols = utils::extract_ofdm_symbol_samples(samples, sample_rate_, est_start_index); + + const auto zc_symbol_4_freq_domain = symbols[3].second; + const auto zc_symbol_6_freq_domain = symbols[5].second; + + const auto zc_4_data_only = utils::extract_data_carriers(zc_symbol_4_freq_domain, fft_size_); + const auto zc_6_data_only = utils::extract_data_carriers(zc_symbol_6_freq_domain, fft_size_); + + // utils::write_samples("/tmp/symbol_4", zc_4_data_only); + // utils::write_samples("/tmp/symbol_6", zc_6_data_only); + + const auto zc_4_channel = utils::calculate_channel(zc_4_data_only, sample_rate_, 4); + const auto zc_6_channel = utils::calculate_channel(zc_6_data_only, sample_rate_, 6); + + // utils::write_samples("/tmp/channel_4", zc_4_channel); + // utils::write_samples("/tmp/channel_6", zc_6_channel); + + const auto zc_4_channel_angles = utils::angle(zc_4_channel); + const auto zc_6_channel_angles = utils::angle(zc_6_channel); + + const auto zc_4_channel_phase = std::accumulate(zc_4_channel_angles.begin(), zc_4_channel_angles.end(), 0.0f) / 600; + const auto zc_6_channel_phase = std::accumulate(zc_6_channel_angles.begin(), zc_6_channel_angles.end(), 0.0f) / 600; + + // std::cout << "4 phase: " << zc_4_channel_phase << "\n"; + // std::cout << "6 phase: " << zc_6_channel_phase << "\n"; + + const auto channel_phase_adj = (zc_4_channel_phase - zc_6_channel_phase) / 2; + + ///////////////////////////////////////////////////////////////////////////////////// + /// Save Constellation + ///////////////////////////////////////////////////////////////////////////////////// + std::vector> all_data_carriers; + for (int idx = 0; idx < 9; idx++) { + if (idx == 1 || idx == 3 || idx == 5) { + continue; + } + auto data_carriers = utils::extract_data_carriers(symbols[idx].second, fft_size_); + + for(uint32_t i = 0; i < 600; i++) { + data_carriers[i] *= zc_4_channel[i]; + // data_carriers[i] *= std::exp(I * (-channel_phase_adj * static_cast(idx - 6))); + } + + all_data_carriers.insert(all_data_carriers.end(), data_carriers.begin(), data_carriers.end()); + } + + message_port_pub(pmt::mp("pdus"), pmt::cons(pmt::make_dict(), pmt::init_c32vector(all_data_carriers.size(), all_data_carriers))); + + // utils::write_samples("/tmp/all_data", all_data_carriers); + + ///////////////////////////////////////////////////////////////////////////////////// + /// QPSK to bits + ///////////////////////////////////////////////////////////////////////////////////// + auto bits = utils::qpsk_to_bits(all_data_carriers); + + for (int idx = 0; idx < 7200; idx++) { + if (bits[idx] == 1 && XOR_BIT_VEC[idx] == 1) { + bits[idx] = 0; + } else if (bits[idx] == 0 && XOR_BIT_VEC[idx] == 0) { + bits[idx] = 0; + } else { + bits[idx] = 1; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Setup and run the Turbo decoder + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + const size_t input_file_bit_count = 7200; + + const int turbo_iterations = 4; + const int turbo_decoder_bit_count = 1412; // Number of bits that the Turbo decoder will take in + const int expected_payload_bytes = 176; // Number of bytes that the Turbo decoder will output + const int expected_payload_bits = expected_payload_bytes * 8; + + // Allocate buffers for the Turbo decoder + std::vector d1(turbo_decoder_bit_count); + std::vector d2(turbo_decoder_bit_count); + std::vector d3(turbo_decoder_bit_count); + std::vector decoded_bytes(expected_payload_bytes); + + // Create the required structures to run the Turbo decoder + struct lte_rate_matcher * rate_matcher = lte_rate_matcher_alloc(); + struct tdecoder * turbo_decoder = alloc_tdec(); + struct lte_rate_matcher_io rate_matcher_io = { + .D = turbo_decoder_bit_count, + .E = input_file_bit_count, + .d = {&d1[0], &d2[0], &d3[0]}, + .e = &bits[0] + }; + + // Setup the rate matching logic + lte_rate_match_rv(rate_matcher, &rate_matcher_io, 0); + + // Run the turbo decoder (will do rate matching as well) + const int decode_status = lte_turbo_decode(turbo_decoder, expected_payload_bits, turbo_iterations, + &decoded_bytes[0], &d1[0], &d2[0], &d3[0]); + + if (decode_status != 0) { + std::cerr << "Failed to decode\n"; + } else { + for (const auto & b : decoded_bytes) { + fprintf(stdout, "%02x", b); + } + fprintf(stdout, "\n"); + + const auto crc_out = CRC::Calculate(&decoded_bytes[0], decoded_bytes.size(), CRC::CRC_24_LTEA()); + if (crc_out != 0) { + std::cerr << "CRC Check Failed!\n"; + } + } + + free_tdec(turbo_decoder); + lte_rate_matcher_free(rate_matcher); +} + +int +demodulation_impl::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) { + // Do <+signal processing+> + + // Tell runtime system how many output items we produced. + return noutput_items; +} + +const uint8_t demodulation_impl::XOR_BIT_VEC[7200] = { + 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1 + , 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0 + , 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0 + , 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0 + , 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0 + , 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0 + , 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 + , 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0 + , 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0 + , 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1 + , 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1 + , 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 + , 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1 + , 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + , 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1 + , 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1 + , 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0 + , 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0 + , 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0 + , 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 + , 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0 + , 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1 + , 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0 + , 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0 + , 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0 + , 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1 + , 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0 + , 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0 + , 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1 + , 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1 + , 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 + , 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1 + , 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1 + , 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0 + , 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1 + , 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0 + , 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0 + , 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0 + , 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1 + , 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0 + , 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0 + , 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0 + , 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0 + , 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 + , 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 + , 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0 + , 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1 + , 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1 + , 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0 + , 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1 + , 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0 + , 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0 + , 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0 + , 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1 + , 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 + , 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1 + , 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1 + , 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1 + , 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0 + , 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 + , 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1 + , 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0 + , 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0 + , 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0 + , 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0 + , 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0 + , 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1 + , 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1 + , 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0 + , 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1 + , 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0 + , 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1 + , 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1 + , 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1 + , 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 + , 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 + , 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1 + , 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0 + , 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 + , 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1 + , 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1 + , 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0 + , 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0 + , 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0 + , 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 + , 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1 + , 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1 + , 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 + , 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1 + , 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1 + , 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1 + , 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0 + , 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1 + , 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0 + , 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 + , 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1 + , 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1 + , 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1 + , 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 + , 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 + , 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0 + , 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0 + , 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1 + , 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1 + , 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0 + , 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0 + , 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0 + , 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0 + , 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1 + , 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1 + , 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0 + , 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 + , 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0 + , 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0 + , 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 + , 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1 + , 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1 + , 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1 + , 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0 + , 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1 + , 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1 + , 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 + , 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0 + , 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1 + , 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1 + , 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1 + , 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 + , 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 + , 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0 + , 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1 + , 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1 + , 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 + , 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0 + , 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1 + , 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 + , 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 + , 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0 + , 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 + , 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1 + , 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 + , 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 + , 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + , 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0 + , 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0 + , 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1 + , 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0 + , 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1 + , 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0 + , 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1 + , 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1 + , 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0 + , 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0 + , 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1 + , 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1 + , 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1 + , 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1 + , 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1 + , 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1 + , 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1 + , 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0 + , 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1 + , 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1 + , 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 + , 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1 + , 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1 + , 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0 + , 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0 + , 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 + , 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0 + , 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1 + , 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0 + , 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0 + , 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 + , 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0 + , 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 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, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1 + , 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0 + , 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1 + , 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1 + , 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1 +}; + +} /* namespace droneid */ +} /* namespace gr */ diff --git a/gnuradio/gr-droneid/lib/demodulation_impl.h b/gnuradio/gr-droneid/lib/demodulation_impl.h new file mode 100644 index 0000000..0b51810 --- /dev/null +++ b/gnuradio/gr-droneid/lib/demodulation_impl.h @@ -0,0 +1,59 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef INCLUDED_DRONEID_DEMODULATION_IMPL_H +#define INCLUDED_DRONEID_DEMODULATION_IMPL_H + +#include +#include +#include + +namespace gr { +namespace droneid { + +class demodulation_impl : public demodulation { +private: + const double sample_rate_; + const uint32_t fft_size_; + const uint32_t long_cp_len_; + const uint32_t short_cp_len_; + const std::string debug_path_; + + const static uint8_t XOR_BIT_VEC [7200]; + + std::unique_ptr fft_; + std::unique_ptr>> fft_shift_; + uint32_t burst_counter_; + size_t sample_count_; + uint32_t cfo_cp_len_; + std::vector cp_lengths_; + std::vector> zc_; + std::vector> channel_; + std::vector>> symbols_; + std::vector> cfo_buffer_; + std::vector> sample_buffer_; + + void handle_msg(pmt::pmt_t pdu); + // Nothing to declare in this block. + +public: + demodulation_impl(double sample_rate, const std::string & debug_path); + + ~demodulation_impl(); + + // Where all the action really happens + int work( + int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items + ); +}; + +} // namespace droneid +} // namespace gr + +#endif /* INCLUDED_DRONEID_DEMODULATION_IMPL_H */ diff --git a/gnuradio/gr-droneid/lib/lte_decode.cc b/gnuradio/gr-droneid/lib/lte_decode.cc new file mode 100644 index 0000000..688f931 --- /dev/null +++ b/gnuradio/gr-droneid/lib/lte_decode.cc @@ -0,0 +1,81 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +namespace gr { +namespace droneid { + +lte_decode::lte_decode() { + d1_.resize(TURBO_DECODER_BIT_COUNT); + d2_.resize(TURBO_DECODER_BIT_COUNT); + d3_.resize(TURBO_DECODER_BIT_COUNT); + + turbo_decoder_input_.resize(TURBO_DECODER_INPUT_BIT_COUNT); + decoded_bytes_.resize(EXPECTED_PAYLOAD_BYTES); + + rate_matcher_ = lte_rate_matcher_alloc(); + turbo_decoder_ = alloc_tdec(); + + rate_matcher_io_ = { + .D = TURBO_DECODER_BIT_COUNT, + .E = TURBO_DECODER_INPUT_BIT_COUNT, + .d = {&d1_[0], &d2_[0], &d3_[0]}, + .e = &turbo_decoder_input_[0] + }; +} + +lte_decode::~lte_decode() { + if (rate_matcher_ != nullptr) { + lte_rate_matcher_free(rate_matcher_); + } + + if (turbo_decoder_ != nullptr) { + free_tdec(turbo_decoder_); + } +} + +std::vector lte_decode::decode(const std::vector &bits) { + if (bits.size() != TURBO_DECODER_INPUT_BIT_COUNT) { + std::ostringstream err; + err << "Turbo decoder expected " << TURBO_DECODER_INPUT_BIT_COUNT << " but got " << bits.size(); + throw std::runtime_error(err.str()); + } + + int8_t bit_lut[2] = {-63, 63}; + std::vector bits_copy = bits; + for (int idx = 0; idx < bits.size(); idx++) { + turbo_decoder_input_[idx] = bit_lut[bits[idx]]; + } + + lte_rate_match_fw(rate_matcher_, &rate_matcher_io_, 0); + const int decode_status = lte_turbo_decode(turbo_decoder_, EXPECTED_PAYLOAD_BITS, TURBO_ITERATIONS, + &decoded_bytes_[0], &d1_[0], &d2_[0], &d3_[0]); + + fprintf(stdout, "MOO: "); + for (const auto & i : decoded_bytes_) { + fprintf(stdout, "%02x", i); + } + fprintf(stdout, "\n"); + + if (decode_status != 0) { + throw std::runtime_error("Failed to remove Turbo code. Status: " + std::to_string(decode_status)); + } + + const uint32_t calculated_crc = CRC::Calculate(&decoded_bytes_[0], decoded_bytes_.size(), CRC::CRC_24_LTEA()); + if (calculated_crc != 0) { + return {}; + } + + return decoded_bytes_; +} + + +} /* namespace droneid */ +} /* namespace gr */ diff --git a/gnuradio/gr-droneid/lib/qa_demodulation.cc b/gnuradio/gr-droneid/lib/qa_demodulation.cc new file mode 100644 index 0000000..8fb29e8 --- /dev/null +++ b/gnuradio/gr-droneid/lib/qa_demodulation.cc @@ -0,0 +1,21 @@ +/* -*- c++ -*- */ +/* + * Copyright 2022 gr-droneid author. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +namespace gr { +namespace droneid { + +BOOST_AUTO_TEST_CASE(test_demodulation_replace_with_specific_test_name) +{ + // Put test here +} + +} /* namespace droneid */ +} /* namespace gr */ diff --git a/gnuradio/gr-droneid/python/droneid/bindings/CMakeLists.txt b/gnuradio/gr-droneid/python/droneid/bindings/CMakeLists.txt index ae58c7a..01e657c 100644 --- a/gnuradio/gr-droneid/python/droneid/bindings/CMakeLists.txt +++ b/gnuradio/gr-droneid/python/droneid/bindings/CMakeLists.txt @@ -30,7 +30,9 @@ include(GrPybind) list(APPEND droneid_python_files misc_utils_python.cc - decode_python.cc python_bindings.cc) + decode_python.cc + demodulation_python.cc + lte_decode_python.cc python_bindings.cc) GR_PYBIND_MAKE_OOT(droneid ../../.. diff --git a/gnuradio/gr-droneid/python/droneid/bindings/demodulation_python.cc b/gnuradio/gr-droneid/python/droneid/bindings/demodulation_python.cc new file mode 100644 index 0000000..72cff1d --- /dev/null +++ b/gnuradio/gr-droneid/python/droneid/bindings/demodulation_python.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(demodulation.h) */ +/* BINDTOOL_HEADER_FILE_HASH(848192abf7e376e88031ffe617420115) */ +/***********************************************************************************/ + +#include +#include +#include + +namespace py = pybind11; + +#include +// pydoc.h is automatically generated in the build directory +#include + +void bind_demodulation(py::module& m) +{ + + using demodulation = ::gr::droneid::demodulation; + + + py::class_>(m, "demodulation", D(demodulation)) + + .def(py::init(&demodulation::make), + py::arg("sample_rate"), + py::arg("debug_path"), + D(demodulation, make)) + + + ; +} diff --git a/gnuradio/gr-droneid/python/droneid/bindings/docstrings/demodulation_pydoc_template.h b/gnuradio/gr-droneid/python/droneid/bindings/docstrings/demodulation_pydoc_template.h new file mode 100644 index 0000000..0aecb2f --- /dev/null +++ b/gnuradio/gr-droneid/python/droneid/bindings/docstrings/demodulation_pydoc_template.h @@ -0,0 +1,24 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr, droneid, __VA_ARGS__) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + +static const char* __doc_gr_droneid_demodulation = R"doc()doc"; + + +static const char* __doc_gr_droneid_demodulation_demodulation = R"doc()doc"; + + +static const char* __doc_gr_droneid_demodulation_make = R"doc()doc"; diff --git a/gnuradio/gr-droneid/python/droneid/bindings/docstrings/lte_decode_pydoc_template.h b/gnuradio/gr-droneid/python/droneid/bindings/docstrings/lte_decode_pydoc_template.h new file mode 100644 index 0000000..8f05c1c --- /dev/null +++ b/gnuradio/gr-droneid/python/droneid/bindings/docstrings/lte_decode_pydoc_template.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr, droneid, __VA_ARGS__) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + +static const char* __doc_gr_droneid_lte_decode = R"doc()doc"; + + +static const char* __doc_gr_droneid_lte_decode_lte_decode_0 = R"doc()doc"; + + +static const char* __doc_gr_droneid_lte_decode_lte_decode_1 = R"doc()doc"; + + +static const char* __doc_gr_droneid_lte_decode_decode = R"doc()doc"; diff --git a/gnuradio/gr-droneid/python/droneid/bindings/lte_decode_python.cc b/gnuradio/gr-droneid/python/droneid/bindings/lte_decode_python.cc new file mode 100644 index 0000000..53f35ce --- /dev/null +++ b/gnuradio/gr-droneid/python/droneid/bindings/lte_decode_python.cc @@ -0,0 +1,47 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(lte_decode.h) */ +/* BINDTOOL_HEADER_FILE_HASH(ded435d307a83ab16445df9b219c865d) */ +/***********************************************************************************/ + +#include +#include +#include + +namespace py = pybind11; + +#include +// pydoc.h is automatically generated in the build directory +#include + +void bind_lte_decode(py::module& m) +{ + + using lte_decode = ::gr::droneid::lte_decode; + + + py::class_>(m, "lte_decode", D(lte_decode)) + + .def(py::init<>(), D(lte_decode, lte_decode, 0)) + .def(py::init(), + py::arg("arg0"), + D(lte_decode, lte_decode, 1)) + + + .def("decode", <e_decode::decode, py::arg("bits"), D(lte_decode, decode)) + + ; +} diff --git a/gnuradio/gr-droneid/python/droneid/bindings/python_bindings.cc b/gnuradio/gr-droneid/python/droneid/bindings/python_bindings.cc index bbd1050..37188a6 100644 --- a/gnuradio/gr-droneid/python/droneid/bindings/python_bindings.cc +++ b/gnuradio/gr-droneid/python/droneid/bindings/python_bindings.cc @@ -23,6 +23,8 @@ namespace py = pybind11; // BINDING_FUNCTION_PROTOTYPES( void bind_misc_utils(py::module& m); void bind_decode(py::module& m); + void bind_demodulation(py::module& m); + void bind_lte_decode(py::module& m); // ) END BINDING_FUNCTION_PROTOTYPES @@ -55,5 +57,7 @@ PYBIND11_MODULE(droneid_python, m) // BINDING_FUNCTION_CALLS( bind_misc_utils(m); bind_decode(m); + bind_demodulation(m); + bind_lte_decode(m); // ) END BINDING_FUNCTION_CALLS } \ No newline at end of file