From 867720171e89563b161f86d8e6c75fa2fe48747d Mon Sep 17 00:00:00 2001 From: adomasbaliuka <52975890+adomasbaliuka@users.noreply.github.com> Date: Thu, 2 May 2024 18:07:48 +0200 Subject: [PATCH] Encoding and Codec creation directly from qc-exponents (#19) Breaking changes: - removes RateAdaptiveCode template parameter Bit. This is now a template parameter of underlying functions, which is more flexible (and hopefully not too much more complicated/prone to errors...) - (ABI-breaking) reworks underlying data representation for RateAdaptiveCode. Non-breaking changes: - New header encoder_advanced.hpp. Enables constexpr storage of QC-exponents for a code in a grouped manner. For how to use, see unit test in test_encoder_advanced.cpp. Requires C++20! - Enable construction of RateAdaptiveCode from a vector> mother_pos_varn - Together, these also enable creating RateAdaptiveCode from QC-exponents stored statically in C++. --- CMakeLists.txt | 17 +- .../code_simulation_helpers.hpp | 14 +- .../main_rate_adapted_fer.cpp | 2 +- benchmarks_runtime/main_benchmark_decoder.cpp | 4 +- benchmarks_runtime/main_benchmark_ra.cpp | 4 +- examples/main_demo_error_correction.cpp | 6 +- src/CMakeLists.txt | 3 +- src/autogen_ldpc_QC.hpp | 294 +++++++++++++++ src/encoder.hpp | 32 +- src/encoder_advanced.hpp | 346 ++++++++++++++++++ src/rate_adaptive_code.hpp | 296 +++++++++------ tests/CMakeLists.txt | 28 +- tests/helpers_for_testing.hpp | 18 +- tests/test_encoder_advanced.cpp | 76 ++++ tests/test_rate_adaptive_code.cpp | 91 +++-- tests/test_read_ldpc_from_files.cpp | 2 +- 16 files changed, 1019 insertions(+), 214 deletions(-) create mode 100644 src/autogen_ldpc_QC.hpp create mode 100644 src/encoder_advanced.hpp create mode 100644 tests/test_encoder_advanced.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a6cbec..b8829ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,10 @@ if (LDPC4QKD_HEADER_ONLY) add_library(LDPC4QKD INTERFACE src/rate_adaptive_code.hpp # contains decoder and encoder. Does not need any other files to work. - + # TODO add `encoder_advanced` and require C++20 + # encoder_advanced.hpp # REQUIRES C++20!!! advanced encoder (QC-enabled and constexpr objects). Handles storage. src/encoder.hpp # contains only minimal encoder that needs static storage of the LDPC matrix - src/read_ldpc_file_formats.hpp # helper methods to generate static storage (not needed to use encoder/decoder class). + src/read_ldpc_file_formats.hpp # helper methods (not needed to use `rate_adaptive_code.hpp`). ) target_compile_features(LDPC4QKD INTERFACE cxx_std_17) @@ -95,9 +96,9 @@ else (LDPC4QKD_HEADER_ONLY) include(CompilerWarnings) set_project_warnings(compiler_warnings) - # ---------------------------------------------------------------------------------------------------------------------- - # ---------------------------------------------------- MISCELLANEOUS --------------------------------------------------- - # ---------------------------------------------------------------------------------------------------------------------- + # ------------------------------------------------------------------------------------------------------------------ + # ---------------------------------------------------- MISCELLANEOUS ----------------------------------------------- + # ------------------------------------------------------------------------------------------------------------------ # This will allow us to print a feature summary. # https://cmake.org/cmake/help/v3.11/module/FeatureSummary.html @@ -130,9 +131,9 @@ else (LDPC4QKD_HEADER_ONLY) message(WARNING "Using ccache to speed up the build is not possible.") endif (CCACHE_FOUND) - # ---------------------------------------------------------------------------------------------------------------------- - # ----------------------------------------------- INCLUDE SUBDIRECTORIES ----------------------------------------------- - # ---------------------------------------------------------------------------------------------------------------------- + # ------------------------------------------------------------------------------------------------------------------ + # ----------------------------------------------- INCLUDE SUBDIRECTORIES ------------------------------------------- + # ------------------------------------------------------------------------------------------------------------------ add_subdirectory(src) diff --git a/benchmarks_error_rate/code_simulation_helpers.hpp b/benchmarks_error_rate/code_simulation_helpers.hpp index 3318083..e372ea1 100644 --- a/benchmarks_error_rate/code_simulation_helpers.hpp +++ b/benchmarks_error_rate/code_simulation_helpers.hpp @@ -62,7 +62,7 @@ namespace LDPC4QKD::CodeSimulationHelpers { template - LDPC4QKD::RateAdaptiveCode load_ldpc_from_cscmat( + LDPC4QKD::RateAdaptiveCode load_ldpc_from_cscmat( const std::string &cscmat_file_path, const std::string &rate_adaption_file_path="" ) { auto pair = LDPC4QKD::read_matrix_from_cscmat(cscmat_file_path); @@ -70,10 +70,10 @@ namespace LDPC4QKD::CodeSimulationHelpers { auto row_idx = pair.second; if(rate_adaption_file_path.empty()) { - return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); + return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); } else { std::vector rows_to_combine = read_rate_adaption_from_csv(rate_adaption_file_path); - return LDPC4QKD::RateAdaptiveCode(colptr, row_idx, rows_to_combine); + return LDPC4QKD::RateAdaptiveCode(colptr, row_idx, rows_to_combine); } } @@ -94,7 +94,7 @@ namespace LDPC4QKD::CodeSimulationHelpers { template - LDPC4QKD::RateAdaptiveCode load_ldpc_from_json( + LDPC4QKD::RateAdaptiveCode load_ldpc_from_json( const std::string &json_file_path, const std::string &rate_adaption_file_path = "" ) { try { @@ -113,10 +113,10 @@ namespace LDPC4QKD::CodeSimulationHelpers { std::vector rowval = data["rowval"]; if (rate_adaption_file_path.empty()) { - return LDPC4QKD::RateAdaptiveCode(colptr, rowval); + return LDPC4QKD::RateAdaptiveCode(colptr, rowval); } else { std::vector rows_to_combine = read_rate_adaption_from_csv(rate_adaption_file_path); - return LDPC4QKD::RateAdaptiveCode(colptr, rowval, rows_to_combine); + return LDPC4QKD::RateAdaptiveCode(colptr, rowval, rows_to_combine); } } else if (data["format"] == "COMPRESSED_SPARSE_COLUMN") { // quasi-cyclic exponents stored // std::vector colptr = data["colptr"]; @@ -155,7 +155,7 @@ namespace LDPC4QKD::CodeSimulationHelpers { template - LDPC4QKD::RateAdaptiveCode load_ldpc( + LDPC4QKD::RateAdaptiveCode load_ldpc( const std::string &file_path, const std::string &rate_adaption_file_path="" ) { std::filesystem::path filePath = file_path; diff --git a/benchmarks_error_rate/main_rate_adapted_fer.cpp b/benchmarks_error_rate/main_rate_adapted_fer.cpp index 6d3da63..754ff2d 100644 --- a/benchmarks_error_rate/main_rate_adapted_fer.cpp +++ b/benchmarks_error_rate/main_rate_adapted_fer.cpp @@ -29,7 +29,7 @@ using namespace LDPC4QKD::CodeSimulationHelpers; template std::pair run_simulation( - const LDPC4QKD::RateAdaptiveCode &H, + const LDPC4QKD::RateAdaptiveCode &H, double p, std::size_t num_frames_to_test, std::mt19937_64 &rng, diff --git a/benchmarks_runtime/main_benchmark_decoder.cpp b/benchmarks_runtime/main_benchmark_decoder.cpp index da44379..6169685 100644 --- a/benchmarks_runtime/main_benchmark_decoder.cpp +++ b/benchmarks_runtime/main_benchmark_decoder.cpp @@ -32,10 +32,10 @@ void noise_bitstring_inplace(std::vector &src, double err_prob, unsigned int } -LDPC4QKD::RateAdaptiveCode get_code_big_nora() { +LDPC4QKD::RateAdaptiveCode get_code_big_nora() { std::vector colptr(AutogenLDPC::colptr.begin(), AutogenLDPC::colptr.end()); std::vector row_idx(AutogenLDPC::row_idx.begin(), AutogenLDPC::row_idx.end()); - return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); + return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); } diff --git a/benchmarks_runtime/main_benchmark_ra.cpp b/benchmarks_runtime/main_benchmark_ra.cpp index c9804ac..3da3239 100644 --- a/benchmarks_runtime/main_benchmark_ra.cpp +++ b/benchmarks_runtime/main_benchmark_ra.cpp @@ -16,11 +16,11 @@ #include "autogen_rate_adaption.hpp" -LDPC4QKD::RateAdaptiveCode get_code_big_wra() { +LDPC4QKD::RateAdaptiveCode get_code_big_wra() { std::vector colptr(AutogenLDPC::colptr.begin(), AutogenLDPC::colptr.end()); std::vector row_idx(AutogenLDPC::row_idx.begin(), AutogenLDPC::row_idx.end()); std::vector rows_to_combine(AutogenRateAdapt::rows.begin(), AutogenRateAdapt::rows.end()); - return LDPC4QKD::RateAdaptiveCode(colptr, row_idx, rows_to_combine); + return LDPC4QKD::RateAdaptiveCode(colptr, row_idx, rows_to_combine); } diff --git a/examples/main_demo_error_correction.cpp b/examples/main_demo_error_correction.cpp index 22d9d09..b8bad86 100644 --- a/examples/main_demo_error_correction.cpp +++ b/examples/main_demo_error_correction.cpp @@ -10,7 +10,7 @@ /// Get a LDPC matrix -LDPC4QKD::RateAdaptiveCode get_code_small() { +auto get_code_small() { /// We use this matrix as an example: /// H = [1 0 1 0 1 0 1 /// 0 1 1 0 0 1 1 @@ -19,7 +19,7 @@ LDPC4QKD::RateAdaptiveCode get_code_small() { /// To use it, we must convert H to compressed sparse column (CSC) storage: std::vector colptr{0, 1, 2, 4, 5, 7, 9, 12}; std::vector row_idx{0, 1, 0, 1, 2, 0, 2, 1, 2, 0, 1, 2}; - return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); + return LDPC4QKD::RateAdaptiveCode(colptr, row_idx); } @@ -44,7 +44,7 @@ int main() { llrs[i] = vlog * (1 - 2 * x_noised[i]); // log likelihood ratios } // alternatively, use the built-in convenience method that does the same: -// std::vector llrs = LDPC4QKD::RateAdaptiveCode::llrs_bsc(x, p); +// std::vector llrs = LDPC4QKD::RateAdaptiveCode::llrs_bsc(x, p); std::vector solution; bool decoder_converged = H.decode_at_current_rate(llrs, syndrome, solution); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b45d6c..085256e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,12 +39,13 @@ add_library(LDPC4QKD INTERFACE rate_adaptive_code.hpp # contains decoder and encoder. Does not need any other files to work. encoder.hpp # contains only minimal encoder that needs static storage of the LDPC matrix + encoder_advanced.hpp # REQUIRES C++20!!! advanced encoder (QC-enabled and constexpr objects). Handles storage. autogen_ldpc_matrix_csc.hpp # static storage of the LDPC matrix. Automatically generated by Julia script. autogen_rate_adaption.hpp # static storage of the rate adaption specification. Automatically generated by Julia script. read_ldpc_file_formats.hpp # helper methods to generate static storage (not needed to use encoder/decoder class). ) -target_compile_features(LDPC4QKD INTERFACE cxx_std_17) +target_compile_features(LDPC4QKD INTERFACE cxx_std_20) target_include_directories(LDPC4QKD INTERFACE diff --git a/src/autogen_ldpc_QC.hpp b/src/autogen_ldpc_QC.hpp new file mode 100644 index 0000000..d51360c --- /dev/null +++ b/src/autogen_ldpc_QC.hpp @@ -0,0 +1,294 @@ +// +// Created by Adomas Baliuka on 18.04.24. +// +// This is an automatically generated file. +// A sparse LDPC matrix (containing only zeros and ones) is saved in compressed sparse column (CSC) format. +// Since the matrix (and LDPC code) is known at compile time, there is no need to save it separately in a file. +// This significantly blows up the executable size (the memory would still have to be used when saving the matrix). +// The method seems to be reasonably fast (on a standard laptop). + + +#ifndef QKD_POSTPROC_BOB_AUTOGEN_LDPC_QC_HPP +#define QKD_POSTPROC_BOB_AUTOGEN_LDPC_QC_HPP + +#include +#include + +namespace AutogenLDPC_QC { + + constexpr inline std::size_t M = 64; + constexpr inline std::size_t N = 128; + constexpr inline std::size_t num_nz = 480; + constexpr inline std::size_t expansion_factor = 32; + + constexpr inline std::array colptr = { + 0x0,0x2,0x5,0xd,0xf,0x11,0x14,0x1c,0x1e,0x20,0x23,0x2b,0x2d,0x2f,0x32,0x3a,0x3c,0x3e,0x41,0x49,0x4b,0x4d,0x50,0x58,0x5a,0x5c,0x5f,0x67,0x69,0x6b,0x6e,0x76,0x78,0x7a,0x7d,0x85,0x87,0x89,0x8c,0x94,0x96,0x98,0x9b,0xa3,0xa5,0xa7,0xaa,0xb2,0xb4,0xb6,0xb9,0xc1,0xc3,0xc5,0xc8,0xd0,0xd2,0xd4,0xd7,0xdf,0xe1,0xe3,0xe6,0xee,0xf0,0xf2,0xf5,0xfd,0xff,0x101,0x104,0x10c,0x10e,0x110,0x113,0x11b,0x11d,0x11f,0x122,0x12a,0x12c,0x12e,0x131,0x139,0x13b,0x13d,0x140,0x148,0x14a,0x14c,0x14f,0x157,0x159,0x15b,0x15e,0x166,0x168,0x16a,0x16d,0x175, + 0x177,0x179,0x17c,0x184,0x186,0x188,0x18b,0x193,0x195,0x197,0x19a,0x1a2,0x1a4,0x1a6,0x1a9,0x1b1,0x1b3,0x1b5,0x1b8,0x1c0,0x1c2,0x1c4,0x1c7,0x1cf,0x1d1,0x1d3,0x1d6,0x1de,0x1e0 + }; + +// ------------------------------------------------------- + + constexpr inline std::array row_idx = { + 0x17,0x1b,0xd,0x2a,0x2e,0x3,0x6,0x1e,0x2e,0x32,0x35,0x36,0x3f,0x39,0x3e,0x27,0x3d,0x22,0x27,0x3c,0x5,0x1f,0x24,0x25,0x2a,0x2c,0x2e,0x36,0x1d,0x30,0xd,0x1d,0xa,0x20,0x39,0x4,0x8,0x11,0x17,0x1a,0x32,0x37,0x38,0xd,0x14,0xb,0x35,0x1f,0x38,0x3e,0x0,0x1,0xa,0x10,0x1b,0x1e,0x1f,0x32,0x0,0x29,0x1b,0x2f,0x7,0x24,0x3a,0xb,0x14,0x1e,0x21,0x22,0x36,0x3a,0x3b,0x23,0x3a,0x5,0x15,0x16,0x2c,0x3f,0x5,0x9,0xc,0x12,0x19,0x1c,0x28,0x3c,0x22,0x3b,0x25,0x33,0xc,0x26,0x3d,0x1,0x3,0xa,0x10,0x18, + 0x1d,0x20,0x24,0x10,0x2b,0xf,0x1f,0x0,0x18,0x2f,0x0,0xf,0x13,0x18,0x23,0x28,0x30,0x3e,0x9,0x20,0xb,0x3f,0x26,0x2e,0x33,0x8,0xd,0xe,0x16,0x1a,0x23,0x30,0x3d,0xe,0x13,0x7,0x31,0x1a,0x1c,0x2d,0x2,0x8,0x11,0x16,0x21,0x31,0x34,0x38,0xa,0x11,0x23,0x2d,0x1,0x1a,0x2a,0xc,0x17,0x20,0x2c,0x2d,0x30,0x37,0x3c,0x17,0x2a,0x7,0x31,0x13,0x18,0x22,0x2,0x5,0xa,0xc,0x24,0x28,0x33,0x35,0x1a,0x3f,0x9,0xf,0x2,0x12,0x15,0x0,0x3,0x6,0xa,0x25,0x2e,0x3a,0x3f,0x1,0x6,0x15,0x23,0x12,0x1b,0x28, + 0xe,0xf,0x12,0x14,0x1a,0x27,0x32,0x37,0x16,0x1b,0x2b,0x39,0x1e,0x2c,0x31,0x4,0x16,0x18,0x1a,0x21,0x2b,0x2c,0x2d,0x8,0x3d,0x13,0x21,0xe,0x11,0x30,0x9,0xc,0x18,0x19,0x1a,0x22,0x23,0x2c,0x5,0x2e,0x17,0x3d,0x10,0x24,0x29,0x6,0xe,0x1d,0x22,0x29,0x2b,0x3a,0x3e,0x18,0x37,0x1d,0x39,0x2,0xb,0x36,0x4,0x6,0x15,0x18,0x2a,0x38,0x3b,0x3d,0x33,0x3c,0x21,0x2f,0x3,0x10,0x30,0x4,0xc,0x10,0x1f,0x20,0x2f,0x38,0x3d,0x32,0x35,0x1f,0x29,0x6,0x1e,0x37,0x7,0xa,0xb,0x22,0x27,0x2a,0x30,0x3a,0xc,0x2f, + 0x19,0x27,0x8,0x3b,0x3c,0x4,0x12,0x15,0x28,0x29,0x2d,0x34,0x3c,0x2,0x3,0x19,0x3f,0x4,0x2b,0x3e,0x7,0x1c,0x24,0x25,0x26,0x2e,0x2f,0x34,0x31,0x36,0x5,0x2b,0xc,0xf,0x28,0x15,0x16,0x17,0x1e,0x26,0x2a,0x3b,0x3c,0x15,0x1e,0x1,0x3b,0x0,0x17,0x1c,0x2,0x10,0x1b,0x1e,0x26,0x39,0x3a,0x3f,0x7,0x28,0x3,0x9,0x23,0x34,0x36,0x1,0x2,0x7,0xe,0x11,0x14,0x26,0x3e,0xb,0x24,0x11,0x29,0x14,0x20,0x25,0x2,0xb,0xd,0x20,0x24,0x32,0x34,0x35,0xf,0x12,0x3,0xd,0x4,0x16,0x35,0x6,0x12,0x13,0x14,0x19, + 0x2b,0x36,0x3e,0x19,0x38,0x1,0x3b,0x5,0x6,0x8,0x8,0x1b,0x1c,0x2a,0x2f,0x30,0x38,0x39,0x1f,0x34,0x25,0x2d,0xa,0x21,0x32,0x0,0x9,0xf,0x10,0x13,0x34,0x36,0x3e,0x21,0x26,0x13,0x37,0xe,0x19,0x38,0x0,0xd,0x16,0x1c,0x1d,0x26,0x2e,0x31,0x1c,0x2d,0x33,0x35,0x9,0x14,0x3a,0x14,0x1c,0x22,0x27,0x28,0x31,0x33,0x3c,0x4,0x27,0x11,0x37,0x1d,0x32,0x34,0x8,0xe,0x12,0x20,0x29,0x2c,0x33,0x39,0x25,0x2c + }; + + +// ------------------------------------------------------- + + constexpr inline std::array values = { + 29,27,7,20,9,26,29,11,9,32,32,20,9,31,17,11,8,24,6,6,22,26,5,26,15,12,10,8,15,23,5,18,14,25,15,3,32,22,2,11,3,22,2,16,4,27,2,6,29,2,10,27,6,1,17,26,13,27,7,25,5,4,11,14,16,31,19,30,19,8,4,25,4,11,11,26,21,30,10,26,3,16,26,22,20,7,25,17,10,14,23,29,1,21,7,25,29,10,26,15, + 5,28,18,14,15,20,14,28,19,24,20,2,1,7,16,21,4,30,22,11,26,26,11,19,27,17,31,3,30,2,21,17,1,19,28,21,26,27,25,26,8,12,15,16,17,12,11,30,24,10,26,4,32,10,5,7,31,5,25,8,3,23,25,32,31,11,11,11,23,21,6,23,16,17,13,21,12,10,25,15,17,31,25,30,30,18,29,3,20,26,3,7,22,18,27,4,6,17,5,16, + 13,8,20,5,25,18,8,16,29,10,12,1,27,20,13,15,4,26,24,7,11,4,4,13,11,30,23,5,10,23,7,26,32,6,20,22,2,18,29,25,4,6,14,26,17,16,1,23,3,8,19,2,26,1,25,14,6,21,32,9,19,27,26,24,14,20,18,23,8,12,11,5,30,17,28,23,17,8,5,7,14,17,14,31,22,22,1,14,9,31,29,3,21,5,15,30,29,10,8,29, + 10,23,15,28,31,13,18,14,22,15,21,26,3,25,9,27,21,9,12,28,30,31,31,27,4,6,1,22,29,16,19,22,10,31,19,31,29,18,23,27,26,10,22,23,23,14,29,7,31,29,18,15,29,27,6,1,2,23,21,4,21,4,15,6,30,24,2,32,1,1,23,5,23,24,17,6,29,22,27,19,25,21,6,10,4,9,16,5,15,28,25,1,29,3,21,21,28,29,18,6, + 23,10,29,24,5,3,25,26,25,22,20,16,16,11,23,29,19,26,27,24,5,18,19,30,11,10,21,32,4,22,15,30,5,1,4,20,17,27,4,23,23,9,15,20,28,22,19,27,13,15,24,1,14,26,25,13,28,8,12,17,12,1,8,24,19,5,10,10,27,5,23,19,17,22,28,23,10,31,10,7 + }; +} // namespace AutogenLDPC_QC + + +namespace AutogenLDPC_QC_1MRhalf { + + constexpr inline std::size_t M = 1024; + constexpr inline std::size_t N = 2048; + constexpr inline std::size_t num_nz = 7680; + constexpr inline std::size_t expansion_factor = 512; + + constexpr inline std::array colptr = { + 0x0,0x2,0x5,0xd,0xf,0x11,0x14,0x1c,0x1e,0x20,0x23,0x2b,0x2d,0x2f,0x32,0x3a,0x3c,0x3e,0x41,0x49,0x4b,0x4d,0x50,0x58,0x5a,0x5c,0x5f,0x67,0x69,0x6b,0x6e,0x76,0x78,0x7a,0x7d,0x85,0x87,0x89,0x8c,0x94,0x96,0x98,0x9b,0xa3,0xa5,0xa7,0xaa,0xb2,0xb4,0xb6,0xb9,0xc1,0xc3,0xc5,0xc8,0xd0,0xd2,0xd4,0xd7,0xdf,0xe1,0xe3,0xe6,0xee,0xf0,0xf2,0xf5,0xfd,0xff,0x101,0x104,0x10c,0x10e,0x110,0x113,0x11b,0x11d,0x11f,0x122,0x12a,0x12c,0x12e,0x131,0x139,0x13b,0x13d,0x140,0x148,0x14a,0x14c,0x14f,0x157,0x159,0x15b,0x15e,0x166,0x168,0x16a,0x16d,0x175, + 0x177,0x179,0x17c,0x184,0x186,0x188,0x18b,0x193,0x195,0x197,0x19a,0x1a2,0x1a4,0x1a6,0x1a9,0x1b1,0x1b3,0x1b5,0x1b8,0x1c0,0x1c2,0x1c4,0x1c7,0x1cf,0x1d1,0x1d3,0x1d6,0x1de,0x1e0,0x1e2,0x1e5,0x1ed,0x1ef,0x1f1,0x1f4,0x1fc,0x1fe,0x200,0x203,0x20b,0x20d,0x20f,0x212,0x21a,0x21c,0x21e,0x221,0x229,0x22b,0x22d,0x230,0x238,0x23a,0x23c,0x23f,0x247,0x249,0x24b,0x24e,0x256,0x258,0x25a,0x25d,0x265,0x267,0x269,0x26c,0x274,0x276,0x278,0x27b,0x283,0x285,0x287,0x28a,0x292,0x294,0x296,0x299,0x2a1,0x2a3,0x2a5,0x2a8,0x2b0,0x2b2,0x2b4,0x2b7,0x2bf,0x2c1,0x2c3,0x2c6,0x2ce,0x2d0,0x2d2,0x2d5,0x2dd,0x2df,0x2e1,0x2e4,0x2ec, + 0x2ee,0x2f0,0x2f3,0x2fb,0x2fd,0x2ff,0x302,0x30a,0x30c,0x30e,0x311,0x319,0x31b,0x31d,0x320,0x328,0x32a,0x32c,0x32f,0x337,0x339,0x33b,0x33e,0x346,0x348,0x34a,0x34d,0x355,0x357,0x359,0x35c,0x364,0x366,0x368,0x36b,0x373,0x375,0x377,0x37a,0x382,0x384,0x386,0x389,0x391,0x393,0x395,0x398,0x3a0,0x3a2,0x3a4,0x3a7,0x3af,0x3b1,0x3b3,0x3b6,0x3be,0x3c0,0x3c2,0x3c5,0x3cd,0x3cf,0x3d1,0x3d4,0x3dc,0x3de,0x3e0,0x3e3,0x3eb,0x3ed,0x3ef,0x3f2,0x3fa,0x3fc,0x3fe,0x401,0x409,0x40b,0x40d,0x410,0x418,0x41a,0x41c,0x41f,0x427,0x429,0x42b,0x42e,0x436,0x438,0x43a,0x43d,0x445,0x447,0x449,0x44c,0x454,0x456,0x458,0x45b,0x463, + 0x465,0x467,0x46a,0x472,0x474,0x476,0x479,0x481,0x483,0x485,0x488,0x490,0x492,0x494,0x497,0x49f,0x4a1,0x4a3,0x4a6,0x4ae,0x4b0,0x4b2,0x4b5,0x4bd,0x4bf,0x4c1,0x4c4,0x4cc,0x4ce,0x4d0,0x4d3,0x4db,0x4dd,0x4df,0x4e2,0x4ea,0x4ec,0x4ee,0x4f1,0x4f9,0x4fb,0x4fd,0x500,0x508,0x50a,0x50c,0x50f,0x517,0x519,0x51b,0x51e,0x526,0x528,0x52a,0x52d,0x535,0x537,0x539,0x53c,0x544,0x546,0x548,0x54b,0x553,0x555,0x557,0x55a,0x562,0x564,0x566,0x569,0x571,0x573,0x575,0x578,0x580,0x582,0x584,0x587,0x58f,0x591,0x593,0x596,0x59e,0x5a0,0x5a2,0x5a5,0x5ad,0x5af,0x5b1,0x5b4,0x5bc,0x5be,0x5c0,0x5c3,0x5cb,0x5cd,0x5cf,0x5d2,0x5da, + 0x5dc,0x5de,0x5e1,0x5e9,0x5eb,0x5ed,0x5f0,0x5f8,0x5fa,0x5fc,0x5ff,0x607,0x609,0x60b,0x60e,0x616,0x618,0x61a,0x61d,0x625,0x627,0x629,0x62c,0x634,0x636,0x638,0x63b,0x643,0x645,0x647,0x64a,0x652,0x654,0x656,0x659,0x661,0x663,0x665,0x668,0x670,0x672,0x674,0x677,0x67f,0x681,0x683,0x686,0x68e,0x690,0x692,0x695,0x69d,0x69f,0x6a1,0x6a4,0x6ac,0x6ae,0x6b0,0x6b3,0x6bb,0x6bd,0x6bf,0x6c2,0x6ca,0x6cc,0x6ce,0x6d1,0x6d9,0x6db,0x6dd,0x6e0,0x6e8,0x6ea,0x6ec,0x6ef,0x6f7,0x6f9,0x6fb,0x6fe,0x706,0x708,0x70a,0x70d,0x715,0x717,0x719,0x71c,0x724,0x726,0x728,0x72b,0x733,0x735,0x737,0x73a,0x742,0x744,0x746,0x749,0x751, + 0x753,0x755,0x758,0x760,0x762,0x764,0x767,0x76f,0x771,0x773,0x776,0x77e,0x780,0x782,0x785,0x78d,0x78f,0x791,0x794,0x79c,0x79e,0x7a0,0x7a3,0x7ab,0x7ad,0x7af,0x7b2,0x7ba,0x7bc,0x7be,0x7c1,0x7c9,0x7cb,0x7cd,0x7d0,0x7d8,0x7da,0x7dc,0x7df,0x7e7,0x7e9,0x7eb,0x7ee,0x7f6,0x7f8,0x7fa,0x7fd,0x805,0x807,0x809,0x80c,0x814,0x816,0x818,0x81b,0x823,0x825,0x827,0x82a,0x832,0x834,0x836,0x839,0x841,0x843,0x845,0x848,0x850,0x852,0x854,0x857,0x85f,0x861,0x863,0x866,0x86e,0x870,0x872,0x875,0x87d,0x87f,0x881,0x884,0x88c,0x88e,0x890,0x893,0x89b,0x89d,0x89f,0x8a2,0x8aa,0x8ac,0x8ae,0x8b1,0x8b9,0x8bb,0x8bd,0x8c0,0x8c8, + 0x8ca,0x8cc,0x8cf,0x8d7,0x8d9,0x8db,0x8de,0x8e6,0x8e8,0x8ea,0x8ed,0x8f5,0x8f7,0x8f9,0x8fc,0x904,0x906,0x908,0x90b,0x913,0x915,0x917,0x91a,0x922,0x924,0x926,0x929,0x931,0x933,0x935,0x938,0x940,0x942,0x944,0x947,0x94f,0x951,0x953,0x956,0x95e,0x960,0x962,0x965,0x96d,0x96f,0x971,0x974,0x97c,0x97e,0x980,0x983,0x98b,0x98d,0x98f,0x992,0x99a,0x99c,0x99e,0x9a1,0x9a9,0x9ab,0x9ad,0x9b0,0x9b8,0x9ba,0x9bc,0x9bf,0x9c7,0x9c9,0x9cb,0x9ce,0x9d6,0x9d8,0x9da,0x9dd,0x9e5,0x9e7,0x9e9,0x9ec,0x9f4,0x9f6,0x9f8,0x9fb,0xa03,0xa05,0xa07,0xa0a,0xa12,0xa14,0xa16,0xa19,0xa21,0xa23,0xa25,0xa28,0xa30,0xa32,0xa34,0xa37,0xa3f, + 0xa41,0xa43,0xa46,0xa4e,0xa50,0xa52,0xa55,0xa5d,0xa5f,0xa61,0xa64,0xa6c,0xa6e,0xa70,0xa73,0xa7b,0xa7d,0xa7f,0xa82,0xa8a,0xa8c,0xa8e,0xa91,0xa99,0xa9b,0xa9d,0xaa0,0xaa8,0xaaa,0xaac,0xaaf,0xab7,0xab9,0xabb,0xabe,0xac6,0xac8,0xaca,0xacd,0xad5,0xad7,0xad9,0xadc,0xae4,0xae6,0xae8,0xaeb,0xaf3,0xaf5,0xaf7,0xafa,0xb02,0xb04,0xb06,0xb09,0xb11,0xb13,0xb15,0xb18,0xb20,0xb22,0xb24,0xb27,0xb2f,0xb31,0xb33,0xb36,0xb3e,0xb40,0xb42,0xb45,0xb4d,0xb4f,0xb51,0xb54,0xb5c,0xb5e,0xb60,0xb63,0xb6b,0xb6d,0xb6f,0xb72,0xb7a,0xb7c,0xb7e,0xb81,0xb89,0xb8b,0xb8d,0xb90,0xb98,0xb9a,0xb9c,0xb9f,0xba7,0xba9,0xbab,0xbae,0xbb6, + 0xbb8,0xbba,0xbbd,0xbc5,0xbc7,0xbc9,0xbcc,0xbd4,0xbd6,0xbd8,0xbdb,0xbe3,0xbe5,0xbe7,0xbea,0xbf2,0xbf4,0xbf6,0xbf9,0xc01,0xc03,0xc05,0xc08,0xc10,0xc12,0xc14,0xc17,0xc1f,0xc21,0xc23,0xc26,0xc2e,0xc30,0xc32,0xc35,0xc3d,0xc3f,0xc41,0xc44,0xc4c,0xc4e,0xc50,0xc53,0xc5b,0xc5d,0xc5f,0xc62,0xc6a,0xc6c,0xc6e,0xc71,0xc79,0xc7b,0xc7d,0xc80,0xc88,0xc8a,0xc8c,0xc8f,0xc97,0xc99,0xc9b,0xc9e,0xca6,0xca8,0xcaa,0xcad,0xcb5,0xcb7,0xcb9,0xcbc,0xcc4,0xcc6,0xcc8,0xccb,0xcd3,0xcd5,0xcd7,0xcda,0xce2,0xce4,0xce6,0xce9,0xcf1,0xcf3,0xcf5,0xcf8,0xd00,0xd02,0xd04,0xd07,0xd0f,0xd11,0xd13,0xd16,0xd1e,0xd20,0xd22,0xd25,0xd2d, + 0xd2f,0xd31,0xd34,0xd3c,0xd3e,0xd40,0xd43,0xd4b,0xd4d,0xd4f,0xd52,0xd5a,0xd5c,0xd5e,0xd61,0xd69,0xd6b,0xd6d,0xd70,0xd78,0xd7a,0xd7c,0xd7f,0xd87,0xd89,0xd8b,0xd8e,0xd96,0xd98,0xd9a,0xd9d,0xda5,0xda7,0xda9,0xdac,0xdb4,0xdb6,0xdb8,0xdbb,0xdc3,0xdc5,0xdc7,0xdca,0xdd2,0xdd4,0xdd6,0xdd9,0xde1,0xde3,0xde5,0xde8,0xdf0,0xdf2,0xdf4,0xdf7,0xdff,0xe01,0xe03,0xe06,0xe0e,0xe10,0xe12,0xe15,0xe1d,0xe1f,0xe21,0xe24,0xe2c,0xe2e,0xe30,0xe33,0xe3b,0xe3d,0xe3f,0xe42,0xe4a,0xe4c,0xe4e,0xe51,0xe59,0xe5b,0xe5d,0xe60,0xe68,0xe6a,0xe6c,0xe6f,0xe77,0xe79,0xe7b,0xe7e,0xe86,0xe88,0xe8a,0xe8d,0xe95,0xe97,0xe99,0xe9c,0xea4, + 0xea6,0xea8,0xeab,0xeb3,0xeb5,0xeb7,0xeba,0xec2,0xec4,0xec6,0xec9,0xed1,0xed3,0xed5,0xed8,0xee0,0xee2,0xee4,0xee7,0xeef,0xef1,0xef3,0xef6,0xefe,0xf00,0xf02,0xf05,0xf0d,0xf0f,0xf11,0xf14,0xf1c,0xf1e,0xf20,0xf23,0xf2b,0xf2d,0xf2f,0xf32,0xf3a,0xf3c,0xf3e,0xf41,0xf49,0xf4b,0xf4d,0xf50,0xf58,0xf5a,0xf5c,0xf5f,0xf67,0xf69,0xf6b,0xf6e,0xf76,0xf78,0xf7a,0xf7d,0xf85,0xf87,0xf89,0xf8c,0xf94,0xf96,0xf98,0xf9b,0xfa3,0xfa5,0xfa7,0xfaa,0xfb2,0xfb4,0xfb6,0xfb9,0xfc1,0xfc3,0xfc5,0xfc8,0xfd0,0xfd2,0xfd4,0xfd7,0xfdf,0xfe1,0xfe3,0xfe6,0xfee,0xff0,0xff2,0xff5,0xffd,0xfff,0x1001,0x1004,0x100c,0x100e,0x1010,0x1013,0x101b, + 0x101d,0x101f,0x1022,0x102a,0x102c,0x102e,0x1031,0x1039,0x103b,0x103d,0x1040,0x1048,0x104a,0x104c,0x104f,0x1057,0x1059,0x105b,0x105e,0x1066,0x1068,0x106a,0x106d,0x1075,0x1077,0x1079,0x107c,0x1084,0x1086,0x1088,0x108b,0x1093,0x1095,0x1097,0x109a,0x10a2,0x10a4,0x10a6,0x10a9,0x10b1,0x10b3,0x10b5,0x10b8,0x10c0,0x10c2,0x10c4,0x10c7,0x10cf,0x10d1,0x10d3,0x10d6,0x10de,0x10e0,0x10e2,0x10e5,0x10ed,0x10ef,0x10f1,0x10f4,0x10fc,0x10fe,0x1100,0x1103,0x110b,0x110d,0x110f,0x1112,0x111a,0x111c,0x111e,0x1121,0x1129,0x112b,0x112d,0x1130,0x1138,0x113a,0x113c,0x113f,0x1147,0x1149,0x114b,0x114e,0x1156,0x1158,0x115a,0x115d,0x1165,0x1167,0x1169,0x116c,0x1174,0x1176,0x1178,0x117b,0x1183,0x1185,0x1187,0x118a,0x1192, + 0x1194,0x1196,0x1199,0x11a1,0x11a3,0x11a5,0x11a8,0x11b0,0x11b2,0x11b4,0x11b7,0x11bf,0x11c1,0x11c3,0x11c6,0x11ce,0x11d0,0x11d2,0x11d5,0x11dd,0x11df,0x11e1,0x11e4,0x11ec,0x11ee,0x11f0,0x11f3,0x11fb,0x11fd,0x11ff,0x1202,0x120a,0x120c,0x120e,0x1211,0x1219,0x121b,0x121d,0x1220,0x1228,0x122a,0x122c,0x122f,0x1237,0x1239,0x123b,0x123e,0x1246,0x1248,0x124a,0x124d,0x1255,0x1257,0x1259,0x125c,0x1264,0x1266,0x1268,0x126b,0x1273,0x1275,0x1277,0x127a,0x1282,0x1284,0x1286,0x1289,0x1291,0x1293,0x1295,0x1298,0x12a0,0x12a2,0x12a4,0x12a7,0x12af,0x12b1,0x12b3,0x12b6,0x12be,0x12c0,0x12c2,0x12c5,0x12cd,0x12cf,0x12d1,0x12d4,0x12dc,0x12de,0x12e0,0x12e3,0x12eb,0x12ed,0x12ef,0x12f2,0x12fa,0x12fc,0x12fe,0x1301,0x1309, + 0x130b,0x130d,0x1310,0x1318,0x131a,0x131c,0x131f,0x1327,0x1329,0x132b,0x132e,0x1336,0x1338,0x133a,0x133d,0x1345,0x1347,0x1349,0x134c,0x1354,0x1356,0x1358,0x135b,0x1363,0x1365,0x1367,0x136a,0x1372,0x1374,0x1376,0x1379,0x1381,0x1383,0x1385,0x1388,0x1390,0x1392,0x1394,0x1397,0x139f,0x13a1,0x13a3,0x13a6,0x13ae,0x13b0,0x13b2,0x13b5,0x13bd,0x13bf,0x13c1,0x13c4,0x13cc,0x13ce,0x13d0,0x13d3,0x13db,0x13dd,0x13df,0x13e2,0x13ea,0x13ec,0x13ee,0x13f1,0x13f9,0x13fb,0x13fd,0x1400,0x1408,0x140a,0x140c,0x140f,0x1417,0x1419,0x141b,0x141e,0x1426,0x1428,0x142a,0x142d,0x1435,0x1437,0x1439,0x143c,0x1444,0x1446,0x1448,0x144b,0x1453,0x1455,0x1457,0x145a,0x1462,0x1464,0x1466,0x1469,0x1471,0x1473,0x1475,0x1478,0x1480, + 0x1482,0x1484,0x1487,0x148f,0x1491,0x1493,0x1496,0x149e,0x14a0,0x14a2,0x14a5,0x14ad,0x14af,0x14b1,0x14b4,0x14bc,0x14be,0x14c0,0x14c3,0x14cb,0x14cd,0x14cf,0x14d2,0x14da,0x14dc,0x14de,0x14e1,0x14e9,0x14eb,0x14ed,0x14f0,0x14f8,0x14fa,0x14fc,0x14ff,0x1507,0x1509,0x150b,0x150e,0x1516,0x1518,0x151a,0x151d,0x1525,0x1527,0x1529,0x152c,0x1534,0x1536,0x1538,0x153b,0x1543,0x1545,0x1547,0x154a,0x1552,0x1554,0x1556,0x1559,0x1561,0x1563,0x1565,0x1568,0x1570,0x1572,0x1574,0x1577,0x157f,0x1581,0x1583,0x1586,0x158e,0x1590,0x1592,0x1595,0x159d,0x159f,0x15a1,0x15a4,0x15ac,0x15ae,0x15b0,0x15b3,0x15bb,0x15bd,0x15bf,0x15c2,0x15ca,0x15cc,0x15ce,0x15d1,0x15d9,0x15db,0x15dd,0x15e0,0x15e8,0x15ea,0x15ec,0x15ef,0x15f7, + 0x15f9,0x15fb,0x15fe,0x1606,0x1608,0x160a,0x160d,0x1615,0x1617,0x1619,0x161c,0x1624,0x1626,0x1628,0x162b,0x1633,0x1635,0x1637,0x163a,0x1642,0x1644,0x1646,0x1649,0x1651,0x1653,0x1655,0x1658,0x1660,0x1662,0x1664,0x1667,0x166f,0x1671,0x1673,0x1676,0x167e,0x1680,0x1682,0x1685,0x168d,0x168f,0x1691,0x1694,0x169c,0x169e,0x16a0,0x16a3,0x16ab,0x16ad,0x16af,0x16b2,0x16ba,0x16bc,0x16be,0x16c1,0x16c9,0x16cb,0x16cd,0x16d0,0x16d8,0x16da,0x16dc,0x16df,0x16e7,0x16e9,0x16eb,0x16ee,0x16f6,0x16f8,0x16fa,0x16fd,0x1705,0x1707,0x1709,0x170c,0x1714,0x1716,0x1718,0x171b,0x1723,0x1725,0x1727,0x172a,0x1732,0x1734,0x1736,0x1739,0x1741,0x1743,0x1745,0x1748,0x1750,0x1752,0x1754,0x1757,0x175f,0x1761,0x1763,0x1766,0x176e, + 0x1770,0x1772,0x1775,0x177d,0x177f,0x1781,0x1784,0x178c,0x178e,0x1790,0x1793,0x179b,0x179d,0x179f,0x17a2,0x17aa,0x17ac,0x17ae,0x17b1,0x17b9,0x17bb,0x17bd,0x17c0,0x17c8,0x17ca,0x17cc,0x17cf,0x17d7,0x17d9,0x17db,0x17de,0x17e6,0x17e8,0x17ea,0x17ed,0x17f5,0x17f7,0x17f9,0x17fc,0x1804,0x1806,0x1808,0x180b,0x1813,0x1815,0x1817,0x181a,0x1822,0x1824,0x1826,0x1829,0x1831,0x1833,0x1835,0x1838,0x1840,0x1842,0x1844,0x1847,0x184f,0x1851,0x1853,0x1856,0x185e,0x1860,0x1862,0x1865,0x186d,0x186f,0x1871,0x1874,0x187c,0x187e,0x1880,0x1883,0x188b,0x188d,0x188f,0x1892,0x189a,0x189c,0x189e,0x18a1,0x18a9,0x18ab,0x18ad,0x18b0,0x18b8,0x18ba,0x18bc,0x18bf,0x18c7,0x18c9,0x18cb,0x18ce,0x18d6,0x18d8,0x18da,0x18dd,0x18e5, + 0x18e7,0x18e9,0x18ec,0x18f4,0x18f6,0x18f8,0x18fb,0x1903,0x1905,0x1907,0x190a,0x1912,0x1914,0x1916,0x1919,0x1921,0x1923,0x1925,0x1928,0x1930,0x1932,0x1934,0x1937,0x193f,0x1941,0x1943,0x1946,0x194e,0x1950,0x1952,0x1955,0x195d,0x195f,0x1961,0x1964,0x196c,0x196e,0x1970,0x1973,0x197b,0x197d,0x197f,0x1982,0x198a,0x198c,0x198e,0x1991,0x1999,0x199b,0x199d,0x19a0,0x19a8,0x19aa,0x19ac,0x19af,0x19b7,0x19b9,0x19bb,0x19be,0x19c6,0x19c8,0x19ca,0x19cd,0x19d5,0x19d7,0x19d9,0x19dc,0x19e4,0x19e6,0x19e8,0x19eb,0x19f3,0x19f5,0x19f7,0x19fa,0x1a02,0x1a04,0x1a06,0x1a09,0x1a11,0x1a13,0x1a15,0x1a18,0x1a20,0x1a22,0x1a24,0x1a27,0x1a2f,0x1a31,0x1a33,0x1a36,0x1a3e,0x1a40,0x1a42,0x1a45,0x1a4d,0x1a4f,0x1a51,0x1a54,0x1a5c, + 0x1a5e,0x1a60,0x1a63,0x1a6b,0x1a6d,0x1a6f,0x1a72,0x1a7a,0x1a7c,0x1a7e,0x1a81,0x1a89,0x1a8b,0x1a8d,0x1a90,0x1a98,0x1a9a,0x1a9c,0x1a9f,0x1aa7,0x1aa9,0x1aab,0x1aae,0x1ab6,0x1ab8,0x1aba,0x1abd,0x1ac5,0x1ac7,0x1ac9,0x1acc,0x1ad4,0x1ad6,0x1ad8,0x1adb,0x1ae3,0x1ae5,0x1ae7,0x1aea,0x1af2,0x1af4,0x1af6,0x1af9,0x1b01,0x1b03,0x1b05,0x1b08,0x1b10,0x1b12,0x1b14,0x1b17,0x1b1f,0x1b21,0x1b23,0x1b26,0x1b2e,0x1b30,0x1b32,0x1b35,0x1b3d,0x1b3f,0x1b41,0x1b44,0x1b4c,0x1b4e,0x1b50,0x1b53,0x1b5b,0x1b5d,0x1b5f,0x1b62,0x1b6a,0x1b6c,0x1b6e,0x1b71,0x1b79,0x1b7b,0x1b7d,0x1b80,0x1b88,0x1b8a,0x1b8c,0x1b8f,0x1b97,0x1b99,0x1b9b,0x1b9e,0x1ba6,0x1ba8,0x1baa,0x1bad,0x1bb5,0x1bb7,0x1bb9,0x1bbc,0x1bc4,0x1bc6,0x1bc8,0x1bcb,0x1bd3, + 0x1bd5,0x1bd7,0x1bda,0x1be2,0x1be4,0x1be6,0x1be9,0x1bf1,0x1bf3,0x1bf5,0x1bf8,0x1c00,0x1c02,0x1c04,0x1c07,0x1c0f,0x1c11,0x1c13,0x1c16,0x1c1e,0x1c20,0x1c22,0x1c25,0x1c2d,0x1c2f,0x1c31,0x1c34,0x1c3c,0x1c3e,0x1c40,0x1c43,0x1c4b,0x1c4d,0x1c4f,0x1c52,0x1c5a,0x1c5c,0x1c5e,0x1c61,0x1c69,0x1c6b,0x1c6d,0x1c70,0x1c78,0x1c7a,0x1c7c,0x1c7f,0x1c87,0x1c89,0x1c8b,0x1c8e,0x1c96,0x1c98,0x1c9a,0x1c9d,0x1ca5,0x1ca7,0x1ca9,0x1cac,0x1cb4,0x1cb6,0x1cb8,0x1cbb,0x1cc3,0x1cc5,0x1cc7,0x1cca,0x1cd2,0x1cd4,0x1cd6,0x1cd9,0x1ce1,0x1ce3,0x1ce5,0x1ce8,0x1cf0,0x1cf2,0x1cf4,0x1cf7,0x1cff,0x1d01,0x1d03,0x1d06,0x1d0e,0x1d10,0x1d12,0x1d15,0x1d1d,0x1d1f,0x1d21,0x1d24,0x1d2c,0x1d2e,0x1d30,0x1d33,0x1d3b,0x1d3d,0x1d3f,0x1d42,0x1d4a, + 0x1d4c,0x1d4e,0x1d51,0x1d59,0x1d5b,0x1d5d,0x1d60,0x1d68,0x1d6a,0x1d6c,0x1d6f,0x1d77,0x1d79,0x1d7b,0x1d7e,0x1d86,0x1d88,0x1d8a,0x1d8d,0x1d95,0x1d97,0x1d99,0x1d9c,0x1da4,0x1da6,0x1da8,0x1dab,0x1db3,0x1db5,0x1db7,0x1dba,0x1dc2,0x1dc4,0x1dc6,0x1dc9,0x1dd1,0x1dd3,0x1dd5,0x1dd8,0x1de0,0x1de2,0x1de4,0x1de7,0x1def,0x1df1,0x1df3,0x1df6,0x1dfe,0x1e00 + }; + +// ------------------------------------------------------- + + constexpr inline std::array row_idx = { + 0x2d5,0x301,0x145,0x14a,0x2ae,0x2,0x38,0x105,0x10b,0x217,0x2bc,0x2c2,0x310,0xb8,0x3fd,0xcd,0xf9,0x4,0x1fe,0x20b,0x53,0x80,0x160,0x1c8,0x2d5,0x2f6,0x396,0x3f7,0x4,0x1cf,0x27,0x3cb,0x1a3,0x21c,0x378,0xaa,0x10f,0x199,0x212,0x287,0x2d8,0x300,0x366,0x2c1,0x2da,0x183,0x1c3,0x270,0x324,0x333,0x43,0xc8,0xfd,0x170,0x206,0x2e4,0x371,0x3f4,0x76,0x249,0x67,0x207,0x8c,0x191,0x2e4,0x5c,0x1ec,0x292,0x2e8,0x323,0x36c,0x3ab,0x3b5,0x58,0x267,0x307,0x387,0x56,0x111,0x2c8,0x1d,0x58,0x100,0x132,0x13f,0x25a,0x30a,0x36b,0x12f,0x1ee,0x55,0x237,0x61,0x148,0x3be,0x5b,0x8f,0xa0,0xae,0xf0, + 0x145,0x330,0x3c6,0x57,0x142,0x1cd,0x1e7,0xe0,0x1dc,0x3e7,0xea,0x119,0x1a4,0x1ea,0x28d,0x390,0x3aa,0x3db,0x26e,0x3cf,0x1e5,0x367,0x1ad,0x30c,0x3f0,0x28,0x5e,0x1bb,0x1c0,0x1ea,0x3bb,0x3d5,0x3ee,0x147,0x310,0x267,0x2db,0x33,0x88,0x30e,0x6b,0x13a,0x16c,0x1bd,0x2a4,0x2da,0x30b,0x3d6,0x12e,0x349,0x14b,0x1ab,0x21,0x1c8,0x268,0xda,0xf9,0x152,0x182,0x297,0x320,0x392,0x3f9,0x12,0x27f,0x21f,0x2ab,0x85,0x166,0x2ce,0x141,0x170,0x1d6,0x22b,0x24c,0x31f,0x378,0x3f6,0x21c,0x21d,0x25b,0x38b,0x7d,0xaa,0x3d0,0x2b,0x102,0x17c,0x1af,0x25a,0x2a5,0x336,0x36c,0x293,0x332,0x10b,0x199,0xed,0xf4,0x2b2, + 0xca,0xfb,0x1ac,0x1e1,0x2cc,0x2e4,0x39a,0x3ed,0x1a3,0x366,0x1f3,0x333,0xfa,0x19c,0x3cf,0x10a,0x179,0x1ca,0x1cc,0x24d,0x31f,0x334,0x364,0x23,0xf0,0x43,0xb1,0x102,0x21d,0x2da,0x5,0x28,0x4a,0x248,0x2ce,0x35d,0x37c,0x3b1,0x218,0x387,0x34f,0x3d5,0x178,0x24e,0x3b3,0xc9,0xe0,0x19e,0x1f4,0x2b7,0x317,0x35a,0x3ce,0x125,0x2b6,0x301,0x3e9,0x72,0x1be,0x3d5,0x96,0xa9,0xd1,0x136,0x1f4,0x1fa,0x313,0x3ec,0x1e,0x69,0x159,0x2a9,0x283,0x390,0x3f2,0x4e,0x1ae,0x1b9,0x217,0x292,0x2f3,0x378,0x3d0,0x36a,0x3d3,0x197,0x1fd,0x106,0x282,0x2e7,0xab,0xb4,0x1d6,0x1d7,0x206,0x27d,0x356,0x3ee,0x114,0x199, + 0xf7,0x2fd,0x53,0x1c6,0x1da,0x6c,0xbd,0x119,0x131,0x13c,0x19e,0x20c,0x2e4,0x1e3,0x1f0,0x2eb,0x3bd,0x1ca,0x34d,0x3e4,0x2d,0x73,0x122,0x1aa,0x238,0x2e0,0x361,0x3ea,0x187,0x268,0x10b,0x129,0x9c,0x209,0x25c,0xe8,0x146,0x23b,0x2aa,0x2af,0x326,0x329,0x342,0x303,0x33c,0xc3,0x3cd,0x198,0x2f3,0x3fa,0x17d,0x242,0x263,0x346,0x372,0x39a,0x3ae,0x3d9,0x17c,0x1b3,0xf9,0x3f1,0x2d3,0x352,0x376,0xcb,0x163,0x18a,0x1de,0x24b,0x2c4,0x33a,0x3b2,0x116,0x3f9,0x7f,0x2dd,0x327,0x332,0x35a,0x158,0x1b3,0x1e0,0x293,0x294,0x2af,0x2f2,0x3b6,0x2ee,0x3ef,0x139,0x249,0x98,0x15d,0x24a,0x92,0x181,0x1a4,0x1e4,0x307, + 0x38c,0x3ee,0x3fd,0xb5,0x110,0x113,0x233,0x93,0xa0,0xac,0xa4,0x185,0x1a1,0x1d2,0x272,0x2e1,0x2ec,0x3b4,0xca,0x2d3,0xe7,0x2d7,0x1be,0x32b,0x37a,0x40,0x7a,0x200,0x275,0x31d,0x3a5,0x3b6,0x3e6,0xfa,0x1bf,0x29,0x1ad,0x1f6,0x206,0x2cb,0xde,0x19a,0x202,0x22b,0x246,0x35f,0x36a,0x383,0x150,0x32d,0xf1,0x3ab,0xe7,0x26a,0x33a,0x18,0x161,0x204,0x22b,0x2c6,0x380,0x38b,0x3c6,0x141,0x15e,0x1c7,0x3d3,0x1,0x60,0x8e,0x16,0xa9,0x106,0x178,0x1f1,0x1f9,0x352,0x398,0x19b,0x2cc,0x5,0x1a1,0x16,0xf0,0x29b,0x16,0x75,0x1e2,0x348,0x34a,0x34e,0x353,0x3bd,0x27d,0x2fc,0x69,0x2f3,0x37,0x1d4,0x2b0, + 0x28,0x16e,0x1cb,0x1df,0x27a,0x283,0x2a0,0x2fc,0x3a8,0x3c3,0x10d,0x383,0x1b8,0x27e,0x367,0xfc,0x102,0x14f,0x15c,0x1c8,0x29f,0x2b9,0x338,0x184,0x353,0x281,0x3e9,0x8,0xc,0x32d,0x55,0x9a,0xb6,0x168,0x176,0x1f9,0x22d,0x3cc,0x108,0x251,0xf3,0x32f,0x192,0x280,0x3bf,0x17f,0x196,0x231,0x266,0x2ae,0x2c4,0x360,0x391,0x101,0x202,0x8f,0x1f9,0x14,0x29e,0x369,0x148,0x167,0x188,0x1f0,0x2c1,0x3aa,0x3dc,0x3f5,0x1bb,0x360,0x9d,0x165,0x114,0x1b7,0x3b2,0x7a,0x11c,0x1bb,0x2c9,0x324,0x356,0x3e2,0x3fb,0x6,0xc1,0x1f7,0x285,0x6a,0x1cb,0x214,0x56,0x5a,0xa9,0xe4,0x1f8,0x2e3,0x3a4,0x3dd,0x81,0x122, + 0x5,0x2d3,0x120,0x354,0x3bd,0x7a,0x97,0xa2,0x11b,0x174,0x1a8,0x2d7,0x346,0x4c,0x2df,0x16b,0x1eb,0x42,0x36d,0x396,0x159,0x258,0x28e,0x2fa,0x331,0x36f,0x3c8,0x3d0,0x86,0x2e7,0x3b,0x353,0xec,0x240,0x3e9,0x2,0x50,0x58,0x9d,0x149,0x301,0x34c,0x376,0x140,0x3df,0x93,0x2fd,0x1aa,0x389,0x3b6,0xa,0x34,0xb2,0xd0,0x215,0x287,0x3d4,0x3fb,0x2b,0x394,0x11,0x14f,0xc4,0x125,0x2b8,0x9d,0x1bc,0x1e5,0x2ba,0x3a0,0x3b4,0x3c9,0x3e4,0x328,0x3d7,0x25d,0x2bd,0x8,0xff,0x22e,0x57,0x5e,0x10a,0x18c,0x2ca,0x328,0x329,0x339,0x56,0x397,0x2f9,0x2ff,0x69,0x2b0,0x2b8,0x6,0x177,0x1ce,0x236,0x255, + 0x25e,0x25f,0x32e,0x11b,0x3fe,0xdb,0xf1,0x13a,0x1bc,0x2fb,0x36,0x3d,0x1ac,0x1f6,0x2c9,0x2db,0x34e,0x376,0x41,0x3c8,0x10d,0x1bb,0x4f,0x112,0x328,0x77,0xaf,0xfa,0x123,0x27c,0x3c0,0x3f0,0x3fa,0x278,0x2eb,0x18f,0x2e5,0xe,0x211,0x3ca,0x9,0x62,0xfc,0x115,0x1aa,0x29a,0x3bb,0x3e8,0x1b4,0x201,0x63,0xab,0x262,0x2d2,0x313,0x3,0x5,0x20,0x189,0x204,0x272,0x30e,0x3dc,0xd2,0x2c7,0x13f,0x227,0x108,0x203,0x34c,0x10,0x30,0xe1,0x113,0x192,0x243,0x318,0x360,0x105,0x390,0x5d,0x2f5,0xa4,0x1d3,0x358,0xe0,0x11d,0x159,0x236,0x2c8,0x312,0x3e4,0x3f5,0xbe,0x335,0x2bf,0x321,0x35,0x80,0x3c6, + 0x47,0x164,0x193,0x1dc,0x21d,0x2b8,0x342,0x36c,0x42,0x1ab,0x71,0xcd,0x6d,0x326,0x3c8,0xf,0x44,0x89,0x19c,0x1de,0x232,0x263,0x3c8,0x29,0x16a,0x1a9,0x29d,0x1b,0x1ea,0x3ec,0xaf,0xda,0xec,0x1b0,0x1d5,0x1e4,0x2b3,0x378,0x37c,0x3a3,0x127,0x37d,0x6b,0x152,0x2c8,0x112,0x126,0x14e,0x1ef,0x2f2,0x324,0x36b,0x3a9,0x252,0x39d,0x11b,0x203,0x5e,0x201,0x392,0x38,0x14f,0x188,0x1cc,0x1f1,0x21a,0x30e,0x379,0x162,0x2e9,0x2bb,0x3ed,0x28,0xd7,0x132,0x75,0x13d,0x1ee,0x284,0x28b,0x2b8,0x36e,0x37a,0x37,0x256,0x49,0x19f,0x225,0x2e8,0x38e,0x1a4,0x227,0x286,0x370,0x37f,0x3ca,0x3d3,0x3d6,0xa0,0x2d7, + 0x2b,0x273,0x1ae,0x34b,0x368,0x51,0x90,0x1c4,0x1ce,0x21e,0x251,0x2fc,0x383,0x117,0x320,0x11f,0x1b5,0x1b4,0x27d,0x2a0,0x6d,0x11e,0x1b4,0x231,0x2be,0x32b,0x34c,0x3d4,0x175,0x1ea,0x23b,0x311,0x1a6,0x2b5,0x3be,0x8b,0x130,0x284,0x2c6,0x2d4,0x372,0x395,0x3af,0x20c,0x281,0x4f,0x5d,0x36,0x304,0x3f1,0x5e,0x88,0xb4,0xee,0x101,0x10d,0x323,0x3a6,0x133,0x3c2,0x163,0x2c3,0xfa,0x2df,0x2e6,0x5a,0x6e,0x12c,0x18d,0x1b8,0x24c,0x371,0x3e9,0x78,0x253,0x255,0x27d,0x7,0x2c6,0x33c,0x52,0x125,0x203,0x262,0x2a2,0x2bb,0x362,0x3ba,0x2fd,0x3bc,0xff,0x36b,0x1a6,0x1bf,0x3aa,0x1f,0x27,0x94,0xc4,0x100, + 0x21c,0x267,0x3f2,0x91,0x392,0x2df,0x319,0x6,0x1c3,0x206,0x39,0x48,0x70,0x112,0x1e4,0x1f3,0x244,0x2f5,0x2f2,0x377,0xa1,0x387,0xb2,0x177,0x3b8,0x89,0x12f,0x1c2,0x262,0x2d6,0x2e0,0x344,0x3ad,0x38,0x109,0x3d,0x33b,0x78,0x33d,0x3ca,0xb5,0xbe,0xe0,0xf7,0x13e,0x16e,0x2dc,0x343,0x71,0x136,0x121,0x339,0x252,0x280,0x39f,0x86,0xa4,0xc6,0x1d0,0x35a,0x35b,0x385,0x3a3,0x8e,0x1e9,0xfd,0x16f,0x23e,0x275,0x2fc,0x103,0x155,0x15a,0x258,0x2be,0x2d5,0x37e,0x386,0x20f,0x2ec,0x2b9,0x377,0x2c,0xc9,0x390,0x4c,0x59,0x13c,0x16e,0x17f,0x18b,0x1a0,0x3f2,0xf6,0x2f3,0xd9,0x145,0x3c,0xe1,0x2be, + 0x82,0x83,0x172,0x1fe,0x223,0x23a,0x24f,0x3a0,0xb,0x340,0x35,0x28f,0xd9,0x1b2,0x31c,0x11,0x64,0xf2,0x215,0x222,0x36e,0x3c5,0x3ee,0x128,0x3ad,0x3,0x243,0x22,0x194,0x1a7,0x5,0x2a,0x4b,0xa6,0x118,0x1fe,0x200,0x3f1,0x1bc,0x357,0x155,0x1e5,0x16e,0x1bd,0x1e0,0x37,0x4b,0x116,0x11e,0x148,0x1cb,0x324,0x3d8,0x2ff,0x3c6,0x7d,0x87,0xfd,0x14c,0x172,0x4f,0x142,0x186,0x1a6,0x214,0x273,0x335,0x3c4,0x2fa,0x337,0x3,0x247,0x9d,0x2cc,0x310,0x114,0x16d,0x1bf,0x1f2,0x208,0x2d7,0x350,0x37a,0x325,0x3e0,0x137,0x19b,0x222,0x2b7,0x370,0x54,0x132,0x239,0x2da,0x2f6,0x305,0x33f,0x362,0x239,0x2d4, + 0x29,0x1a7,0x285,0x2ea,0x378,0x53,0x8e,0x146,0x22e,0x2bc,0x2f9,0x385,0x3b4,0x31e,0x323,0xaf,0x3f9,0x19a,0x393,0x3fe,0x60,0xd6,0x1cb,0x242,0x2bc,0x332,0x33d,0x3c7,0x2c0,0x3b3,0x345,0x349,0x161,0x23a,0x3c0,0x3c,0x6e,0xbd,0xd6,0x146,0x2fd,0x334,0x3a1,0x22,0x3db,0x39,0x31b,0xa8,0x267,0x3c2,0xcd,0x11a,0x166,0x1fc,0x29f,0x2b4,0x3de,0x3ef,0x0,0x375,0x20f,0x299,0x1b0,0x24a,0x37d,0xa3,0xa7,0x142,0x206,0x20b,0x27e,0x374,0x3e8,0xb7,0x228,0x15f,0x201,0x6f,0x1e2,0x2ea,0x8f,0xa8,0x1ef,0x208,0x2d9,0x340,0x3a0,0x3ac,0x1c3,0x3ca,0x1cb,0x1f9,0x77,0xe2,0x260,0x73,0xb0,0xc0,0xde,0x26a, + 0x26d,0x2cb,0x356,0xd0,0x3e7,0x35d,0x3ab,0x2ed,0x398,0x3e2,0x43,0x6c,0x111,0x178,0x198,0x2a9,0x2be,0x344,0x4f,0x3d6,0xb,0x25f,0x7a,0x2ab,0x3de,0x15f,0x19b,0x234,0x25a,0x2a0,0x2ff,0x38e,0x3a8,0x17,0x1b6,0x185,0x27d,0x9,0x182,0x284,0xdb,0x120,0x1df,0x230,0x250,0x252,0x336,0x37d,0x16d,0x37e,0x3b9,0x3c7,0x44,0x1a0,0x3ab,0xac,0xd8,0xeb,0x1bc,0x237,0x2da,0x2f4,0x32f,0x1c0,0x34f,0xbf,0x10f,0x25,0xca,0x3d4,0xc,0xc5,0x167,0x18a,0x2d1,0x322,0x32c,0x3b8,0x15c,0x33b,0x97,0x293,0x1f,0x34,0x2d4,0xe2,0xf3,0x27e,0x292,0x2fe,0x309,0x325,0x3fe,0x3b,0x39e,0x11f,0x20d,0x119,0x152,0x34a, + 0x7c,0x108,0x11e,0x1ad,0x2b6,0x2c5,0x303,0x31a,0x33d,0x35a,0x23f,0x3f3,0x26c,0x2d7,0x3a2,0x7,0x14a,0x1b0,0x1bd,0x288,0x398,0x3be,0x3f3,0x25d,0x364,0xf,0x1f,0x118,0x17e,0x21b,0x11b,0x152,0x1ca,0x261,0x280,0x296,0x29c,0x2cb,0xd8,0x28f,0x355,0x391,0x236,0x2d6,0x2eb,0x8a,0xc4,0x16e,0x268,0x2c3,0x36f,0x38a,0x3b7,0x1e1,0x30e,0x65,0x247,0x1ca,0x20f,0x300,0x30,0x7d,0x15d,0x1b6,0x1d4,0x1f8,0x201,0x388,0x2d,0x3a2,0x1fb,0x215,0x164,0x1c4,0x1d1,0x40,0x7f,0x85,0xa8,0xd8,0x266,0x2a3,0x3ce,0x130,0x257,0x91,0xd5,0x55,0x164,0x306,0x70,0xeb,0x13f,0x17f,0x25e,0x282,0x30c,0x366,0x30d,0x3d0, + 0x12d,0x1d1,0x76,0x23c,0x25f,0x13b,0x1a6,0x1b5,0x224,0x266,0x2f3,0x30a,0x366,0x60,0x383,0x61,0x2f9,0x208,0x239,0x3e0,0x2f,0x34,0x1fa,0x23e,0x2c8,0x2da,0x365,0x3e5,0x2a8,0x3dd,0x145,0x213,0xe,0x68,0x87,0xf8,0x207,0x2d8,0x2e9,0x2ea,0x318,0x398,0x3c7,0x5c,0x185,0xc7,0x1b7,0x96,0xb7,0x114,0x2b,0x11b,0x19c,0x286,0x2da,0x338,0x3a3,0x3de,0x2ba,0x391,0x373,0x3f9,0x64,0x97,0x1d6,0x12,0xd4,0x133,0x140,0x20a,0x23f,0x2e8,0x389,0x127,0x35e,0x5f,0x399,0x94,0x1ef,0x292,0x41,0x114,0x234,0x241,0x282,0x2f4,0x3a3,0x3ee,0xcf,0x24c,0x25,0x1ef,0xfc,0x1b5,0x380,0x79,0xfe,0x19c,0x1cf,0x274, + 0x37b,0x38a,0x3a6,0x35,0x3b0,0x1c5,0x363,0xf7,0x2e0,0x32c,0xa8,0x191,0x288,0x2dd,0x2de,0x36e,0x387,0x3ae,0x7a,0x343,0xef,0x2d1,0x9f,0xbc,0x37e,0xc8,0x140,0x1ce,0x29f,0x2ac,0x322,0x387,0x3e5,0x1a6,0x363,0x7,0x2d9,0x73,0x16e,0x3e8,0x54,0xf2,0x1cd,0x220,0x240,0x28d,0x350,0x3fd,0x16f,0x240,0x131,0x3d9,0x40,0x29a,0x2b9,0x90,0x1d4,0x1f3,0x29d,0x2a3,0x2b2,0x334,0x38a,0x13c,0x309,0x38b,0x3cf,0x47,0x326,0x3fe,0x31,0x56,0x6a,0x14a,0x1aa,0x1db,0x1fb,0x32a,0xed,0x17e,0x73,0x20d,0x70,0x1aa,0x2bd,0xf9,0x1ac,0x1b4,0x1b6,0x1f0,0x1f7,0x316,0x3cd,0x151,0x206,0x9,0x11d,0x186,0x269,0x3f8, + 0x4f,0xf0,0xf4,0x127,0x14a,0x25c,0x308,0x3dd,0x242,0x3b7,0xbd,0x2b5,0xb9,0x10a,0x22c,0xc,0xa0,0x12a,0x152,0x15b,0x21c,0x2d3,0x33b,0x3e,0xef,0x169,0x179,0x38,0x180,0x2a1,0x2c,0x8a,0x90,0x152,0x197,0x3a1,0x3ac,0x3df,0x115,0x37a,0x37,0x83,0x49,0xb4,0x232,0x7f,0x178,0x2a0,0x2df,0x35d,0x366,0x3b2,0x3c0,0x2b3,0x3e6,0x2e9,0x395,0x30,0xf4,0x151,0x43,0x8c,0xac,0xad,0xe6,0xe8,0x2c7,0x368,0xb1,0x3fc,0xd,0x3a1,0xa4,0x351,0x3f4,0x67,0x257,0x272,0x2fc,0x302,0x32d,0x34c,0x3e8,0x132,0x3c5,0x59,0x231,0x1c9,0x27a,0x2ca,0x39,0x8c,0x128,0x198,0x1c8,0x1f7,0x295,0x3c4,0x2c8,0x37b, + 0x31,0x3a5,0xba,0x1ab,0x216,0x2c,0x33,0x3c,0xff,0x338,0x36e,0x384,0x3e7,0x2e2,0x351,0x107,0x3f3,0xa,0x355,0x392,0x12d,0x130,0x173,0x1ea,0x33a,0x356,0x38a,0x3cf,0x2dd,0x3b6,0xa9,0xdf,0x124,0x28e,0x2a9,0x3e,0xea,0x20d,0x248,0x2ba,0x2ff,0x320,0x3cd,0xff,0x378,0x24d,0x381,0x127,0x246,0x31a,0x9a,0xf5,0x13e,0x18b,0x22e,0x31e,0x359,0x3be,0x230,0x3a7,0x225,0x2f7,0xd2,0x2ec,0x3f5,0xb8,0xe5,0xe8,0x116,0x201,0x2bb,0x35a,0x3a2,0x8f,0x148,0x173,0x259,0x16a,0x365,0x36e,0x5c,0x7b,0x8b,0x1dd,0x2a6,0x2c2,0x2c4,0x3cc,0x11d,0x356,0xbf,0x223,0x2e,0x1bc,0x337,0x11,0x4a,0xaa,0x12a,0x196, + 0x209,0x3b6,0x3f3,0x49,0x322,0x2b,0xb3,0x21e,0x22e,0x387,0x91,0xae,0x181,0x1bc,0x2b5,0x340,0x3da,0x3fa,0xa9,0x2b2,0x95,0x389,0x14e,0x150,0x249,0xe6,0x148,0x161,0x192,0x29d,0x2d6,0x374,0x3c1,0x1c4,0x39f,0x95,0x2ef,0x31,0x1d6,0x31c,0x3f,0x150,0x1d3,0x1e2,0x25c,0x2ae,0x3a7,0x3f0,0x2dc,0x379,0x1b5,0x2eb,0x1a,0x11f,0x2ae,0x6d,0xc3,0x106,0x17a,0x242,0x2a2,0x32a,0x35b,0xac,0x215,0x25b,0x36b,0x27b,0x28a,0x294,0x12,0x1a,0xf5,0x1bc,0x1d9,0x23c,0x34b,0x35e,0x2fb,0x38e,0xdd,0x139,0x238,0x2f6,0x3d9,0x108,0x10d,0x13e,0x15d,0x2ee,0x381,0x3b4,0x3ea,0x1f3,0x3f0,0x73,0x195,0x86,0x88,0x321, + 0x2,0xb,0x2c,0x57,0x188,0x1fa,0x300,0x389,0x1a5,0x1ce,0x15f,0x2b9,0x96,0x1e9,0x34c,0x3c,0x88,0x179,0x1a5,0x25c,0x2b8,0x32b,0x382,0x345,0x352,0x7f,0xb5,0xb2,0xe4,0x183,0x2a,0x32,0xe2,0xe9,0x1c4,0x23d,0x24d,0x29e,0x265,0x32e,0x1d,0x1cd,0x20a,0x358,0x38b,0x5d,0xc3,0x154,0x21a,0x236,0x27b,0x2d0,0x3f0,0x18b,0x272,0xeb,0x219,0x19e,0x252,0x2d5,0x22,0x1f2,0x210,0x21d,0x28b,0x290,0x293,0x304,0xc8,0x3f3,0x197,0x3d1,0x16,0x11e,0x257,0x53,0xa7,0x11e,0x174,0x244,0x260,0x2aa,0x2f7,0xf5,0x208,0x30f,0x383,0x100,0x14f,0x27a,0xe9,0x116,0x182,0x186,0x1c8,0x255,0x259,0x37e,0x290,0x2ed, + 0x1c5,0x347,0xcc,0x11c,0x24b,0x2f,0x37,0xb4,0xe4,0x18e,0x329,0x336,0x35e,0x1fb,0x3ea,0x119,0x27f,0xbe,0x272,0x3a7,0x5b,0x106,0x199,0x19a,0x21f,0x28c,0x2ea,0x354,0x177,0x1e4,0x7,0x29b,0xf6,0x289,0x300,0xb2,0xf2,0x19b,0x1dc,0x254,0x25b,0x35c,0x3e1,0x380,0x3f5,0xc9,0x143,0x153,0x38a,0x3ac,0x70,0xb1,0x168,0x286,0x29e,0x316,0x32d,0x3d5,0x6b,0x296,0x1f1,0x3a7,0x51,0x37e,0x3f8,0x13,0x110,0x1a4,0x1c4,0x221,0x30a,0x3b9,0x3f0,0x3af,0x3ce,0x41,0x9b,0xac,0x247,0x342,0x1c,0x72,0x10e,0x112,0x20c,0x2ab,0x2bf,0x2d3,0x229,0x384,0x171,0x231,0x7c,0x3ba,0x3d3,0x2a,0x77,0xcf,0x127,0x1d6, + 0x20c,0x36e,0x3da,0xe2,0x381,0x1a3,0x277,0x212,0x36f,0x3b0,0x58,0xbc,0xde,0x16c,0x1f5,0x228,0x361,0x3b7,0x17d,0x336,0x6b,0x25d,0x1b6,0x23b,0x23e,0x1f,0x47,0x60,0x14c,0x1f4,0x265,0x28a,0x2a8,0x4a,0x247,0x4d,0xd7,0x43,0x1fc,0x2c6,0x177,0x20e,0x21c,0x258,0x30f,0x312,0x3b5,0x3e2,0x65,0x234,0x39b,0x3b1,0x0,0xdc,0x12d,0xf,0x5c,0x268,0x288,0x29c,0x2d7,0x308,0x361,0x119,0x26c,0x229,0x3df,0x32,0x28c,0x2bf,0x17,0x98,0xe7,0x143,0x1b0,0x244,0x3d8,0x3e8,0x27e,0x2c5,0x169,0x24f,0x3c,0x30b,0x320,0x254,0x304,0x375,0x384,0x39c,0x3a5,0x3a8,0x3b1,0x79,0x25c,0x26b,0x3e5,0x143,0x15e,0x286, + 0x3e,0xb2,0x1ae,0x201,0x208,0x279,0x27c,0x3a5,0x28c,0x395,0x33,0x291,0x173,0x17c,0x196,0x30,0x31,0x72,0x1ec,0x24a,0x2dc,0x2ef,0x3c3,0x99,0x292,0xe5,0x2ed,0x6e,0x306,0x379,0x36,0xc6,0xd3,0x135,0x2c0,0x2c7,0x2fa,0x3a2,0x2b5,0x306,0x1,0x1af,0xf2,0x171,0x3d8,0x4,0x3b,0x95,0xa6,0x1d2,0x1d3,0x252,0x3be,0xa5,0x2a4,0x21b,0x2c3,0x15,0x2f4,0x348,0x34,0xd6,0x111,0x16a,0x26d,0x26f,0x304,0x3d0,0xe4,0x2e5,0x2cd,0x3c9,0xde,0x237,0x3ae,0x1d,0x48,0x84,0x92,0x2a6,0x2ca,0x39b,0x3a7,0x30c,0x333,0x22f,0x32b,0x50,0x65,0x29c,0x14,0x12d,0x138,0x1fa,0x254,0x279,0x2fa,0x301,0x374,0x3c7, + 0x71,0x30d,0xea,0x113,0x39a,0xb8,0x109,0x12e,0x194,0x1f6,0x20f,0x326,0x371,0x1aa,0x1d1,0x187,0x3a7,0x1f9,0x268,0x3b8,0x38,0x62,0xfe,0x1e0,0x2b0,0x2cf,0x315,0x3eb,0x178,0x3d9,0xd3,0x18b,0x134,0x2dd,0x336,0xc7,0x13e,0x1d9,0x25a,0x2b6,0x2b7,0x37e,0x3f2,0xd5,0x11c,0x79,0x39f,0xdc,0x139,0x1de,0x2f,0x33,0x3a,0x40,0x66,0x192,0x1f4,0x3c5,0x311,0x34a,0x1d7,0x233,0xcf,0x158,0x266,0x24,0x7e,0xa5,0xcb,0x222,0x247,0x2ac,0x328,0x43,0x1a0,0x9d,0x345,0x3b,0x98,0x276,0x100,0x14c,0x1be,0x24b,0x25e,0x28f,0x358,0x36d,0xa6,0x26f,0x22d,0x24f,0x57,0x138,0x2ec,0x7c,0xec,0x1a9,0x1be,0x1e8, + 0x22c,0x363,0x3f1,0x10,0xbf,0x77,0x239,0xd0,0x1c7,0x244,0x2b,0xcc,0x12c,0x168,0x1fd,0x2f6,0x3d3,0x3ec,0xc5,0x12a,0x85,0x3c1,0x1ac,0x2de,0x329,0xde,0x14c,0x171,0x182,0x1c0,0x1d4,0x1eb,0x3b3,0x54,0xbd,0x2ff,0x341,0x2ba,0x361,0x39e,0x15,0x68,0x13c,0x1a5,0x250,0x29e,0x345,0x3b2,0x172,0x24b,0x16d,0x34b,0x5b,0x9c,0x248,0x8,0x49,0x66,0x70,0xae,0x2ee,0x2ef,0x3bb,0x111,0x20a,0x289,0x327,0x29,0x100,0x2c2,0x30,0x1d8,0x218,0x232,0x277,0x31a,0x33f,0x357,0x1e8,0x285,0x19b,0x1b9,0x250,0x308,0x341,0x51,0x62,0x144,0x23f,0x290,0x2fb,0x3ec,0x3f6,0x223,0x288,0x1,0x18b,0x178,0x1e4,0x2ef, + 0xfb,0x16f,0x176,0x21a,0x24c,0x341,0x366,0x3f8,0x246,0x3bd,0x2a3,0x2bb,0x18b,0x294,0x2b6,0x74,0x1a1,0x1c8,0x1d9,0x248,0x262,0x2f7,0x3a8,0x143,0x170,0x109,0x39d,0xd,0x50,0x28a,0x52,0x6a,0x114,0x14f,0x1c7,0x1e8,0x22d,0x256,0x88,0x1e5,0x14d,0x2e1,0x1c5,0x1d2,0x386,0x62,0x88,0x145,0x158,0x15b,0x34c,0x36b,0x3a4,0x2f,0x3ac,0x189,0x307,0xbc,0xbf,0x310,0x82,0x98,0xbc,0x209,0x23b,0x285,0x33e,0x3ba,0x262,0x32b,0x23b,0x33f,0xca,0x259,0x290,0x76,0x86,0x93,0x10a,0x1da,0x2b6,0x30d,0x3af,0x1be,0x2f9,0x9f,0x1dd,0x2be,0x30f,0x35a,0x28,0xb4,0xc2,0x107,0x14c,0x17d,0x1a3,0x2f6,0xf,0x36c, + 0x17,0x317,0xef,0x2a6,0x30a,0x76,0x19a,0x1a0,0x259,0x27f,0x2c7,0x32c,0x390,0x216,0x341,0x37,0x8d,0xcc,0x22a,0x38d,0x19,0x36,0xb0,0x175,0x17a,0x1ef,0x1fc,0x382,0x1fa,0x203,0x49,0x26d,0x10b,0x356,0x3d6,0xc,0x20,0x26,0xef,0xf1,0x144,0x260,0x3d1,0x220,0x3f1,0x1d3,0x3bb,0x7e,0x11a,0x32f,0x54,0x63,0xa8,0x197,0x299,0x316,0x3c4,0x3c6,0x77,0xcc,0x53,0x8b,0x1db,0x212,0x2a0,0xfc,0x166,0x19a,0x242,0x26f,0x29b,0x3bd,0x3f6,0x3d,0xe6,0x135,0x38d,0x109,0x2a2,0x2fe,0x10,0xdc,0x104,0x11d,0x166,0x218,0x27d,0x395,0xfc,0x15d,0x81,0x8d,0x16c,0x23f,0x2f4,0x8e,0x121,0x180,0x1a0,0x203, + 0x2fe,0x31b,0x3d6,0x1ae,0x371,0x22f,0x27f,0x1e4,0x2ad,0x3d6,0x1e,0x22,0xf1,0x161,0x2de,0x2f7,0x38c,0x3e4,0x1b0,0x1cb,0x291,0x32f,0x30,0x1c0,0x265,0x2e,0x8d,0xb9,0x15f,0x1c0,0x226,0x29a,0x2e8,0x5,0x6a,0x2d,0x3c5,0x174,0x2c4,0x2cf,0x10,0x14,0x9f,0x11c,0x1d4,0x22e,0x34d,0x38f,0x138,0x1ef,0x367,0x38f,0x140,0x29d,0x2de,0x29,0x2e,0xfc,0x109,0x185,0x2f8,0x310,0x3be,0xb9,0x164,0x2e7,0x375,0x1e,0x62,0xeb,0x42,0xa6,0xd1,0x119,0x210,0x39e,0x3e3,0x3f6,0xaf,0x1fc,0x2db,0x38f,0xe8,0x12a,0x317,0x7d,0x12c,0x26b,0x31a,0x331,0x332,0x380,0x3ca,0x1a4,0x255,0x2a3,0x36f,0xae,0xc5,0x24c, + 0x94,0xd1,0x144,0x1e6,0x2ad,0x2d9,0x33a,0x3c0,0x160,0x2af,0x26b,0x35f,0x24,0x1bb,0x3f6,0x6,0x1a,0x9f,0x156,0x1be,0x1d0,0x213,0x36f,0x44,0x367,0xf,0x33f,0x346,0x388,0x3f9,0x7,0x72,0x174,0x18d,0x1c3,0x270,0x2e8,0x3d4,0x97,0x2ca,0xa9,0x101,0x140,0x169,0x184,0x4a,0xc2,0x11f,0x22d,0x238,0x2b1,0x2d0,0x384,0x59,0x192,0x31b,0x375,0x2c7,0x336,0x370,0x7a,0xca,0xce,0x147,0x173,0x184,0x321,0x3d8,0x2f1,0x2f6,0x1a5,0x1f3,0x1b9,0x2d4,0x338,0x10,0xee,0x12a,0x24d,0x252,0x269,0x296,0x2e3,0x1f5,0x3de,0x287,0x305,0x91,0x1f0,0x220,0x12,0x130,0x1c7,0x266,0x26f,0x31d,0x354,0x3fa,0x80,0x1eb, + 0x1bb,0x3b5,0x8e,0x2e3,0x38c,0x7d,0x134,0x18a,0x1ec,0x2ae,0x2c1,0x2c6,0x367,0x30f,0x39a,0x1eb,0x20b,0x107,0x1f2,0x2f8,0xf,0x8e,0x11f,0x141,0x18e,0x1ca,0x39e,0x3d4,0xee,0x145,0x35,0x2b1,0x18e,0x1dd,0x21e,0x4c,0x60,0x1a6,0x1aa,0x1eb,0x233,0x2d1,0x350,0x90,0x2bb,0x65,0x177,0xc6,0x17b,0x25c,0x46,0xce,0x1a4,0x219,0x228,0x2ce,0x3e3,0x3e5,0x1e0,0x355,0xe7,0x1d9,0x5f,0x382,0x3a4,0x18,0x36,0x252,0x26e,0x281,0x2f5,0x3ba,0x3ef,0x129,0x388,0x1fb,0x209,0x20e,0x253,0x360,0xc4,0x151,0x155,0x21a,0x243,0x2aa,0x306,0x316,0x9a,0x29f,0x361,0x3d7,0xcd,0x1a8,0x290,0x5d,0xbf,0x18a,0x200,0x204, + 0x245,0x2a8,0x384,0x123,0x3a0,0x31,0x1e9,0x13f,0x188,0x1a0,0x38,0x127,0x228,0x2bf,0x2ed,0x33e,0x352,0x3e4,0x1c6,0x3cb,0xe1,0x2d7,0xfb,0x1cc,0x350,0xba,0x18b,0x214,0x26e,0x298,0x2bc,0x3cd,0x3db,0x4e,0x1e7,0x217,0x35b,0x105,0x29a,0x32a,0xb,0x35,0x6e,0xf6,0x16c,0x2e4,0x320,0x333,0x8b,0x31a,0x1b7,0x3cf,0x17f,0x298,0x3ea,0x1b,0x21,0x142,0x1de,0x296,0x2a8,0x35c,0x39f,0xc9,0x104,0x5b,0x2e9,0x72,0x25e,0x26b,0x48,0x19a,0x1e5,0x2b8,0x2bd,0x350,0x378,0x3e1,0xb4,0x139,0xd1,0xfb,0xda,0x272,0x279,0xcc,0x10c,0x122,0x138,0x14e,0x1b9,0x307,0x3f9,0x12c,0x317,0x61,0x19f,0x95,0x308,0x3f4, + 0x59,0x80,0x86,0x10b,0x23a,0x2a4,0x2ab,0x318,0x16b,0x3ba,0x21d,0x313,0x6c,0x2e6,0x353,0x1,0x4a,0x77,0xbe,0xdc,0x12b,0x1b8,0x386,0x68,0xd1,0x221,0x34b,0x2b,0x68,0x6c,0xb,0x18,0x58,0x6b,0x15e,0x16e,0x194,0x3c9,0xbb,0xce,0x119,0x185,0xea,0x1c2,0x343,0x8e,0x90,0x195,0x1a7,0x1b8,0x2a2,0x2d2,0x309,0x20e,0x2d5,0x355,0x373,0xec,0x223,0x314,0x60,0xd7,0xd9,0x118,0x19c,0x2c0,0x2e6,0x3d1,0x28b,0x2a2,0x269,0x333,0x1d,0x126,0x3e2,0x42,0x75,0xd2,0xee,0x128,0x354,0x387,0x3af,0x1cc,0x3d5,0x34f,0x3c3,0x144,0x302,0x3c3,0xc,0x72,0x114,0x198,0x220,0x25d,0x27b,0x337,0x15b,0x368, + 0x23d,0x329,0xf2,0x305,0x3cc,0x24,0x48,0x7b,0x1b1,0x2a2,0x2a9,0x322,0x3e0,0x2a9,0x3f2,0xa7,0xd1,0x194,0x1c0,0x2c3,0x21,0x9c,0xb5,0x180,0x1b0,0x272,0x310,0x34d,0x53,0x314,0x1ef,0x203,0x1af,0x1ba,0x22c,0x4,0xf8,0x13c,0x16c,0x182,0x1f9,0x31b,0x38b,0x14,0x1a1,0x117,0x183,0x160,0x163,0x296,0x68,0x103,0x194,0x256,0x28e,0x319,0x3b8,0x3f3,0xf8,0x217,0x67,0xab,0xfe,0x1b0,0x221,0x94,0xb3,0xf0,0x11d,0x15a,0x1f8,0x32f,0x3ac,0x222,0x3eb,0x147,0x1bf,0xc4,0x157,0x2c4,0x38,0x61,0x1e7,0x210,0x266,0x278,0x295,0x3ea,0x1c8,0x20b,0x13b,0x26d,0x24,0x162,0x2a5,0x61,0x79,0x213,0x2cc,0x2fe, + 0x37a,0x39c,0x3ca,0x1a,0x31d,0x85,0x3f7,0x10,0xcb,0x188,0x85,0x98,0x15e,0x1b4,0x1ca,0x203,0x2ac,0x32f,0x31f,0x3b2,0x181,0x295,0x17,0x156,0x26e,0x4,0xbe,0xc9,0x124,0x214,0x22c,0x2b1,0x327,0x36,0x34b,0x303,0x3b7,0x17a,0x224,0x385,0x1e,0xea,0xed,0x115,0x1fe,0x275,0x32a,0x370,0x3c,0x6f,0x2d1,0x3fb,0xae,0x2af,0x364,0x108,0x149,0x14a,0x14d,0x21d,0x2de,0x3d2,0x3f8,0x165,0x23e,0x191,0x2c1,0xaa,0x195,0x1ea,0x2a,0x81,0x8a,0x178,0x1af,0x273,0x2b4,0x36a,0x1d9,0x358,0x133,0x1cf,0xaf,0x260,0x2c0,0x42,0x180,0x183,0x1cc,0x202,0x215,0x2f1,0x360,0x1d3,0x27a,0x34d,0x3f5,0x23,0x258,0x3da, + 0x36,0x132,0x250,0x2a1,0x2b9,0x2d8,0x2ec,0x347,0x1e6,0x235,0x2f7,0x2fb,0x58,0x330,0x383,0x2d,0x91,0x121,0x1ca,0x1e8,0x2c8,0x32e,0x35e,0x40,0x2ab,0x115,0x1e3,0x2ce,0x2dc,0x3df,0x6f,0xa1,0xf4,0x274,0x307,0x318,0x37c,0x3fc,0x236,0x3a9,0x21,0x1f7,0x3a,0x1ed,0x3ce,0x94,0x9e,0x1b3,0x29e,0x2bf,0x2d4,0x2df,0x35e,0x2a3,0x3aa,0x57,0x255,0x9b,0xe6,0x312,0x8d,0x10c,0x151,0x212,0x23e,0x246,0x26c,0x31b,0x18f,0x2de,0x97,0x2dd,0x90,0xe5,0x142,0x29,0x54,0x64,0xac,0xd2,0x23f,0x2f1,0x304,0x250,0x32f,0x239,0x2b3,0xdb,0x19a,0x2d8,0x1b,0x80,0xee,0x117,0x13b,0x2fe,0x394,0x3e8,0x174,0x313, + 0x135,0x265,0xdd,0x270,0x2a8,0x1e,0xe2,0x1bb,0x29b,0x358,0x3b8,0x3c5,0x3e0,0x7,0xae,0x319,0x33d,0x137,0x3cc,0x3e6,0x10e,0x120,0x147,0x204,0x20d,0x297,0x306,0x362,0xe7,0x1de,0x35f,0x3fd,0x18d,0x2ee,0x3dc,0x4b,0x114,0x162,0x1a8,0x1de,0x260,0x2cd,0x365,0x1a8,0x29b,0x89,0x9f,0x74,0x90,0x27f,0x0,0x3a,0x137,0x278,0x313,0x332,0x397,0x3e2,0x231,0x2bc,0x157,0x389,0xee,0x18c,0x19b,0xe,0x5c,0xa1,0x1a7,0x206,0x260,0x276,0x2bd,0x73,0x146,0x1ed,0x2d9,0x232,0x2c9,0x3ea,0x1c,0xd4,0x264,0x288,0x2be,0x317,0x325,0x363,0x219,0x326,0x13,0x34d,0x14d,0x1ce,0x256,0xb8,0x1b1,0x214,0x234,0x25f, + 0x276,0x2ab,0x33c,0x7e,0x1ed,0x2f,0x13d,0xd8,0x15b,0x2d6,0xc,0x17,0xb3,0x118,0x20a,0x375,0x388,0x3dc,0x163,0x2b8,0x77,0x1cf,0x115,0x2e0,0x328,0x45,0xd8,0x11e,0x25b,0x322,0x349,0x38c,0x3a6,0x188,0x2f5,0x4d,0x22d,0x66,0x13c,0x1e3,0x56,0x8a,0x8f,0x102,0x253,0x298,0x2ea,0x2ed,0x10e,0x25b,0x1b1,0x1df,0x21f,0x2d0,0x318,0x23,0x7c,0x166,0x1b8,0x21f,0x227,0x3c4,0x3cc,0x213,0x370,0xe9,0x143,0x186,0x2f0,0x33b,0x8c,0xeb,0x158,0x20a,0x308,0x315,0x34b,0x3da,0xc,0x33,0x151,0x1d5,0x2c,0x155,0x166,0x89,0xb8,0x104,0x124,0x1a5,0x330,0x3ac,0x3ff,0x11f,0x338,0x15,0x1ff,0x63,0x262,0x34a, + 0x5b,0x84,0xbc,0x1db,0x1f2,0x233,0x2ee,0x39c,0x6c,0x193,0x14f,0x2cd,0x170,0x175,0x220,0x23,0xa2,0x134,0x179,0x256,0x2f4,0x368,0x36d,0x52,0x3e9,0x75,0x2bf,0x210,0x224,0x2d1,0x55,0xd5,0x1da,0x274,0x286,0x32c,0x354,0x3db,0x385,0x3c4,0x265,0x2c5,0x4e,0x273,0x3fc,0x78,0xe7,0x22f,0x247,0x298,0x29a,0x3d6,0x3e6,0x2cf,0x36e,0x23,0xa7,0xd6,0x167,0x1f6,0x40,0x13a,0x218,0x21b,0x27b,0x2e1,0x3ac,0x3d2,0x1dd,0x38c,0x16f,0x3cd,0x10d,0x13a,0x234,0x21,0xd3,0xe2,0x182,0x1d3,0x270,0x2b6,0x326,0xe8,0x3a5,0x28f,0x331,0x7a,0x9e,0x2a3,0xa,0x3e,0x118,0x1c6,0x1e3,0x25d,0x381,0x3ec,0x7c,0x327, + 0x1a5,0x3b3,0x2a4,0x31d,0x3f2,0x20,0x84,0xbb,0x19d,0x1a3,0x1b6,0x200,0x3ea,0xa8,0xad,0x279,0x3d7,0x2,0x1cd,0x2fa,0x12,0xa4,0x12a,0x16a,0x1a9,0x1cc,0x1fd,0x2db,0x321,0x33a,0x379,0x3fb,0x9a,0x229,0x2e8,0x44,0xcc,0xed,0x1b4,0x226,0x2a7,0x321,0x3fa,0x13f,0x3ae,0x1ab,0x21b,0x18e,0x1ba,0x3cb,0x6,0x37,0xf2,0x14c,0x1c4,0x27f,0x2e2,0x3cf,0x2c2,0x36d,0xd3,0x237,0x296,0x2a7,0x39e,0x66,0x7c,0x134,0x13a,0x1ae,0x1e9,0x253,0x34f,0x14a,0x271,0xa3,0x35b,0x3d,0xa2,0x14e,0xc2,0x1fc,0x205,0x229,0x246,0x2c2,0x2e5,0x3f2,0xb6,0x1ff,0x13f,0x2e5,0x13d,0x266,0x382,0x12,0x20,0x40,0x209,0x240, + 0x267,0x2c1,0x370,0x17b,0x2aa,0x18d,0x339,0xab,0xb8,0x108,0x27,0x46,0xf1,0x210,0x222,0x2e9,0x320,0x39a,0xe,0x2b1,0x153,0x361,0x22,0x18f,0x3de,0x18,0x3e,0x113,0x148,0x18c,0x1b9,0x22a,0x3a9,0x1a9,0x3d8,0x24b,0x2c7,0x200,0x344,0x3b5,0x58,0x180,0x1d2,0x1e2,0x237,0x33b,0x351,0x392,0xf2,0x2bf,0xff,0x227,0x230,0x323,0x3e8,0xe7,0x1ab,0x1d1,0x200,0x236,0x2a0,0x2ee,0x372,0x1b2,0x20d,0x24d,0x2b5,0x18,0x2d,0x37a,0x50,0x144,0x25b,0x264,0x292,0x315,0x33f,0x398,0x196,0x2a7,0x1fd,0x357,0x117,0x12e,0x176,0x71,0x118,0x217,0x23c,0x250,0x2a0,0x2eb,0x318,0x1f1,0x25a,0xed,0x317,0x136,0x2a8,0x399, + 0x2,0x2e,0xc1,0x140,0x167,0x230,0x309,0x3bc,0x225,0x324,0x17f,0x30b,0xa7,0x282,0x298,0xa,0x1ab,0x219,0x302,0x31a,0x38d,0x3ae,0x3fc,0x19,0x5a,0x15d,0x171,0x101,0x10a,0x340,0x26,0x12b,0x1b0,0x1ea,0x277,0x27a,0x291,0x2d2,0x244,0x361,0x28b,0x325,0xdf,0x146,0x286,0x9b,0xa5,0xd7,0x1ba,0x1d6,0x2ae,0x38c,0x3b0,0xc6,0x3d1,0x25,0x2a7,0x1c6,0x2b6,0x39d,0x14,0x1fb,0x20b,0x229,0x252,0x38c,0x39e,0x3da,0xd9,0x29a,0x229,0x3a9,0x1c,0xf1,0x20e,0x1c,0xbc,0xc5,0x110,0x1b7,0x25e,0x38d,0x3c2,0xc7,0x33e,0xc9,0x399,0xa,0x25a,0x3ff,0xd8,0x11a,0x156,0x223,0x264,0x2d1,0x2e6,0x39b,0x24d,0x396, + 0x111,0x393,0x27,0x24e,0x36e,0x84,0x1c9,0x1e8,0x210,0x284,0x2ce,0x3c3,0x3d9,0x18d,0x2f8,0x3b7,0x3d5,0x13e,0x347,0x394,0x96,0x10c,0x115,0x140,0x16f,0x1a8,0x1c5,0x1e8,0xd,0x13e,0x281,0x2a1,0x25a,0x2e9,0x30c,0x52,0x81,0xf9,0x12c,0x2a7,0x2bc,0x2d6,0x3de,0xfd,0x21a,0x1b,0x17b,0x380,0x3a3,0x3d4,0x83,0x174,0x17e,0x193,0x1f0,0x2a6,0x362,0x3df,0x94,0xd7,0x101,0x1e7,0x4,0x2f5,0x360,0x50,0x99,0x1b5,0x230,0x24c,0x25a,0x2e1,0x34a,0x1f,0x2e,0x21d,0x2b3,0x8b,0x126,0x14c,0x30,0x159,0x190,0x221,0x26a,0x274,0x33b,0x3cc,0x61,0x3ee,0x2a1,0x2fb,0x52,0x133,0x13c,0x22,0x6a,0x9f,0xbf,0x160, + 0x289,0x368,0x388,0x126,0x393,0x4b,0x141,0x13,0x21a,0x3e6,0x67,0x9c,0x1a2,0x1fa,0x1fb,0x256,0x34a,0x3f1,0x207,0x29c,0xbb,0xd9,0xda,0x10f,0x354,0x1f,0xc2,0x136,0x147,0x14b,0x198,0x2aa,0x3e0,0x2e1,0x32a,0x25f,0x377,0x46,0xad,0x118,0x9e,0x11c,0x1a3,0x20c,0x265,0x2c2,0x305,0x320,0x286,0x3b1,0x297,0x395,0x70,0x17d,0x2b4,0x6b,0x13d,0x1cc,0x1f6,0x208,0x242,0x294,0x353,0x4b,0x1f8,0x3f,0x41,0x116,0x159,0x278,0x69,0x10c,0x1dc,0x1ec,0x220,0x222,0x2d3,0x38f,0x7b,0x3ec,0x1c9,0x26f,0x1a8,0x277,0x384,0x9a,0x9c,0xda,0x19e,0x1b7,0x2b9,0x32e,0x3c1,0x144,0x283,0x263,0x321,0x246,0x291,0x2a6, + 0x23,0xd0,0xf6,0x126,0x17c,0x239,0x2e9,0x348,0xd3,0x1f4,0x28d,0x36d,0x21a,0x332,0x3e3,0xa,0x2c,0x47,0x122,0x1a1,0x206,0x365,0x376,0xf4,0xf7,0x1cb,0x309,0x196,0x261,0x304,0xba,0x16a,0x249,0x296,0x2b7,0x2be,0x2d0,0x3f5,0x83,0x14c,0xcf,0x3e7,0xf3,0x146,0x27c,0x4a,0xbf,0x102,0x235,0x23e,0x2a4,0x3a2,0x3fd,0x155,0x362,0x6d,0x2c1,0x190,0x213,0x322,0x4d,0x153,0x15a,0x1be,0x222,0x267,0x30c,0x3da,0x1c1,0x27c,0x385,0x3d9,0xd0,0xee,0x1fd,0xce,0xfd,0x1d8,0x2c6,0x2de,0x333,0x37f,0x392,0xf9,0x2ac,0x2b7,0x2cf,0x1b3,0x200,0x3a0,0x95,0xa8,0xcb,0x17e,0x1e0,0x23a,0x28e,0x3cb,0x1,0x3c0, + 0x305,0x3e1,0x10c,0x204,0x251,0x116,0x160,0x16f,0x183,0x26a,0x2d0,0x378,0x37b,0x18e,0x3bb,0x219,0x293,0x4e,0x11e,0x12f,0x13,0x10f,0x156,0x1ce,0x304,0x311,0x386,0x3cc,0x159,0x3a6,0x207,0x313,0x8d,0x13e,0x242,0x8c,0x154,0x25d,0x268,0x270,0x32c,0x359,0x3f9,0x261,0x346,0x1e1,0x22b,0x160,0x2a2,0x335,0x24,0x1f6,0x1f8,0x313,0x314,0x369,0x373,0x3fe,0x1b1,0x1ba,0x79,0x351,0xc7,0x214,0x3b6,0xce,0xcf,0x17a,0x27c,0x2a6,0x319,0x39e,0x3f7,0x23a,0x2b9,0x193,0x263,0x1f7,0x216,0x3a4,0x1e,0x7c,0x10b,0x26c,0x26e,0x291,0x393,0x3b6,0x2e8,0x3a1,0xed,0x3ef,0x16b,0x180,0x32e,0x18e,0x1b5,0x27c,0x29c,0x2e0, + 0x2fd,0x32a,0x357,0x16e,0x305,0xeb,0x3f1,0x46,0x147,0x352,0x6e,0x110,0x248,0x2bb,0x31e,0x324,0x325,0x3d1,0x2a,0x1c5,0x261,0x295,0x165,0x2b4,0x2f8,0x34,0x164,0x17a,0x18f,0x19f,0x1f3,0x234,0x2ac,0xa7,0x1ca,0x131,0x2a5,0xe3,0x338,0x3b2,0x16,0x78,0x12b,0x14b,0x20e,0x28a,0x2f0,0x379,0xe5,0x3fa,0x15d,0x3b9,0x20,0xd3,0x258,0x18,0x29,0x5a,0x10e,0x140,0x17c,0x32b,0x369,0x85,0x294,0x287,0x369,0x240,0x33f,0x3a2,0xc8,0x105,0x130,0x196,0x1a2,0x23c,0x30b,0x3dd,0x197,0x260,0x17d,0x3c5,0x45,0xe0,0x254,0x0,0x99,0xba,0x1c6,0x1e2,0x1eb,0x2ce,0x391,0x28e,0x315,0x99,0x341,0x1d4,0x256,0x3e5, + 0x9e,0x142,0x18f,0x19b,0x285,0x2dc,0x380,0x390,0x259,0x2c4,0x181,0x1e3,0x2a,0x15c,0x37b,0x82,0xf7,0x129,0x1b2,0x23d,0x262,0x2ba,0x3c2,0x248,0x3ed,0x285,0x39d,0x36,0x193,0x3a8,0xca,0x108,0x14e,0x1ae,0x224,0x2fb,0x337,0x3b3,0x15a,0x39b,0x38d,0x3ef,0x1c,0x2bc,0x2ff,0x26,0x88,0xe8,0x1bf,0x212,0x279,0x330,0x3bf,0x20,0x237,0x161,0x2c7,0x16c,0x28f,0x36a,0xe5,0x218,0x251,0x2ca,0x2ec,0x2f4,0x3aa,0x3d7,0x1a2,0x2d9,0x125,0x27b,0x24f,0x32e,0x35c,0x35,0x3a,0x4f,0x96,0x17c,0x1de,0x294,0x38f,0x2ad,0x38a,0x257,0x2c9,0x1b1,0x1b2,0x316,0x32,0x6f,0xfe,0x11f,0x1e7,0x282,0x364,0x3c0,0x7d,0x342, + 0x363,0x3c7,0x31f,0x324,0x3d2,0x74,0x131,0x18c,0x190,0x1cd,0x23d,0x338,0x3a2,0x194,0x2e3,0x20b,0x3ad,0xf,0x5e,0x2aa,0x16,0x25,0x32,0x13a,0x1ac,0x1da,0x2f3,0x3c7,0x258,0x35d,0x161,0x1d7,0x136,0x2cc,0x3f7,0x126,0x137,0x160,0x202,0x25f,0x2b2,0x2c9,0x31e,0xea,0x22b,0x117,0x2d5,0x4b,0x1d0,0x342,0x86,0x92,0xaa,0x104,0x1a0,0x1e9,0x30f,0x3ed,0x10f,0x266,0x17b,0x343,0xf6,0x149,0x302,0x2c,0x91,0x137,0x138,0x15c,0x1ac,0x1c9,0x20e,0x8,0x161,0xdb,0x187,0xb6,0xe8,0x25b,0x6,0x18d,0x26a,0x2d2,0x2e6,0x36c,0x379,0x3b9,0x8c,0x93,0xaf,0x2cb,0x121,0x36c,0x374,0x45,0x69,0x184,0x2cc,0x2dd, + 0x3a4,0x3b0,0x3f8,0x287,0x382,0x19,0xdf,0xb0,0xd4,0x219,0x17b,0x196,0x214,0x289,0x2fa,0x2fd,0x332,0x364,0xe9,0x270,0x29d,0x30d,0xb8,0x1d8,0x28b,0x10c,0x132,0x175,0x1e4,0x226,0x264,0x26b,0x3bd,0xa4,0x359,0x1a7,0x221,0x6e,0x1a5,0x2c2,0x66,0x93,0xef,0x106,0x1c2,0x223,0x224,0x396,0xd6,0x3e1,0x1f5,0x2f1,0xfc,0x2c1,0x396,0x7e,0xb2,0xdd,0xe3,0x1b4,0x1f2,0x35b,0x3b0,0x3d4,0x3f7,0x19,0x23f,0x154,0x395,0x3bc,0x9,0x88,0xea,0x139,0x286,0x2b4,0x2ef,0x368,0x30,0x1b7,0x223,0x3db,0x110,0x22f,0x3e0,0xd4,0x15e,0x1d7,0x1e3,0x228,0x232,0x2cc,0x2df,0x1db,0x1f6,0x14b,0x325,0x92,0x376,0x3dd, + 0xd4,0x176,0x276,0x30d,0x321,0x32e,0x343,0x34e,0x168,0x365,0x261,0x3dd,0x5d,0x374,0x398,0x32,0x39,0x8d,0xf5,0x34a,0x368,0x3c8,0x3fc,0xcb,0x210,0x12b,0x15b,0x2a,0x205,0x362,0x22,0x7b,0x80,0xb1,0xdc,0x171,0x258,0x314,0xdb,0x3b4,0x103,0x2af,0x11,0x1ae,0x29c,0xe6,0x12a,0x176,0x225,0x275,0x2af,0x2e2,0x3d2,0x295,0x3f6,0x271,0x3d3,0x288,0x375,0x3a6,0x52,0xc5,0x187,0x1a2,0x272,0x335,0x358,0x370,0x28,0x2ef,0x1b,0x3bf,0x18c,0x1f3,0x312,0x14,0x17b,0x216,0x2d4,0x30e,0x319,0x393,0x3e4,0xec,0x34d,0x289,0x315,0x158,0x1e5,0x2aa,0x5a,0xb5,0xc6,0xc7,0x117,0x2d2,0x336,0x3c2,0x302,0x35b, + 0x393,0x3bb,0x1ee,0x318,0x34f,0x68,0xc8,0x11c,0x12f,0x133,0x162,0x21b,0x37a,0xa3,0x2d0,0x343,0x3b3,0xe6,0x1cc,0x3ef,0x1a,0x2d,0xda,0x177,0x220,0x29a,0x331,0x3fe,0x2c,0x37d,0x273,0x391,0xc1,0x20c,0x22a,0x28,0x3b,0x116,0x16b,0x1d8,0x2b6,0x377,0x3ce,0x12b,0x31c,0x163,0x1c1,0xd2,0xf9,0x1f8,0x2,0x3c,0x56,0xd9,0x1e7,0x24e,0x2f8,0x37d,0x2e4,0x329,0xf3,0x2af,0x8c,0xc0,0x245,0xf8,0x180,0x1a2,0x1a7,0x1bd,0x2f9,0x374,0x3aa,0x22e,0x2b7,0x1c7,0x201,0x32,0x1a1,0x25e,0x5f,0xfa,0x186,0x18c,0x207,0x239,0x244,0x3c8,0xaa,0x269,0x133,0x30f,0x128,0x2db,0x32c,0x9b,0xc0,0x111,0x14a,0x1e6, + 0x263,0x270,0x3b2,0x190,0x369,0xcb,0x121,0x15f,0x1a4,0x348,0xaa,0x150,0x172,0x1c9,0x243,0x314,0x35f,0x3e6,0x5e,0x137,0x57,0x11d,0x3e,0x26c,0x373,0x80,0xe0,0xef,0x1ff,0x26c,0x2f0,0x38b,0x3e2,0x183,0x308,0x2f,0x18f,0x34,0x7f,0x3c4,0xd2,0xdc,0x169,0x1bc,0x20e,0x261,0x3d5,0x3fe,0x38b,0x3be,0x115,0x347,0x198,0x3c0,0x3d1,0xdf,0xe1,0x168,0x16a,0x174,0x27f,0x2c0,0x3ca,0x1af,0x372,0xf5,0x2ab,0x2f,0x2fc,0x3ce,0xa,0x41,0x226,0x236,0x310,0x31c,0x399,0x3d7,0x186,0x1ad,0xc5,0x3db,0x19e,0x1a9,0x26e,0x74,0xb4,0x157,0x178,0x278,0x311,0x314,0x38d,0x13b,0x274,0x1ff,0x279,0x42,0x384,0x38f, + 0x6a,0x83,0x136,0x169,0x230,0x232,0x3eb,0x3fc,0x179,0x276,0x1d5,0x3a5,0x1c8,0x297,0x3d8,0x70,0x108,0x1e9,0x2ca,0x3a8,0x3b2,0x3bf,0x3f7,0xdc,0x10b,0x19d,0x35d,0xc3,0x132,0x254,0x7a,0x135,0x165,0x1c6,0x254,0x34c,0x373,0x396,0x169,0x3e2,0x13b,0x3e3,0x228,0x307,0x39a,0xe4,0x10d,0x155,0x1ee,0x1f6,0x2c3,0x30c,0x3ce,0x84,0x3ff,0x1ed,0x1f1,0x1df,0x27c,0x35e,0x3,0x26,0x9e,0xb7,0x1d0,0x211,0x2e6,0x3ea,0x121,0x3dc,0x111,0x337,0xb3,0x106,0x190,0x168,0x170,0x219,0x2eb,0x334,0x337,0x37c,0x3a6,0x16,0x33f,0x69,0x153,0x162,0x16f,0x2fa,0xd,0x42,0x8a,0xfa,0x165,0x1ff,0x2e0,0x2fc,0x212,0x3e3, + 0x4b,0x331,0x235,0x3ae,0x3ee,0x49,0x60,0x78,0xc2,0x154,0x24e,0x2d5,0x3df,0x120,0x19d,0x137,0x1a1,0x1a,0x94,0x397,0xb6,0x12f,0x136,0x188,0x1cf,0x1d5,0x314,0x364,0x31,0x2f4,0x75,0x2cf,0x1ac,0x217,0x274,0x44,0xd7,0xf0,0x11c,0x18f,0x1d1,0x310,0x3fc,0x70,0x243,0x45,0x2a9,0x10e,0x24d,0x2ba,0x34,0x46,0x84,0x141,0x150,0x15b,0x183,0x3aa,0x2a6,0x2db,0x123,0x37d,0xa5,0x20c,0x2f2,0x49,0x92,0xc0,0x107,0x24f,0x2f0,0x300,0x3ca,0xa2,0x399,0x32d,0x335,0xb1,0x27e,0x2d0,0x69,0x9c,0x11a,0x126,0x150,0x225,0x241,0x246,0x1d,0x19a,0x2a7,0x3ed,0x2e,0x1f4,0x2cd,0xf3,0x135,0x205,0x264,0x29c, + 0x2ca,0x348,0x382,0x9,0x9c,0x105,0x1df,0x11b,0x172,0x2d2,0x1d,0xa5,0x15e,0x2ad,0x2c4,0x312,0x330,0x39a,0x2c6,0x3b5,0xa3,0x311,0x122,0x129,0x2ca,0x3c,0x15d,0x16a,0x187,0x2ba,0x323,0x33e,0x3a2,0x9d,0x334,0x275,0x3b5,0x8f,0x174,0x316,0x19,0x5e,0xfd,0x15a,0x166,0x23b,0x24a,0x31a,0x131,0x2a0,0x303,0x397,0x6,0x1b8,0x31b,0x7e,0xa3,0xde,0x1ea,0x2de,0x2f4,0x35d,0x3d9,0x74,0x24f,0xe9,0x2ad,0x3e,0x9a,0x3af,0xaa,0xd2,0x13d,0x162,0x1b1,0x216,0x277,0x2e0,0x112,0x22d,0x17,0x3e5,0xc2,0x334,0x381,0x1c1,0x1c2,0x1d2,0x202,0x235,0x2f2,0x395,0x3c6,0x2be,0x2c9,0x7b,0xb9,0x5c,0x76,0x299, + 0xa0,0x112,0x133,0x1ad,0x29a,0x370,0x38e,0x39f,0x11a,0x22f,0x45,0x1af,0xba,0x22b,0x33c,0x121,0x150,0x26c,0x2b0,0x2b5,0x302,0x392,0x3ab,0x173,0x198,0x2f5,0x36f,0xa0,0x2e4,0x3a1,0x8b,0x10e,0x164,0x17e,0x245,0x262,0x288,0x3bf,0x191,0x348,0x191,0x381,0x18,0x325,0x3b4,0x16c,0x1c3,0x1ee,0x237,0x280,0x2a1,0x302,0x336,0x9f,0x3cc,0x81,0x18d,0x5a,0x7b,0x154,0x6c,0x87,0xa4,0x136,0x15f,0x1ee,0x39c,0x3e7,0x113,0x34c,0x12f,0x1dd,0x54,0x14b,0x3c2,0xf6,0x251,0x260,0x273,0x2d6,0x2e5,0x308,0x3d4,0x157,0x32c,0x2b1,0x335,0x3a,0xbb,0xd4,0xa6,0x125,0x13c,0x152,0x1f0,0x2f5,0x303,0x33e,0x1f7,0x26a, + 0xfb,0x1b9,0xe9,0x104,0x1ce,0xca,0x13f,0x1c1,0x22a,0x32d,0x342,0x3c4,0x3d2,0x23d,0x2ae,0xb,0xdd,0x84,0x34e,0x363,0x14,0x1e,0x128,0x139,0x24f,0x27e,0x399,0x3e6,0x1c,0x47,0xc1,0x217,0x23a,0x23c,0x271,0xa4,0xe3,0x105,0x18c,0x27e,0x299,0x2a4,0x2c8,0x21,0x1d6,0x315,0x371,0x52,0x89,0xb6,0x45,0xc0,0x1ec,0x2bd,0x2d2,0x33d,0x372,0x382,0xa,0xf1,0x2bd,0x3dd,0x11d,0x1e8,0x28c,0x15a,0x225,0x293,0x2ac,0x2e7,0x30e,0x31c,0x382,0xc4,0x3cd,0x89,0x13d,0xd6,0x123,0x1ec,0x61,0x154,0x1cd,0x298,0x2ee,0x346,0x358,0x3b7,0xe0,0x1bd,0x141,0x205,0x19d,0x1da,0x1fa,0x42,0x79,0xb6,0xec,0x113, + 0x289,0x31c,0x3d8,0x3a,0xe3,0x12d,0x365,0x48,0x103,0x1c4,0x51,0x59,0xf6,0xf8,0x12e,0x281,0x38e,0x3d6,0x176,0x233,0x349,0x371,0x3,0x344,0x3d2,0x11,0x1a,0x3a,0x20c,0x211,0x22f,0x24e,0x28c,0xc2,0x171,0xfd,0x1a3,0x2e5,0x32a,0x334,0xa6,0xae,0x1ed,0x290,0x2cc,0x2e2,0x341,0x34f,0x11e,0x12d,0x149,0x2e1,0x4d,0x202,0x29e,0x68,0x143,0x22c,0x235,0x316,0x33a,0x35a,0x3ab,0x154,0x3c1,0x11,0x3a3,0x281,0x314,0x3a6,0x64,0x9a,0xbb,0xfc,0x194,0x2c5,0x355,0x3fe,0x13d,0x1d8,0x129,0x1d9,0xf8,0x1ff,0x292,0x31,0x97,0xe6,0x110,0x122,0x189,0x268,0x300,0x19c,0x241,0x13,0x3cb,0x4c,0x15a,0x2d9, + 0x17,0xb6,0x10a,0x16b,0x18e,0x1ae,0x24b,0x290,0x152,0x1df,0xf5,0x259,0x1c2,0x36a,0x3a5,0x5c,0x98,0x181,0x212,0x213,0x28a,0x364,0x39d,0x15f,0x214,0xad,0x155,0x176,0x250,0x3fb,0x2a,0x1c3,0x21f,0x276,0x282,0x306,0x308,0x367,0x1da,0x1fd,0xc5,0x23d,0x17c,0x1c1,0x1f2,0x5a,0x8c,0xcd,0x145,0x23e,0x287,0x394,0x3d0,0x38d,0x398,0x12b,0x1f5,0xf8,0x18a,0x3ad,0x87,0xcc,0xf4,0x1b2,0x28e,0x2cb,0x2d4,0x3e9,0x2ce,0x301,0x12f,0x235,0xce,0x134,0x36b,0x9e,0xa1,0x22c,0x2b8,0x33c,0x3be,0x3e7,0x3ed,0x34e,0x3ab,0x257,0x3bf,0x12a,0x1b6,0x20d,0x57,0x176,0x1aa,0x1ed,0x271,0x2a6,0x2f8,0x3c0,0x386,0x389, + 0x309,0x37f,0x144,0x284,0x2f1,0x157,0x1d8,0x298,0x2ae,0x2b1,0x32e,0x398,0x3ef,0x27,0x25e,0xbd,0x3d1,0x44,0x230,0x3b7,0x8,0x10a,0x195,0x1fe,0x22a,0x353,0x355,0x374,0x19f,0x350,0x27,0x27b,0xe2,0x15a,0x1eb,0x4e,0xdd,0x1af,0x244,0x2e3,0x352,0x38e,0x3f4,0x291,0x354,0x1ad,0x21f,0x56,0x1a4,0x35b,0x104,0x153,0x20a,0x24e,0x253,0x2dd,0x2fa,0x348,0x5b,0x18c,0xb1,0x1bd,0x5c,0x189,0x394,0x19,0xe2,0x1b6,0x1ba,0x1ee,0x27a,0x351,0x3ad,0x6e,0x195,0x11b,0x32d,0xde,0x339,0x35c,0x27,0x9c,0xd8,0x122,0x195,0x226,0x37c,0x3b5,0x264,0x2f7,0x297,0x33b,0x5,0x2dc,0x30a,0x24,0x96,0xb0,0x138,0x19f, + 0x1bf,0x339,0x374,0x107,0x200,0x2cb,0x3fd,0x116,0x1a2,0x37f,0x52,0x82,0xc8,0x14e,0x270,0x28f,0x2ff,0x39b,0x103,0x2b4,0x3b,0x175,0xa8,0x1b4,0x3b9,0x6,0x2e,0xc7,0xd4,0x204,0x207,0x282,0x36d,0x10a,0x23b,0xa5,0x1b1,0x104,0x241,0x3c8,0x1c,0x6c,0x74,0x149,0x186,0x2f1,0x31f,0x362,0x46,0x38f,0x215,0x3bd,0x9e,0x122,0x309,0x50,0x153,0x196,0x1dc,0x285,0x2a8,0x340,0x375,0x189,0x284,0x39f,0x3ff,0x4a,0x15e,0x199,0x172,0x17a,0x1c5,0x205,0x231,0x376,0x380,0x3c8,0x182,0x2cb,0x109,0x123,0x8a,0x228,0x263,0xd0,0x175,0x254,0x2b2,0x33a,0x33d,0x3a9,0x3ba,0x39,0x2ea,0x127,0x357,0xfe,0x24c,0x311, + 0xe,0x3d,0x124,0x139,0x146,0x1e2,0x39d,0x3ae,0x89,0xfe,0x1bd,0x3eb,0x82,0x1d8,0x3e1,0x5e,0xac,0x123,0x13e,0x173,0x2a5,0x30c,0x330,0xba,0x21f,0xad,0x2df,0x19c,0x2f7,0x3aa,0xbc,0xcd,0x117,0x124,0x14e,0x280,0x359,0x3f4,0x92,0x36f,0xef,0x235,0x26,0x2ac,0x3c9,0x6f,0x1c6,0x21e,0x249,0x28c,0x342,0x388,0x393,0x5f,0x14e,0x125,0x15b,0x99,0x202,0x3da,0x3d,0x8e,0xcc,0x191,0x21e,0x278,0x27a,0x2cd,0x2c3,0x304,0x2a5,0x3df,0x17a,0x215,0x264,0x99,0xa2,0x107,0x13a,0x21c,0x35c,0x37b,0x386,0x102,0x1b9,0x147,0x2e7,0x5a,0x1d2,0x377,0x90,0x128,0x143,0x17b,0x28a,0x2a5,0x324,0x344,0x1e2,0x25f, + 0x113,0x241,0x12,0x3ec,0x3f3,0x26,0xd3,0xe0,0x1b2,0x26a,0x281,0x2ec,0x345,0x75,0x1d2,0xa5,0x3f7,0x71,0x226,0x350,0x1b,0xc4,0xf0,0x120,0x184,0x1b3,0x3c3,0x3ce,0x28d,0x30a,0xe1,0x2ed,0x1d7,0x31e,0x3d0,0x72,0xab,0xd9,0x1ba,0x230,0x265,0x2b2,0x37e,0x62,0x29d,0xf7,0x195,0x3f,0x4c,0x1f0,0x63,0x66,0x160,0x170,0x261,0x2d8,0x30d,0x328,0x72,0x23f,0x53,0x87,0x12c,0x16d,0x1f4,0x7,0x7e,0xca,0x190,0x1a9,0x1f1,0x1fe,0x39e,0x135,0x19e,0x107,0x211,0xa6,0x1d9,0x2c0,0xbe,0x109,0x162,0x19c,0x1b7,0x22e,0x233,0x390,0x25,0x204,0x211,0x253,0xc6,0x23d,0x276,0xb7,0xfa,0x156,0x158,0x1c6, + 0x224,0x2ad,0x377,0xdf,0x2f0,0x327,0x37b,0xf5,0x10c,0x150,0xe,0x20,0xe8,0x1d7,0x295,0x2eb,0x36a,0x396,0x3f,0x280,0x8f,0x3e1,0x179,0x2f2,0x3c4,0xd0,0x191,0x1d8,0x1f8,0x20d,0x29d,0x29e,0x2f0,0xc0,0x273,0x245,0x271,0x135,0x210,0x364,0x55,0x82,0x255,0x2b4,0x317,0x31e,0x358,0x3b4,0x1ec,0x211,0x83,0x165,0x62,0x1d5,0x3ee,0xb9,0x1d0,0x1d6,0x1e6,0x2a1,0x2b0,0x352,0x3c9,0xc3,0x3f4,0x2d,0xd7,0x1e,0x2bb,0x33e,0xfe,0x156,0x1e5,0x284,0x28d,0x34b,0x352,0x3bc,0x1d5,0x2e0,0xd,0x39b,0x170,0x222,0x26f,0x7f,0x172,0x1cf,0x2e2,0x312,0x31d,0x346,0x36c,0x221,0x29e,0x39,0x2c9,0xf0,0x1e0,0x35f, + 0x3f,0xc6,0xfe,0x120,0x129,0x16b,0x1d4,0x26e,0x4d,0x2b0,0x283,0x329,0x0,0x86,0x187,0x62,0x104,0x138,0x164,0x165,0x2a3,0x357,0x3f4,0x82,0x319,0x91,0xb9,0xa2,0x168,0x293,0xfb,0x216,0x23a,0x2cf,0x2f8,0x335,0x392,0x3a6,0xcd,0x13a,0x2ad,0x3e3,0x248,0x315,0x3bc,0x63,0x12e,0x19e,0x232,0x25e,0x2cf,0x389,0x38a,0x50,0x263,0x5f,0x275,0x64,0x35e,0x3c7,0x202,0x20f,0x2a8,0x302,0x344,0x377,0x38e,0x397,0x8a,0xab,0x31d,0x31f,0x84,0x156,0x1fb,0x0,0xb3,0x1e0,0x224,0x25c,0x32a,0x381,0x3cf,0x11,0x376,0x6d,0x205,0x226,0x39c,0x3bb,0x65,0xec,0x158,0x211,0x228,0x24a,0x2d9,0x2ea,0x3,0x22a, + 0x17d,0x29f,0x74,0x12b,0x340,0x6a,0xb2,0xcf,0x148,0x1c0,0x269,0x271,0x33c,0x10c,0x277,0xbb,0xcf,0x1f5,0x3ba,0x3dc,0x46,0xb6,0x142,0x1ed,0x27d,0x327,0x3a4,0x3ae,0x10d,0x2fe,0x193,0x267,0x39,0x37c,0x3f0,0xfa,0x101,0x134,0x15c,0x199,0x1e0,0x30f,0x394,0x26,0x31b,0xa1,0x105,0x238,0x31a,0x3c5,0x46,0x4c,0x146,0x14d,0x151,0x185,0x198,0x240,0x153,0x254,0x29b,0x2b7,0x6a,0xb0,0x185,0x76,0x132,0x1d1,0x1ff,0x291,0x2d6,0x346,0x386,0x64,0x2a5,0xb5,0x3c1,0x20,0xb5,0x346,0x8,0x81,0xf4,0x126,0x1a2,0x257,0x383,0x3bc,0x95,0x18a,0xcb,0x1bf,0xa6,0x124,0x1cf,0x4e,0x171,0x1da,0x26e,0x2c3, + 0x31c,0x3d3,0x3e2,0xb2,0x17f,0xb7,0x1c9,0x15c,0x1de,0x25d,0xa0,0xdd,0x11a,0x15c,0x1c4,0x1df,0x2d8,0x3c1,0x22c,0x26b,0x6f,0x36d,0xa9,0x386,0x38e,0x56,0xdb,0x241,0x2c6,0x30a,0x30e,0x39d,0x3ba,0x205,0x3b8,0x21,0x353,0x182,0x1dc,0x2b1,0x3,0xba,0xda,0x123,0x227,0x24c,0x35a,0x3f4,0x1b,0x226,0x1a9,0x3c3,0x82,0x3b0,0x3cd,0x1,0x25,0x85,0x120,0x2b0,0x384,0x394,0x3fa,0x7f,0x100,0x19d,0x20f,0x7c,0x128,0x22d,0x54,0x92,0x164,0x192,0x2b5,0x39c,0x3d7,0x3e9,0xfb,0x318,0x31d,0x3eb,0x1e6,0x295,0x37c,0x95,0xb0,0x131,0x22c,0x256,0x280,0x3b0,0x3b1,0x298,0x339,0x4f,0x199,0xa1,0x264,0x3fa, + 0x44,0x17d,0x1c0,0x1f7,0x229,0x24a,0x278,0x338,0xde,0x14b,0x1e1,0x337,0x1e1,0x1e6,0x26a,0x74,0xb1,0xc9,0x134,0x2b3,0x332,0x372,0x3d8,0x279,0x2d8,0x37f,0x3f5,0xd1,0x330,0x356,0x4,0x4c,0x12e,0x19d,0x2e5,0x2ec,0x2f9,0x31e,0x156,0x1b5,0x43,0x103,0x26,0x7e,0x3db,0xd2,0xd6,0x100,0x186,0x271,0x297,0x3a4,0x3cb,0x32,0x373,0x1b3,0x2f1,0x58,0x288,0x331,0x4e,0x101,0x1f2,0x1f5,0x247,0x2ce,0x30c,0x33e,0x48,0x181,0x1f,0x369,0x102,0x20a,0x303,0x68,0x9a,0x169,0x1f4,0x240,0x292,0x367,0x3b9,0x331,0x3a4,0x179,0x2e3,0xd8,0x1e2,0x243,0x15,0xb9,0x1ad,0x24a,0x27e,0x326,0x360,0x376,0x35f,0x3f8, + 0x33,0x26f,0x208,0x218,0x255,0x1,0xa3,0xba,0xc6,0x13b,0x1b2,0x220,0x2a2,0x6d,0x282,0x1c1,0x269,0x13b,0x242,0x2da,0x6d,0x17e,0x250,0x26c,0x30b,0x355,0x35e,0x3f0,0x166,0x307,0x47,0x51,0xbe,0x1e7,0x3fc,0x3f,0x5d,0x184,0x2e8,0x2fb,0x2fc,0x37a,0x396,0x106,0x299,0x167,0x253,0x8a,0x17e,0x30d,0x9d,0x11a,0x124,0x129,0x157,0x1a0,0x276,0x27a,0x5d,0x24a,0x17f,0x213,0x322,0x36c,0x39b,0xaf,0x163,0x20a,0x234,0x238,0x341,0x360,0x3b8,0x35c,0x3b9,0x1d1,0x31f,0x16a,0x1e8,0x3b1,0x8,0xad,0xf2,0x351,0x3b0,0x3b8,0x3e6,0x3ff,0xbc,0x149,0x55,0x37b,0x131,0x39c,0x3ac,0x25,0x4d,0x10e,0x1dc,0x1dd, + 0x218,0x21a,0x2e2,0x67,0x300,0xc1,0x14d,0x40,0xbd,0x12e,0x1c,0x3e,0xb0,0x1e1,0x20f,0x257,0x2b0,0x2f8,0x55,0x330,0x5b,0x283,0x79,0x2f6,0x31e,0x13,0xdb,0x172,0x1d2,0x296,0x2f6,0x342,0x3a1,0x87,0x118,0xb3,0x3ff,0x141,0x30e,0x33e,0x4e,0xf7,0x128,0x184,0x19d,0x1a8,0x1e1,0x3dc,0xf3,0x180,0x2ef,0x2f3,0x92,0x234,0x359,0xc1,0xec,0x24e,0x2d0,0x2e7,0x34a,0x369,0x380,0x63,0x2d6,0x63,0x251,0xd5,0x148,0x2ee,0x48,0xe4,0x1e6,0x21b,0x22a,0x28b,0x328,0x3e3,0xda,0x3bf,0xe3,0x16d,0x59,0x274,0x34e,0xd,0xf8,0x19f,0x33c,0x37d,0x3d2,0x3e0,0x3ec,0xb0,0x1d7,0x209,0x241,0x233,0x33a,0x366, + 0x86,0xc0,0x216,0x248,0x283,0x28c,0x339,0x3ff,0x1c2,0x1c7,0xb7,0x1e9,0x80,0x1a2,0x231,0xc3,0xce,0x15e,0x1c7,0x299,0x2c0,0x306,0x334,0x1c9,0x3e8,0x3d,0x359,0x2fe,0x372,0x3fd,0xe,0x18a,0x18e,0x1a6,0x1d5,0x2b2,0x311,0x343,0x232,0x36b,0x29f,0x365,0xc8,0x3a9,0x3c6,0x76,0x97,0x208,0x269,0x2cd,0x2d4,0x2ea,0x31c,0x30b,0x3e4,0x3f,0x6b,0x78,0x301,0x38a,0x73,0xc4,0x197,0x1e4,0x21e,0x32c,0x344,0x385,0x8d,0x134,0x6f,0x323,0x14,0x2c5,0x362,0xe1,0x19e,0x1dd,0x238,0x28c,0x301,0x34e,0x35c,0x16c,0x2d1,0x7d,0xd5,0xc,0x320,0x3d7,0x22a,0x2aa,0x2c5,0x2e4,0x34f,0x36a,0x37e,0x3e1,0x289,0x2d2, + 0x8b,0xc7,0xb,0x10,0x48,0x10,0x15,0x24,0x1ab,0x283,0x29c,0x2c8,0x3bc,0x23c,0x3fb,0x7b,0x32b,0x181,0x18a,0x278,0x33,0x100,0x190,0x20e,0x294,0x347,0x35f,0x3e0,0x1d4,0x227,0xe5,0x2e3,0x41,0x1fc,0x3e4,0xe,0x50,0x6c,0xa0,0x163,0x26d,0x2ba,0x2ed,0x45,0x124,0x149,0x359,0x110,0x2e2,0x391,0x6e,0xa7,0xe9,0x22e,0x2a7,0x2dc,0x2fe,0x3c2,0xe1,0x1ac,0x177,0x33d,0x19f,0x1fe,0x38c,0x3a,0x44,0x71,0x12e,0x187,0x189,0x23a,0x3f8,0x17a,0x297,0x1db,0x30b,0x207,0x236,0x2ac,0x4c,0x64,0xe5,0x103,0x144,0x16d,0x1ba,0x394,0x13,0x1fe,0x9,0x225,0x83,0xc0,0x168,0xac,0xdf,0xff,0x15c,0x1c2, + 0x216,0x399,0x3a0,0x1f2,0x275,0x47,0x1b3,0xce,0x204,0x2f9,0x32,0x112,0x1f5,0x221,0x22f,0x23c,0x2b4,0x3b6,0x14f,0x28a,0x2c5,0x2d3,0x130,0x28e,0x2b3,0x16,0x3b,0xbe,0xd5,0x170,0x1f0,0x2c0,0x3fb,0x98,0xeb,0x24b,0x3b1,0xc8,0x28d,0x3a8,0x9,0xf6,0x154,0x162,0x1b6,0x1c1,0x1c5,0x3f8,0x1dc,0x21b,0xc3,0x3ad,0x1f1,0x1fa,0x218,0x2e,0x98,0x125,0x190,0x28f,0x2db,0x37c,0x3c2,0x26d,0x3da,0x173,0x245,0x75,0x11a,0x368,0x35,0x94,0xdf,0x106,0x194,0x268,0x26b,0x2f2,0x14d,0x1b8,0x151,0x251,0x14a,0x197,0x2d8,0xd5,0xd6,0x102,0x23c,0x249,0x259,0x2f2,0x3f2,0x24,0x3e5,0x99,0x28d,0xa3,0x2e2,0x3a0, + 0x78,0xe3,0x1a8,0x1e6,0x322,0x347,0x3ad,0x3bc,0x158,0x2cd,0x23,0x51,0x4a,0x319,0x372,0x65,0x71,0xa2,0x12c,0x1a6,0x1da,0x212,0x3cb,0x15,0x34,0xe3,0x10f,0x12c,0x287,0x3f6,0x4d,0x93,0xf4,0x110,0x29b,0x306,0x348,0x36a,0xd4,0x1cd,0x323,0x3a9,0x2,0x112,0x371,0xa2,0xad,0x2dc,0x305,0x328,0x39a,0x3c6,0x3eb,0x2a1,0x39c,0x15,0x9b,0x19,0x2a4,0x2b2,0x76,0x7e,0xae,0xb7,0x14d,0x27c,0x2e7,0x354,0xa1,0x238,0x93,0x175,0x11c,0x1f8,0x349,0x64,0x67,0x20b,0x28e,0x2e6,0x333,0x33c,0x340,0x1f9,0x21e,0x59,0x249,0x10e,0x244,0x35d,0xdc,0x1fc,0x2b3,0x340,0x34e,0x373,0x391,0x3dc,0x2,0xb3, + 0x243,0x3af,0x28,0x67,0x1ee,0x5f,0xbb,0xe4,0x1e3,0x23e,0x350,0x356,0x3a8,0x9b,0x224,0x1c3,0x28b,0x38,0x21c,0x357,0x4,0xff,0x25c,0x274,0x280,0x345,0x37f,0x388,0x51,0x24e,0x385,0x3c9,0x138,0x29f,0x366,0xc1,0xd0,0x1be,0x1ce,0x1fc,0x258,0x39f,0x3a7,0x96,0x37f,0x1d,0x351,0x120,0x2fd,0x3b4,0xed,0xee,0x1d0,0x246,0x349,0x363,0x390,0x3a0,0x316,0x3c9,0x16b,0x3a1,0x192,0x26d,0x2bc,0xd,0x96,0x16d,0x192,0x2f0,0x326,0x397,0x3de,0x209,0x3d2,0x1db,0x397,0x130,0x142,0x3ed,0x193,0x1b8,0x1fd,0x240,0x300,0x312,0x3b3,0x3de,0x2bd,0x2e6,0x277,0x3af,0x66,0x1d0,0x3eb,0xea,0x1b2,0x1db,0x245,0x2a9, + 0x2c4,0x35c,0x3d0,0x9e,0x1a7,0x167,0x22b,0x60,0x2f0,0x3c1,0x8,0x188,0x21c,0x303,0x30a,0x327,0x349,0x3f6,0x1d0,0x27b,0x159,0x3a3,0xc2,0xe4,0x2e1,0x22,0x41,0x87,0xab,0x130,0x17c,0x1c2,0x2a4,0x245,0x344,0x189,0x3e7,0xb4,0x184,0x227,0x0,0x5f,0x78,0x9b,0x238,0x290,0x2c2,0x34d,0x18,0x347,0x157,0x299,0x54,0x1ec,0x345,0xe6,0xf3,0x12d,0x14b,0x1ba,0x21e,0x284,0x294,0xdd,0x312,0x1d3,0x379,0x12,0x81,0x388,0x0,0x1a,0x65,0xb8,0xbd,0x10f,0x17e,0x28a,0x66,0x167 + }; + + +// ------------------------------------------------------- + + constexpr inline std::array values = { + 505,79,213,500,321,109,315,36,214,323,345,6,451,98,64,390,373,457,439,304,167,39,397,284,222,497,373,384,214,507,369,60,447,467,497,510,475,361,218,158,1,5,347,417,95,88,409,164,456,189,509,215,508,214,465,170,215,305,370,64,177,123,368,304,393,446,132,65,326,487,158,222,236,350,149,301,300,173,254,34,173,511,447,466,235,313,173,109,423,2,56,42,179,485,97,291,311,379,297,225, + 462,382,295,186,473,159,242,18,345,169,393,24,465,166,384,479,243,258,417,24,387,72,204,186,365,283,89,41,329,479,337,496,357,314,478,437,93,475,210,64,177,212,250,307,242,134,342,292,43,374,112,472,54,11,251,279,182,512,385,267,453,208,445,280,140,253,466,74,409,267,448,291,400,110,205,82,470,42,9,469,107,103,428,166,451,326,257,289,362,185,258,460,442,47,257,355,409,208,284,221, + 441,197,135,189,425,390,477,430,105,301,326,158,484,97,120,364,161,65,270,198,401,269,297,242,341,23,419,298,491,313,364,495,173,30,317,389,335,379,374,110,40,69,448,391,290,447,304,30,169,213,406,136,159,393,360,209,265,132,66,317,59,67,468,47,479,12,420,347,193,287,210,251,241,490,344,308,75,400,343,410,398,500,28,462,470,132,211,160,238,271,384,19,374,51,415,490,122,261,101,134, + 50,260,53,312,283,327,21,434,117,89,156,360,91,450,40,212,83,25,184,502,54,81,464,200,190,496,27,199,187,494,381,233,135,36,432,149,481,48,499,459,365,112,467,360,468,104,370,343,391,434,460,41,140,117,493,279,365,447,196,372,14,509,487,507,441,215,71,468,4,294,511,450,112,221,165,49,496,380,79,394,175,269,448,78,206,247,293,374,18,85,261,414,181,359,309,345,33,195,388,414, + 172,187,65,301,280,165,135,473,52,15,32,323,423,198,496,75,448,149,20,431,250,425,153,498,7,441,385,422,288,501,105,219,20,302,425,9,205,425,428,60,201,324,382,91,5,210,439,338,401,160,391,307,154,306,176,497,384,475,244,39,55,275,505,114,312,439,203,497,378,280,480,14,482,199,479,133,432,350,300,497,457,18,149,237,167,384,92,164,423,38,475,405,70,342,336,329,173,285,114,70, + 290,68,512,27,112,250,214,467,365,180,343,262,406,205,305,502,422,138,342,269,452,263,485,49,25,131,425,178,216,152,489,178,437,245,180,96,197,462,113,284,18,314,486,136,392,440,2,145,226,404,24,215,482,195,144,191,365,285,186,214,46,245,93,71,127,219,374,174,310,346,164,212,450,44,388,148,207,476,124,311,78,423,179,60,142,101,321,449,453,288,189,512,349,463,285,297,112,120,162,380, + 220,492,333,166,182,315,161,69,5,47,220,409,464,322,38,291,434,211,69,261,294,443,158,72,165,395,176,385,357,87,112,354,281,190,428,204,423,215,110,271,252,427,454,252,175,136,457,111,378,496,118,26,190,143,67,460,408,490,489,332,484,213,130,325,456,511,486,44,373,499,7,100,410,391,441,48,376,346,218,107,45,55,466,496,63,17,425,357,397,222,109,7,66,302,38,257,184,188,410,203, + 270,410,256,474,71,378,114,142,42,471,161,262,103,456,210,59,116,508,274,231,27,200,174,447,80,198,379,82,487,12,93,428,397,166,217,81,30,330,192,430,444,129,233,195,139,371,173,57,422,259,489,506,356,223,81,145,382,446,139,269,181,428,467,489,65,233,388,477,332,492,461,233,120,238,493,277,8,121,23,268,282,421,6,185,102,253,147,330,449,435,330,259,429,56,470,341,115,88,212,369, + 286,270,204,199,69,292,507,288,376,262,486,159,117,231,182,152,198,107,300,36,161,360,190,308,355,150,448,378,327,219,170,76,131,434,211,364,286,181,202,56,265,479,321,407,420,116,312,184,301,17,128,44,154,37,214,200,97,408,466,18,491,47,264,42,43,57,189,40,27,424,336,466,147,271,283,358,77,224,265,2,446,352,207,39,299,171,233,111,503,115,266,115,314,91,166,49,487,168,56,55, + 204,438,263,16,493,99,457,8,88,180,134,155,404,481,462,317,433,294,78,125,94,165,281,234,128,190,308,132,147,402,188,89,280,91,512,303,183,343,306,481,196,85,310,238,449,171,137,78,487,121,175,115,334,185,143,296,439,269,75,84,466,322,65,216,397,107,338,239,53,55,409,472,404,358,130,8,207,346,485,328,445,501,19,391,510,173,390,295,231,337,496,96,445,478,368,59,355,453,140,369, + 190,234,464,56,354,301,65,321,379,170,45,483,243,188,165,53,217,314,186,17,108,168,159,63,325,235,442,131,241,435,91,455,298,10,153,451,144,63,308,456,384,268,463,299,19,355,503,370,397,509,145,78,141,277,223,13,172,486,56,482,77,292,133,269,437,417,302,150,242,22,258,33,377,26,361,452,89,469,175,218,271,498,464,359,449,369,249,48,76,303,360,280,347,140,9,321,408,371,147,75, + 125,391,275,211,68,314,394,196,361,139,48,495,128,496,31,430,475,236,57,34,96,75,225,280,247,53,181,265,330,53,372,504,376,103,70,65,443,488,464,472,315,418,349,334,283,363,475,330,22,166,323,431,429,135,446,18,217,325,113,103,231,233,15,300,36,20,391,306,508,14,31,391,201,371,17,166,360,157,161,307,101,37,55,476,370,203,119,436,502,8,28,114,457,344,33,37,208,220,128,177, + 163,41,410,225,131,243,303,345,281,451,275,190,86,359,444,273,221,323,57,483,149,482,432,476,59,443,369,146,21,456,221,490,211,146,237,254,364,387,279,122,455,115,302,459,39,369,392,257,205,14,272,407,173,389,88,501,32,116,378,400,282,13,485,496,137,44,59,276,157,450,267,366,479,179,306,37,158,62,281,254,4,318,28,39,220,126,367,511,308,224,416,439,187,208,199,242,381,494,308,488, + 15,98,306,251,292,11,322,181,312,221,44,325,175,181,356,261,62,255,49,426,478,3,8,416,30,293,1,163,187,269,84,102,185,178,452,13,277,13,183,504,466,214,489,397,353,55,268,287,494,488,185,450,358,450,325,404,309,152,133,155,315,296,227,151,417,492,7,431,476,1,415,264,448,309,59,302,210,300,379,446,225,315,254,127,296,425,215,506,228,350,442,96,416,257,251,51,170,327,135,223, + 451,321,25,22,493,212,45,467,262,225,206,55,174,393,402,439,11,179,340,472,25,27,351,8,418,309,491,309,82,56,308,170,12,99,106,350,503,370,118,313,430,373,474,83,374,194,58,207,27,286,360,101,246,396,177,371,119,151,389,466,235,141,82,226,262,456,339,2,227,479,472,127,126,246,238,80,119,202,459,265,100,78,63,190,40,413,312,446,179,296,384,152,361,28,101,115,308,482,351,320, + 381,119,89,263,322,373,226,511,460,487,464,406,354,48,405,453,144,441,279,464,193,103,331,71,340,232,464,511,398,161,17,65,217,43,33,218,451,373,366,164,332,111,504,472,10,485,8,444,50,489,264,161,494,423,141,506,422,252,477,414,62,364,445,298,462,65,400,374,329,107,63,92,315,366,3,195,59,285,221,293,200,242,152,188,136,226,350,256,145,261,456,152,439,4,7,95,447,438,207,137, + 507,333,161,71,185,241,37,316,353,356,278,68,504,230,266,320,216,63,482,183,99,130,439,345,116,438,218,260,473,188,287,333,82,26,508,260,387,59,450,342,287,66,503,253,42,370,182,479,124,183,122,45,249,278,355,496,472,130,416,83,360,456,186,263,27,149,265,26,307,438,375,438,478,237,213,363,65,320,132,88,353,52,236,12,231,123,444,402,406,47,388,380,346,310,313,154,430,94,147,197, + 75,60,19,52,85,367,246,311,123,288,414,365,428,223,480,304,140,106,100,309,400,156,133,424,2,416,255,457,233,78,440,64,234,126,67,21,219,366,404,70,131,439,202,316,138,321,109,426,125,190,439,121,110,372,39,363,252,41,190,73,145,353,340,420,269,69,103,358,173,314,261,30,403,309,201,194,169,37,186,116,167,394,47,132,177,254,360,165,104,293,342,426,170,477,312,102,422,189,82,114, + 461,235,256,414,219,494,425,243,235,379,496,55,217,81,33,117,145,425,275,160,216,511,295,204,173,333,353,420,326,347,200,204,365,504,387,378,81,133,62,381,244,201,50,221,497,116,493,346,401,303,34,36,511,356,253,425,138,35,2,186,454,55,144,315,238,244,253,475,254,453,179,506,248,312,13,47,142,50,482,224,409,2,2,117,243,101,445,72,336,200,287,434,475,138,438,20,110,139,259,41, + 488,74,282,94,504,293,406,85,107,469,64,364,281,173,25,446,255,40,212,511,245,495,232,202,194,36,109,306,283,146,149,495,176,285,395,364,333,394,145,47,146,160,344,360,30,363,182,180,242,469,478,62,470,208,116,106,424,73,323,367,421,255,277,215,86,373,141,392,88,37,214,266,321,182,419,348,390,330,234,47,461,434,99,29,278,82,346,81,418,469,180,212,293,287,194,336,134,413,233,15, + 318,183,359,462,464,76,286,379,448,262,135,103,379,155,474,345,117,122,182,337,77,385,431,49,54,298,33,46,459,425,456,211,87,186,261,458,463,499,69,191,246,236,128,58,365,485,375,46,425,362,163,70,234,329,339,347,423,505,147,17,459,450,445,82,458,206,27,215,345,131,366,489,267,63,340,137,504,301,195,199,262,465,80,179,274,281,324,385,446,160,103,297,124,45,405,66,341,290,86,208, + 220,230,343,253,274,390,30,340,219,377,459,28,270,41,206,164,408,196,11,122,219,77,429,90,124,272,193,262,372,147,301,243,179,365,504,10,210,335,458,116,30,452,219,303,258,486,483,57,334,314,445,62,407,337,57,499,2,352,414,351,425,296,338,265,502,105,192,400,221,212,206,336,24,377,378,231,19,22,500,71,16,79,88,118,140,157,401,26,482,77,169,246,469,215,50,469,457,441,340,257, + 81,134,163,158,235,341,12,78,49,59,116,157,124,73,85,178,419,422,376,224,251,318,43,49,309,140,468,467,74,37,318,151,371,46,1,354,201,132,344,410,413,431,75,162,349,353,415,56,388,369,156,280,131,157,145,413,481,98,19,389,154,272,83,427,214,280,26,206,331,506,259,85,37,227,430,87,272,146,223,28,461,484,204,355,431,12,56,41,259,89,105,210,223,338,348,261,454,8,428,285, + 6,143,153,391,374,431,148,451,446,208,36,250,468,222,365,359,155,313,91,47,507,461,321,76,95,253,416,9,53,192,447,307,451,77,381,389,71,495,375,338,119,452,351,275,161,179,5,354,402,380,495,128,482,273,105,98,283,462,209,16,101,482,281,109,105,363,512,36,206,215,385,212,160,80,38,191,69,426,466,432,373,52,456,48,12,494,371,105,3,372,172,461,443,251,77,237,490,459,231,159, + 38,162,299,332,136,389,284,255,39,142,60,299,27,220,79,367,346,171,354,203,164,501,304,341,110,50,264,498,239,505,351,349,70,4,44,147,107,54,267,83,154,176,113,299,309,398,267,299,186,504,204,161,45,270,31,215,150,218,472,353,22,361,186,126,450,276,284,104,120,264,147,175,415,79,485,339,407,481,332,236,131,365,266,338,108,51,146,437,316,358,351,405,217,207,112,42,174,289,165,305, + 405,177,132,95,499,292,274,399,147,127,156,29,164,338,399,251,364,497,141,370,103,462,451,357,76,403,472,367,456,323,5,458,81,369,72,384,379,171,206,100,207,137,221,52,448,336,182,66,447,337,506,152,146,417,51,436,204,73,438,442,84,421,349,315,507,275,430,289,206,120,206,333,292,175,417,208,501,35,33,458,154,509,105,119,424,331,508,432,398,374,52,375,6,89,446,390,408,467,208,61, + 233,264,259,228,153,91,160,148,485,337,315,376,470,235,217,499,20,217,79,89,391,439,149,299,393,238,400,386,269,465,217,406,223,353,510,283,341,25,200,116,314,8,194,100,243,114,260,496,442,250,20,155,298,477,73,344,307,162,301,449,85,228,42,360,197,180,255,163,231,155,482,46,366,421,31,141,493,195,256,272,336,289,477,342,466,280,170,488,147,167,395,33,361,25,74,469,308,385,376,96, + 429,385,112,300,102,239,375,303,191,397,466,477,182,312,216,224,187,501,269,398,30,466,372,172,490,485,369,273,118,342,321,190,136,22,129,511,477,213,57,196,409,501,205,297,156,446,460,491,53,330,195,283,194,165,452,444,225,39,394,55,100,406,49,26,501,208,401,479,53,422,416,363,392,340,78,223,449,256,417,222,388,432,390,240,488,338,316,254,17,407,216,274,500,445,184,261,385,509,150,326, + 34,201,182,69,508,307,314,36,339,155,126,194,423,100,310,327,42,353,55,152,362,28,482,376,234,1,68,104,261,115,397,60,399,105,275,125,84,268,384,183,434,106,250,442,266,87,336,14,343,42,409,77,262,134,327,499,29,479,449,475,164,511,134,116,328,61,321,64,278,273,428,289,258,18,343,399,202,377,398,265,450,44,457,94,204,176,229,230,249,392,322,227,206,490,145,64,126,505,211,362, + 335,347,435,170,465,391,287,510,493,33,212,78,512,314,252,305,233,297,427,75,340,456,138,215,355,399,343,136,470,278,368,232,320,293,143,17,69,342,205,497,92,389,9,94,48,334,403,491,477,245,326,155,422,404,316,209,114,82,171,171,370,354,136,342,62,192,315,462,129,390,425,95,401,466,153,111,440,232,191,442,224,61,353,331,371,474,290,77,124,440,360,214,51,499,98,404,205,392,448,45, + 234,203,291,84,404,107,472,92,236,460,503,61,5,425,175,465,36,6,55,239,58,280,439,431,364,78,222,21,164,496,161,471,283,364,15,276,338,111,43,77,25,115,479,118,167,361,205,456,314,484,393,373,218,333,460,61,41,452,152,246,312,279,244,286,82,75,392,448,24,7,348,417,362,174,244,353,314,263,255,45,351,113,337,361,286,78,363,13,388,315,119,202,431,259,437,14,258,385,352,304, + 319,430,19,348,293,432,345,36,238,426,386,429,348,443,284,57,491,324,19,199,35,489,324,332,472,218,489,255,52,386,55,273,473,193,53,301,190,220,471,465,506,290,237,100,18,393,98,488,79,263,373,435,217,395,123,424,37,396,99,189,279,76,26,318,417,294,500,414,109,449,50,507,211,8,483,327,285,288,128,511,366,229,507,278,298,149,160,182,414,314,194,67,292,463,402,233,241,117,256,50, + 188,487,464,175,354,454,306,241,170,283,24,68,174,281,240,71,156,90,50,30,344,114,345,171,451,49,390,247,112,288,157,353,184,372,310,354,405,268,340,441,348,453,376,160,364,293,4,303,361,48,345,227,70,252,171,3,115,398,385,453,104,120,349,308,375,162,69,309,377,212,168,286,142,215,42,72,325,21,312,62,221,447,479,66,255,251,350,319,504,307,312,351,103,401,479,59,212,320,220,500, + 125,10,191,413,53,241,77,93,280,134,399,429,84,330,23,468,475,41,315,195,335,44,41,331,229,497,91,390,26,250,252,337,48,496,346,180,488,141,135,450,485,211,452,422,89,202,36,366,205,115,175,139,374,347,474,206,350,88,113,376,151,65,362,122,317,329,64,306,115,42,251,346,207,155,91,261,43,244,234,389,69,152,87,421,380,491,208,16,332,320,75,262,511,255,489,467,492,311,67,488, + 393,394,99,289,301,371,75,494,58,116,374,258,188,99,336,294,363,93,39,283,120,33,501,58,354,183,499,346,482,420,258,435,382,316,299,396,113,498,484,179,319,275,274,340,149,394,408,22,245,466,466,104,18,167,450,5,245,249,247,2,53,487,86,94,143,236,424,338,76,42,94,391,437,257,192,340,434,234,30,191,302,136,241,213,64,116,225,100,299,1,465,312,229,491,329,63,159,16,372,229, + 32,335,266,482,362,136,206,47,75,263,229,157,488,355,186,274,466,469,412,405,133,123,13,336,159,200,367,15,488,168,127,166,167,252,5,118,145,372,115,143,103,464,218,455,162,152,311,434,66,266,257,498,241,56,67,109,42,1,125,19,472,209,409,495,2,158,127,307,184,116,503,254,388,460,78,49,141,187,376,255,172,475,300,354,373,140,189,287,466,472,207,295,131,165,7,510,36,386,66,375, + 247,7,9,117,344,97,226,494,460,359,12,315,371,68,295,239,230,84,40,281,490,250,397,179,295,320,283,23,125,385,330,339,293,491,426,435,280,2,163,503,88,175,369,463,294,218,425,333,368,264,317,135,411,469,59,117,208,357,362,416,47,185,491,222,50,119,52,239,330,463,8,28,497,409,277,88,374,375,279,85,491,494,192,469,461,57,371,365,115,266,339,98,246,259,376,54,485,399,37,134, + 8,405,37,499,250,314,29,182,389,15,406,98,299,459,225,422,162,390,126,242,150,173,79,147,115,274,167,359,432,164,391,357,296,353,72,469,507,224,189,402,162,84,383,293,354,394,6,35,399,283,363,437,131,295,301,181,407,217,278,34,402,195,219,496,25,240,357,301,443,115,473,311,234,62,13,104,18,204,417,259,218,490,352,107,306,88,90,400,225,297,255,288,306,200,315,66,476,302,244,211, + 82,3,426,120,243,450,20,327,31,406,186,428,101,220,161,375,436,208,345,60,30,261,114,454,167,241,235,123,321,242,122,482,422,378,20,76,82,368,3,331,474,186,261,138,226,121,47,268,280,281,347,308,435,190,146,166,392,454,4,461,430,489,301,3,242,41,453,137,49,17,336,254,462,28,337,103,154,376,118,84,38,143,406,119,43,439,86,171,25,45,308,384,11,470,506,471,106,333,287,415, + 459,166,32,382,61,128,256,384,1,252,502,429,374,235,153,242,162,461,112,267,10,329,60,208,248,132,330,173,60,302,180,139,2,204,399,16,73,378,41,492,119,502,328,29,468,70,12,420,144,189,105,12,441,506,331,4,467,224,135,306,48,94,418,439,6,270,129,430,464,444,364,167,160,343,149,130,318,380,62,307,432,475,185,493,317,128,246,347,325,112,307,10,134,117,189,82,386,355,281,269, + 203,137,168,186,31,374,331,35,212,180,416,395,91,51,378,294,53,364,264,480,12,43,78,301,283,78,326,64,407,225,219,348,73,96,113,315,262,296,80,409,135,115,493,75,107,228,288,393,447,271,197,329,214,332,481,173,311,446,4,471,489,305,172,309,394,326,127,36,152,30,468,301,254,357,410,251,113,495,15,238,72,150,53,306,64,120,264,88,60,386,241,142,458,484,172,39,464,81,426,197, + 65,492,8,19,224,291,231,480,384,324,89,29,484,47,324,82,330,336,387,503,233,67,190,438,429,407,104,422,214,496,27,402,38,447,260,400,345,64,45,389,207,329,494,309,416,291,407,16,487,432,99,6,227,374,357,309,93,390,238,423,481,239,127,507,286,344,110,475,237,247,122,7,512,247,423,244,73,121,361,358,81,304,274,169,365,262,499,227,168,210,315,477,107,133,241,138,487,163,170,295, + 231,48,61,203,318,147,136,105,328,208,143,466,141,134,394,215,279,67,405,196,78,259,414,404,298,347,346,62,54,185,348,396,55,109,257,465,458,83,239,180,463,246,204,46,210,354,279,393,417,207,62,397,403,380,181,284,2,68,104,175,96,216,387,341,365,382,384,68,50,61,9,98,51,95,174,370,491,144,497,36,11,6,123,332,466,107,36,33,40,85,346,119,70,297,334,480,296,460,195,246, + 96,327,373,202,400,403,7,457,146,287,243,75,226,168,217,200,314,297,449,331,355,412,441,447,37,425,211,420,343,313,177,366,360,421,106,232,67,222,9,65,117,160,415,33,155,145,504,29,98,118,119,76,217,249,154,93,19,129,315,212,25,439,145,428,481,297,259,37,227,368,8,67,223,375,392,252,366,298,419,165,234,252,407,267,371,5,246,220,303,456,486,111,350,146,508,297,257,340,6,474, + 485,215,443,47,3,238,209,250,333,78,147,458,224,502,503,186,132,425,281,96,443,443,82,354,15,489,292,414,78,114,174,192,22,135,182,205,182,256,399,486,356,322,258,374,338,379,425,337,1,304,453,428,181,204,348,82,100,452,329,482,252,136,456,437,346,390,102,136,25,160,388,47,314,362,51,350,190,19,58,358,444,270,449,200,315,500,64,351,225,419,45,386,161,227,306,60,90,115,59,108, + 440,258,139,375,508,75,501,168,8,230,194,312,126,485,157,36,259,489,247,321,228,384,357,19,339,316,247,437,239,24,73,416,510,309,11,336,339,199,29,260,261,85,440,266,27,300,448,124,213,86,171,76,184,368,282,249,146,7,436,350,452,370,224,419,256,368,487,81,304,228,174,281,449,220,413,347,409,184,349,177,121,353,4,389,311,290,303,326,52,383,11,77,185,279,478,107,200,74,223,263, + 221,423,69,282,11,117,449,33,157,417,285,430,494,340,98,34,246,116,310,69,342,484,230,318,311,361,63,367,391,418,281,252,168,245,436,268,377,313,180,56,364,73,364,28,252,126,346,2,131,109,329,305,49,166,211,98,89,76,489,72,460,257,258,411,12,241,252,43,53,380,224,178,423,47,148,43,246,82,200,440,401,317,153,398,362,103,27,276,154,136,302,469,349,201,141,507,342,351,51,126, + 145,235,296,376,112,227,336,398,275,218,202,499,43,185,5,260,188,265,307,378,251,255,350,455,304,203,434,46,449,48,446,199,332,423,326,174,48,163,100,151,398,322,138,292,209,347,237,187,502,407,342,347,83,462,30,222,2,212,289,228,418,98,34,199,302,476,314,445,134,414,108,244,356,108,172,285,103,455,143,103,488,123,495,99,270,152,101,260,35,230,349,69,199,78,355,471,127,365,266,151, + 288,264,359,261,437,58,433,139,229,157,155,386,336,268,420,249,44,380,307,157,26,125,459,125,112,65,170,437,268,357,466,163,497,510,181,228,437,478,132,257,175,262,154,134,352,228,341,293,117,382,364,403,174,496,138,25,457,79,508,36,279,134,10,286,284,441,164,348,199,454,469,423,490,385,172,147,412,423,156,160,439,280,169,489,142,141,123,483,139,444,395,2,449,389,179,366,230,209,258,101, + 349,432,498,29,20,410,390,4,489,117,509,289,247,405,251,250,302,407,84,336,358,17,206,196,139,364,133,62,470,334,423,36,317,480,249,371,332,368,475,179,141,364,420,477,184,497,273,359,213,153,177,332,250,484,493,446,462,213,273,363,183,468,304,394,359,270,152,399,452,266,308,40,226,361,129,99,50,41,250,209,202,430,88,445,169,394,496,69,453,168,353,47,313,164,305,17,108,161,190,180, + 445,215,37,300,327,307,263,242,249,450,35,252,155,29,459,271,344,483,340,171,64,502,477,399,421,159,195,433,389,435,154,136,392,180,154,9,371,80,172,314,63,14,359,64,388,59,335,62,111,210,17,235,269,27,134,250,12,444,80,474,19,139,401,104,111,406,112,185,304,472,26,342,31,359,86,90,491,221,470,159,419,223,254,276,15,495,290,77,12,378,354,400,140,354,329,314,261,32,334,389, + 497,280,499,388,391,18,458,501,271,485,87,16,329,33,473,508,184,198,320,459,406,125,88,247,512,16,484,414,344,227,407,385,240,282,171,33,482,187,263,157,452,241,149,209,291,500,61,127,176,271,464,386,108,403,192,41,116,432,429,176,461,109,412,385,339,503,172,181,480,230,248,183,462,346,414,219,414,250,118,367,214,405,139,427,87,325,287,432,177,486,118,190,503,495,337,383,307,228,131,194, + 190,326,204,171,12,255,112,406,487,401,252,342,415,74,431,232,390,450,293,139,378,126,153,61,274,194,55,258,188,378,228,133,426,208,267,168,285,396,255,386,328,135,326,241,469,103,443,317,405,175,27,351,161,488,314,327,324,380,184,466,158,298,79,465,301,174,430,451,410,238,9,165,88,436,51,158,207,111,18,439,303,465,142,235,433,176,142,419,142,445,33,281,476,248,100,483,410,58,271,424, + 449,175,364,277,290,56,338,270,172,200,178,464,136,474,421,163,471,92,444,363,26,470,384,44,500,371,334,188,8,207,273,102,473,22,315,394,446,1,98,288,1,271,483,397,470,344,494,463,115,182,222,44,64,402,86,289,431,309,495,483,136,325,151,448,84,259,493,107,442,356,455,93,132,490,48,479,63,465,383,322,339,62,379,211,46,288,48,279,300,321,138,288,512,429,404,118,394,63,148,511, + 192,194,412,85,458,193,316,188,225,396,62,63,124,109,424,353,273,75,507,181,248,47,412,157,231,150,234,437,66,261,114,316,153,183,334,265,238,311,310,479,323,510,180,468,460,258,384,373,40,23,225,281,234,177,152,497,443,112,201,496,322,103,470,225,96,365,328,120,356,134,253,377,449,257,425,398,501,168,36,338,97,443,139,470,95,206,333,239,422,491,171,340,29,345,20,146,43,502,45,18, + 99,70,117,378,85,151,456,391,131,105,275,443,244,96,131,385,135,103,489,418,71,301,141,1,170,364,216,469,358,258,228,277,9,335,435,324,11,401,81,436,394,324,80,161,22,240,282,336,260,139,462,350,500,262,286,64,95,288,315,97,430,67,281,63,361,1,240,6,189,60,245,76,444,164,148,455,314,428,454,166,133,46,218,265,53,503,264,483,121,154,47,167,423,386,275,406,157,268,174,445, + 184,296,185,417,64,355,482,64,256,90,494,498,264,209,159,325,44,450,84,431,177,229,123,455,345,487,358,436,286,19,256,365,391,510,386,398,412,211,135,58,26,259,69,234,306,435,256,192,469,105,296,424,154,384,76,125,195,453,20,363,114,399,98,370,474,104,153,288,221,345,52,81,496,493,435,385,352,342,411,342,345,308,174,474,119,8,474,216,46,391,458,79,383,244,124,320,48,505,229,485, + 386,181,132,253,5,492,272,405,256,294,105,381,100,288,427,251,6,250,160,67,65,393,170,317,143,405,357,480,93,132,414,470,230,347,297,359,48,330,293,137,138,429,198,327,207,264,54,419,155,315,464,313,238,406,46,339,241,407,177,475,186,165,231,382,360,161,35,193,201,270,39,452,138,113,246,194,338,24,235,326,418,492,277,278,361,98,47,363,491,65,357,443,179,373,307,323,218,474,417,478, + 306,116,357,41,9,150,391,247,187,178,52,166,127,478,27,508,166,130,306,352,344,103,28,121,355,269,374,507,193,330,158,399,49,46,122,23,408,454,251,151,302,130,480,366,266,302,283,41,10,402,464,509,29,99,410,364,8,217,447,67,499,419,429,97,40,41,466,244,249,92,70,267,50,70,42,325,154,216,429,76,98,391,308,279,231,298,234,174,98,403,506,69,396,100,153,10,288,131,426,34, + 402,237,248,10,159,104,358,429,208,359,96,3,458,423,500,61,120,199,194,267,291,86,265,232,337,325,148,43,475,63,269,251,436,378,323,266,242,209,372,380,322,444,245,487,24,163,161,287,20,396,94,402,274,474,249,74,490,458,202,44,35,84,26,183,6,297,51,455,352,237,389,239,239,439,295,72,172,233,265,220,370,492,474,464,295,423,129,172,465,384,263,71,296,442,326,339,190,4,7,124, + 435,408,411,448,444,338,349,50,66,469,260,389,357,55,118,42,337,295,466,435,154,19,272,424,20,257,110,82,198,29,185,389,269,445,267,80,430,183,174,235,311,35,84,98,360,119,179,81,467,387,165,66,220,180,237,158,152,193,157,91,169,102,223,163,225,308,26,379,377,111,190,447,360,69,396,462,430,136,289,110,489,464,484,246,421,467,234,177,103,175,132,263,368,14,319,372,381,263,320,405, + 40,252,109,420,310,343,34,165,7,18,265,284,405,119,145,167,494,62,314,120,169,451,455,441,423,198,230,509,178,23,190,439,426,418,391,256,400,462,413,254,482,279,163,135,112,381,394,3,321,282,24,100,390,5,206,386,438,373,302,55,431,277,487,155,227,276,26,92,289,277,288,230,171,36,272,125,493,378,444,87,61,223,506,315,88,380,512,334,388,92,374,410,230,296,487,100,464,148,175,337, + 237,39,218,72,208,243,279,156,28,276,168,251,71,388,66,102,485,340,273,347,425,232,299,505,436,200,25,383,23,307,306,302,226,208,354,23,480,105,351,59,36,61,356,283,250,308,114,107,127,310,390,284,98,441,166,172,503,497,69,345,258,206,319,255,361,363,171,362,8,318,87,348,244,239,115,177,146,217,85,417,93,220,159,204,316,29,326,285,183,152,167,303,320,377,329,429,477,297,136,47, + 37,46,367,365,379,335,119,48,168,279,363,131,201,35,60,273,184,450,437,412,276,291,312,45,474,185,72,156,366,401,29,141,178,231,180,1,473,256,417,483,350,71,238,148,357,278,388,443,455,275,501,355,341,125,307,299,76,215,282,369,41,445,408,445,67,3,168,393,115,431,322,39,67,157,493,152,318,141,357,389,4,246,116,33,504,41,485,257,339,56,67,165,215,377,435,354,381,260,110,353, + 424,104,309,338,436,329,445,431,414,151,359,56,463,360,14,96,128,104,196,504,413,31,138,425,176,365,49,318,88,198,119,489,21,116,284,341,361,200,470,261,5,374,49,102,229,328,293,272,377,310,39,422,274,294,167,363,477,423,225,148,272,357,40,94,187,241,391,450,40,445,126,322,35,314,364,381,182,276,11,491,178,474,81,380,360,255,150,28,178,501,191,262,383,167,251,429,179,180,345,13, + 73,335,379,255,423,35,358,390,79,228,284,398,156,442,292,401,391,236,311,324,200,140,265,151,342,349,501,352,304,28,107,388,204,464,235,24,378,20,104,14,437,449,170,343,405,474,167,249,418,30,232,479,423,262,453,348,337,368,168,15,110,231,478,386,13,22,5,198,319,2,190,13,231,292,441,446,80,420,265,483,280,395,229,299,325,292,282,456,116,303,308,254,5,501,167,187,350,228,262,298, + 28,100,21,500,79,41,96,252,382,31,2,375,78,300,51,420,230,234,325,96,9,486,136,120,51,507,165,93,304,62,316,444,354,78,224,257,411,289,209,351,448,104,173,198,444,219,15,88,439,112,200,341,279,109,34,482,293,36,367,205,265,188,431,23,103,452,37,274,131,277,197,205,282,355,38,296,74,198,205,392,91,325,52,342,506,255,349,136,121,59,472,167,480,346,73,172,428,141,262,86, + 175,362,402,122,227,332,371,286,185,184,118,49,53,243,222,301,336,123,107,146,55,213,95,310,298,408,148,423,476,183,341,390,257,430,96,218,462,138,376,344,215,304,289,56,69,311,243,131,139,42,428,233,255,170,211,237,120,410,350,393,225,207,297,103,223,364,101,415,223,373,504,78,31,462,209,345,170,126,72,326,176,67,168,35,511,110,251,117,37,197,242,219,139,307,2,484,461,71,242,502, + 139,160,471,442,92,319,251,354,228,223,263,84,416,430,66,134,395,192,81,68,437,457,391,511,81,127,301,315,508,475,262,31,225,291,159,53,417,141,292,191,221,457,21,402,352,305,471,373,88,462,463,484,55,176,309,427,326,352,465,68,139,240,482,428,253,474,305,36,393,500,396,215,108,295,224,299,453,55,376,313,202,51,487,287,325,386,113,456,53,74,282,456,269,498,324,454,47,462,395,207, + 69,64,503,388,501,474,221,209,63,162,92,312,212,184,404,190,443,109,481,314,410,130,251,234,401,445,178,487,444,151,447,489,396,269,411,165,285,456,65,142,95,441,501,109,211,269,347,413,247,460,302,190,430,485,71,132,391,369,73,188,198,47,388,271,242,370,64,312,424,200,112,451,342,57,467,469,169,227,143,305,259,510,164,434,47,302,450,213,166,100,148,393,420,96,184,143,310,495,44,332, + 262,268,153,36,129,316,272,301,382,458,458,117,325,498,407,337,229,103,429,170,150,196,225,214,131,250,283,225,308,502,274,101,382,258,88,175,236,234,450,487,42,168,484,230,57,114,149,194,39,167,437,336,2,63,465,224,217,171,87,492,146,216,124,497,98,40,447,339,71,146,238,238,362,56,167,242,347,380,376,33,396,219,28,421,184,473,423,498,301,45,432,208,200,507,348,110,233,122,412,127, + 416,158,12,460,35,161,254,391,148,221,82,99,334,387,472,429,313,347,478,97,132,3,198,368,327,397,248,158,24,332,326,101,455,148,244,412,251,278,332,148,241,431,489,82,309,39,477,70,181,51,284,266,348,126,505,318,409,85,340,485,120,464,4,169,454,277,117,306,493,33,129,417,52,178,105,221,476,79,409,184,245,267,288,160,280,42,153,24,178,281,32,31,171,327,458,467,247,306,336,370, + 508,296,343,418,86,451,406,17,267,298,152,224,220,349,204,91,218,263,243,29,9,425,343,425,110,331,496,416,501,404,222,472,112,356,441,488,178,380,85,373,112,137,76,172,307,502,393,386,76,437,374,404,426,492,449,194,187,294,122,434,221,200,482,133,144,274,194,455,70,467,20,472,419,8,468,73,207,325,253,446,442,429,362,249,52,497,365,124,495,167,130,16,347,76,244,65,235,257,159,361, + 447,86,220,233,152,205,245,266,295,199,5,130,384,274,214,222,377,267,215,196,313,209,305,341,500,474,58,196,12,460,401,103,297,139,460,226,417,2,203,443,35,64,182,212,381,174,191,501,184,324,300,115,119,371,240,66,357,226,433,367,346,315,22,406,271,185,82,208,105,179,71,472,271,369,87,501,52,164,423,114,72,497,424,473,414,426,469,66,48,78,301,249,168,312,202,170,287,390,99,90, + 310,282,218,226,267,262,45,23,375,442,500,355,130,42,471,31,332,303,184,253,262,169,290,27,229,444,371,399,307,306,359,182,52,59,360,233,192,323,137,91,367,393,319,76,304,337,61,270,417,404,123,105,497,37,306,381,276,15,93,439,370,124,121,95,317,23,82,345,470,219,191,87,454,217,80,227,381,502,109,306,195,113,236,385,496,11,488,45,144,259,82,399,107,266,433,416,440,174,86,427, + 288,93,191,72,180,106,275,479,408,4,107,505,133,140,420,246,62,38,273,500,78,379,486,81,55,163,235,8,147,228,446,438,258,452,413,298,277,508,480,327,487,366,124,178,233,323,174,100,235,369,329,14,2,136,354,153,384,448,2,360,26,404,370,259,337,281,494,236,344,87,83,98,435,317,170,253,109,94,466,325,474,255,164,474,309,359,348,91,227,493,438,457,454,180,382,355,88,222,4,351, + 461,239,153,170,494,61,398,243,374,317,186,172,308,464,258,512,460,111,163,404,190,146,97,56,447,184,261,197,181,193,457,383,501,195,366,204,188,342,389,108,3,508,497,487,382,64,403,185,155,365,357,126,145,198,17,327,4,505,25,280,495,278,373,89,477,113,16,98,285,392,416,353,44,119,412,82,509,241,112,135 + }; + +} // namespace + + +namespace AutogenLDPC_QC_Rate33_block6k { + + constexpr inline std::size_t M = 64; + constexpr inline std::size_t N = 192; + constexpr inline std::size_t num_nz = 800; + constexpr inline std::size_t expansion_factor = 32; + + constexpr inline std::array colptr = { + 0x0,0x2,0x5,0xd,0xf,0x12,0x19,0x1b,0x1e,0x26,0x28,0x2b,0x32,0x34,0x37,0x3f,0x41,0x44,0x4b,0x4d,0x50,0x58,0x5a,0x5d,0x64,0x66,0x69,0x71,0x73,0x76,0x7d,0x7f,0x82,0x8a,0x8c,0x8f,0x96,0x98,0x9b,0xa3,0xa5,0xa8,0xaf,0xb1,0xb4,0xbc,0xbe,0xc1,0xc8,0xca,0xcd,0xd5,0xd7,0xda,0xe1,0xe3,0xe6,0xee,0xf0,0xf3,0xfa,0xfc,0xff,0x107,0x109,0x10c,0x113,0x115,0x118,0x120,0x122,0x125,0x12c,0x12e,0x131,0x139,0x13b,0x13e,0x145,0x147,0x14a,0x152,0x154,0x157,0x15e,0x160,0x163,0x16b,0x16d,0x170,0x177,0x179,0x17c,0x184,0x186,0x189,0x190,0x192,0x195,0x19d, + 0x19f,0x1a2,0x1a9,0x1ab,0x1ae,0x1b6,0x1b8,0x1bb,0x1c2,0x1c4,0x1c7,0x1cf,0x1d1,0x1d4,0x1db,0x1dd,0x1e0,0x1e8,0x1ea,0x1ed,0x1f4,0x1f6,0x1f9,0x201,0x203,0x206,0x20d,0x20f,0x212,0x21a,0x21c,0x21f,0x226,0x228,0x22b,0x233,0x235,0x238,0x23f,0x241,0x244,0x24c,0x24e,0x251,0x258,0x25a,0x25d,0x265,0x267,0x26a,0x271,0x273,0x276,0x27e,0x280,0x283,0x28a,0x28c,0x28f,0x297,0x299,0x29c,0x2a3,0x2a5,0x2a8,0x2b0,0x2b2,0x2b5,0x2bc,0x2be,0x2c1,0x2c9,0x2cb,0x2ce,0x2d5,0x2d7,0x2da,0x2e2,0x2e4,0x2e7,0x2ee,0x2f0,0x2f3,0x2fb,0x2fd,0x300,0x307,0x309,0x30c,0x314,0x316,0x319,0x320 + }; + +// ------------------------------------------------------- + + constexpr inline std::array row_idx = { + 0xe,0x1f,0x11,0x15,0x2d,0x0,0xd,0x17,0x1e,0x23,0x24,0x2d,0x34,0x19,0x2d,0xd,0x1f,0x3c,0x0,0x17,0x20,0x26,0x31,0x34,0x35,0x10,0x35,0x13,0x19,0x1f,0x0,0x7,0xa,0x12,0x13,0x1b,0x21,0x22,0xd,0x2b,0x2b,0x2e,0x35,0x7,0x8,0xf,0x15,0x32,0x34,0x3c,0x2,0x5,0xf,0x21,0x2d,0x2,0x3,0x13,0x1b,0x22,0x24,0x3a,0x3f,0x1f,0x29,0x5,0x10,0x39,0xb,0xc,0x12,0x16,0x1f,0x31,0x38,0x29,0x3a,0x9,0xb,0x33,0x5,0xa,0x10,0x1a,0x23,0x27,0x2f,0x34,0x5,0x13,0x34,0x35,0x3f,0xe,0x1b,0x26,0x29,0x30,0x36,0x3b, + 0x12,0x1d,0x1,0xf,0x1b,0x0,0xb,0x11,0x13,0x17,0x18,0x1c,0x36,0x1,0x37,0x2,0xd,0xf,0x3,0x6,0x11,0x1e,0x1f,0x38,0x3a,0x1c,0x31,0x21,0x25,0x31,0xe,0x15,0x1c,0x1f,0x2b,0x2e,0x31,0x36,0x1d,0x27,0x1e,0x27,0x29,0xa,0x11,0x1a,0x1d,0x20,0x2a,0x2d,0x2e,0x3d,0x5,0x2f,0x39,0xa,0x1b,0x1c,0x1d,0x26,0x30,0x31,0x39,0x15,0x31,0x5,0x11,0x1a,0x0,0x2,0x1e,0x21,0x23,0x24,0x3b,0xf,0x18,0x29,0x33,0x37,0xb,0x10,0x15,0x1f,0x23,0x38,0x3a,0x3e,0xb,0x19,0xc,0x1d,0x31,0x1,0x8,0x19,0x24,0x25,0x28,0x2e, + 0x2a,0x3b,0x11,0x1d,0x39,0x8,0x9,0xe,0x11,0x2b,0x2c,0x32,0x35,0x3,0x23,0xb,0xe,0x31,0x8,0xa,0xd,0xf,0x1c,0x2c,0x37,0x0,0x27,0x27,0x35,0x37,0x0,0x5,0x7,0x11,0x20,0x2e,0x37,0x3e,0xf,0x3b,0x9,0x1c,0x2b,0x2,0x11,0x18,0x20,0x24,0x33,0x3f,0x1e,0x2f,0x3,0x1d,0x3f,0x6,0xf,0x10,0x1a,0x20,0x25,0x2b,0x2d,0xb,0x2d,0x6,0x21,0x3d,0xc,0x17,0x1a,0x1d,0x22,0x2a,0x39,0x4,0x15,0xd,0x17,0x35,0x4,0xf,0x16,0x18,0x21,0x28,0x29,0x3d,0x1,0x1f,0x2f,0x32,0x3f,0x4,0xf,0x13,0x1c,0x23,0x28,0x2a, + 0x1,0xc,0x3,0x33,0x3b,0x5,0x7,0x1a,0x26,0x28,0x2d,0x34,0x35,0x9,0x3d,0x1f,0x2a,0x39,0xe,0x12,0x1f,0x27,0x2c,0x37,0x3e,0x1a,0x25,0x15,0x1d,0x39,0x8,0x9,0x19,0x1b,0x22,0x29,0x38,0x3a,0x35,0x3f,0x9,0x29,0x38,0x1,0xb,0x10,0x22,0x25,0x28,0x2e,0x17,0x36,0x1f,0x29,0x3d,0x2,0x8,0x9,0x17,0x18,0x2c,0x39,0x3d,0x5,0x21,0x2d,0x30,0x33,0x0,0x14,0x18,0x1e,0x21,0x2d,0x3d,0x20,0x33,0x5,0xd,0x2b,0x12,0x28,0x2a,0x2f,0x32,0x33,0x3b,0x3f,0x25,0x2b,0xf,0x2c,0x2d,0x5,0x7,0x10,0x2e,0x3a,0x3c,0x3d, + 0x1b,0x30,0x3,0x7,0x19,0x7,0xd,0x14,0x16,0x1d,0x21,0x38,0x3e,0x9,0x3b,0x8,0x19,0x27,0x4,0x8,0x10,0x15,0x1e,0x37,0x3b,0x8,0x13,0x1,0xd,0x23,0x4,0xf,0x18,0x1c,0x2b,0x35,0x37,0x3c,0x3,0x7,0x7,0x14,0x1b,0x5,0x18,0x1b,0x2b,0x2e,0x36,0x3a,0x9,0x38,0x23,0x3d,0x3f,0x8,0xb,0x25,0x2e,0x30,0x31,0x35,0x3c,0xd,0x33,0x21,0x23,0x24,0xa,0x19,0x1b,0x1c,0x34,0x38,0x39,0x23,0x32,0x9,0x1f,0x21,0x2,0x4,0x1f,0x20,0x32,0x33,0x37,0x3d,0x1b,0x1d,0x19,0x2f,0x3e,0x2,0x13,0x14,0x15,0x18,0x24,0x3f, + 0x2c,0x39,0x5,0x25,0x3b,0x6,0x11,0x12,0x16,0x32,0x33,0x37,0x3b,0x15,0x37,0x1d,0x25,0x28,0x0,0x2,0x12,0x14,0x21,0x2f,0x33,0x3,0x3c,0x9,0x2f,0x3f,0x1,0x9,0xc,0x17,0x19,0x1e,0x2c,0x3e,0x7,0x27,0x13,0x16,0x1b,0xe,0x1d,0x22,0x26,0x29,0x2d,0x30,0x21,0x24,0x23,0x37,0x3d,0x1,0x4,0x6,0xd,0x1a,0x1d,0x1e,0x25,0xf,0x33,0x13,0x3a,0x3b,0x1,0x23,0x2b,0x2c,0x32,0x38,0x3c,0x14,0x2b,0x17,0x19,0x2d,0xf,0x16,0x1f,0x22,0x27,0x2a,0x2d,0x30,0x17,0x25,0x33,0x36,0x37,0x6,0x9,0x10,0x25,0x2c,0x32,0x3d, + 0x6,0x2d,0xb,0x17,0x27,0xe,0x12,0x15,0x28,0x36,0x39,0x3b,0x3d,0x17,0x35,0x11,0x18,0x25,0x4,0x9,0xb,0xd,0x20,0x28,0x32,0xb,0x16,0x7,0x25,0x27,0xa,0xc,0x19,0x27,0x2a,0x2e,0x3b,0x3f,0x1b,0x2f,0x4,0xb,0x23,0x3,0x9,0xc,0x17,0x1c,0x30,0x3e,0x26,0x37,0x1,0x13,0x31,0x3,0xc,0x14,0x1d,0x24,0x27,0x33,0x3a,0x13,0x31,0x15,0x17,0x26,0x6,0xa,0x16,0x22,0x2b,0x2f,0x33,0x7,0x22,0x29,0x2b,0x2f,0x2,0x10,0x14,0x19,0x2f,0x36,0x39,0x3f,0x39,0x3f,0x3,0x22,0x37,0x4,0x7,0x13,0x27,0x30,0x34,0x36, + 0xd,0x28,0x1b,0x31,0x3b,0x1,0x3,0x14,0x21,0x25,0x26,0x2a,0x3c,0x11,0x3d,0x1,0xa,0x3d,0x14,0x16,0x26,0x2f,0x35,0x3a,0x3f,0x19,0x34,0xb,0x15,0x2b,0x3,0xd,0x13,0x29,0x30,0x34,0x38,0x3c,0x21,0x39,0x1,0x12,0x17,0x5,0x6,0xe,0x1a,0x29,0x31,0x3c,0x11,0x3e,0xf,0x11,0x1b,0x5,0xb,0xc,0x1e,0x24,0x26,0x29,0x2f,0x23,0x29,0x0,0x3,0x3b,0xd,0x12,0x19,0x2a,0x35,0x36,0x3e,0xa,0x3f,0x7,0x13,0x35,0x1,0x6,0xe,0x15,0x20,0x23,0x2c,0x31,0x11,0x2f,0x7,0x15,0x20,0x3,0xc,0x16,0x1a,0x27,0x39,0x3e + + }; + + +// ------------------------------------------------------- + + constexpr inline std::array values = { + 27,3,1,30,24,28,28,22,24,30,31,25,3,11,9,18,11,30,28,26,10,9,23,23,8,5,32,31,19,17,20,12,31,13,9,9,20,22,26,30,27,22,14,17,4,7,10,16,9,28,2,12,5,2,24,9,25,6,28,5,31,22,2,5,13,22,24,19,11,11,24,26,28,4,24,23,20,23,5,31,28,27,23,13,14,6,20,12,25,27,3,10,2,32,22,13,14,27,32,9, + 4,15,25,10,4,7,14,31,25,22,18,2,7,5,10,2,2,2,17,10,32,13,28,1,6,4,31,3,22,23,31,20,4,28,19,11,23,13,15,23,22,19,1,29,2,17,3,11,25,30,28,1,10,13,21,31,15,13,23,5,7,28,7,16,9,6,16,19,22,16,32,4,11,16,26,28,31,27,32,19,23,24,1,7,7,10,12,10,18,32,11,13,29,4,9,4,22,24,4,23, + 25,32,6,31,13,3,22,22,4,12,8,27,9,5,14,6,26,30,30,24,24,26,25,13,2,28,18,13,31,10,1,17,10,29,31,30,10,16,17,8,2,13,13,14,11,19,18,13,10,19,17,17,23,1,18,27,25,27,10,5,3,31,28,23,26,9,6,27,30,7,6,15,7,28,21,6,3,13,6,16,32,16,2,10,23,18,19,20,6,7,18,26,3,29,1,23,30,3,8,3, + 25,6,12,21,10,26,31,12,21,6,10,26,8,20,7,29,22,28,32,11,25,15,21,28,3,1,5,16,24,9,18,20,6,8,3,9,18,29,16,5,24,30,4,7,7,13,24,25,2,16,9,32,15,3,5,29,21,24,13,6,13,22,2,2,25,21,1,10,18,19,20,1,29,28,28,16,12,16,10,7,7,17,21,5,2,8,10,1,3,28,16,22,28,10,22,23,2,15,12,14, + 4,19,25,10,25,26,24,7,20,29,14,6,24,6,23,24,28,21,10,3,32,27,27,22,14,17,30,16,16,21,28,17,14,20,4,16,24,5,9,3,9,14,14,20,12,9,16,5,23,10,30,4,15,14,16,15,16,24,20,24,5,26,23,30,25,2,1,1,28,30,22,25,30,23,18,17,3,10,16,6,19,30,16,2,6,20,30,32,30,25,27,32,31,9,28,30,2,25,25,18, + 11,32,6,30,9,25,16,8,27,18,27,4,18,13,9,29,29,12,25,7,21,24,22,5,30,24,23,17,15,5,4,30,8,5,20,4,9,16,2,4,22,22,13,5,27,22,20,10,24,25,26,4,32,2,6,29,19,15,9,31,5,30,21,16,29,14,20,25,12,24,24,10,23,23,13,20,13,18,5,14,30,5,26,15,19,6,26,15,17,30,26,17,18,12,25,18,1,17,13,29, + 32,25,2,15,24,19,17,23,32,25,24,21,29,16,9,10,2,23,23,28,29,14,28,5,9,15,1,23,13,3,29,25,24,27,18,22,27,27,21,29,28,16,27,7,7,12,4,29,13,9,28,18,20,21,6,2,28,12,27,4,16,23,22,4,27,3,18,7,12,17,22,19,6,11,11,9,25,26,24,23,16,29,21,30,20,7,16,29,20,18,4,23,9,1,2,13,30,7,7,19, + 21,27,20,32,4,20,17,13,29,19,16,18,15,17,6,28,24,28,5,8,17,5,11,27,18,20,12,25,27,21,20,27,22,18,2,26,7,9,16,20,10,10,24,9,23,5,23,31,11,25,28,1,17,16,12,3,31,29,27,29,21,14,29,25,12,13,8,29,17,16,31,2,2,14,7,30,2,27,31,12,4,30,3,32,30,4,10,4,31,29,9,21,2,14,13,9,22,8,22,17 + + }; + +} // namespace + +#endif //QKD_POSTPROC_BOB_AUTOGEN_LDPC_QC_HPP diff --git a/src/encoder.hpp b/src/encoder.hpp index 2566c3b..5ba30e7 100644 --- a/src/encoder.hpp +++ b/src/encoder.hpp @@ -4,6 +4,8 @@ // The decoder can also encode and has a much cleaner interface. // Basically, this file just implements matrix-vector multiplication in compressed sparse column (CSC) storage format. // +// For a more user-friendly version using storage of QC-exponents directly, see `encoder_advanced.hpp`. +// TODO This file will be deprecated and removed in a future version. #ifndef LDPC4QKD_ENCODER_HPP #define LDPC4QKD_ENCODER_HPP @@ -39,6 +41,16 @@ namespace LDPC4QKD { } + //```julia + // H = SparseMatrixCSC(...) + // function encode(in, out) + // for col=1:length(in) + // for j = H.colptr[col]:(H.colptr[col+1]-1) + // out[H.rowval[j]] = xor(out[H.rowval[j]], in[col]) + // end + // end + // end + //``` template constexpr void encode(const std::array &in, std::array &out) { using namespace AutogenLDPC; @@ -75,8 +87,8 @@ namespace LDPC4QKD { // have to modify values to mark which bits are used during rate adaption // Also need -1 as a value, which has special purpose below. - std::array syndrome_copy; // uninitialized because filled via copy right below - std::copy(syndrome.begin(), syndrome.end(), syndrome_copy.begin()); + std::array syndrome_copy{}; // initialization is optimized out! + std::copy(syndrome.cbegin(), syndrome.cend(), syndrome_copy.begin()); // TODO look if this can't be done in one line static_assert(reduced_size < AutogenLDPC::M, "Requested rate adapted syndrome size must be less than the original syndrome size."); @@ -149,20 +161,8 @@ namespace LDPC4QKD { } - /// TODO in-place computation may require moving bits around a lot. Think about how to achieve it efficiently. -// /// TODO allow runtime determination of reduced_size!!! -// /// This is just for testing and seeing the difference in performance -// template -// constexpr void rate_adapt_inplace(const std::array &syndrome) { -// -// } -// -// /// TODO allow runtime determination of reduced_size!!! -// /// This is just for testing and seeing the difference in performance -// template -// constexpr void rate_adapt_inplace(const std::array &syndrome, const std::size_t reduced_size) { -// -// } + // TODO in-place computation may require moving bits around a lot. + // Think about how to achieve it efficiently and if we need it... } // namespace RALDPC diff --git a/src/encoder_advanced.hpp b/src/encoder_advanced.hpp new file mode 100644 index 0000000..389f706 --- /dev/null +++ b/src/encoder_advanced.hpp @@ -0,0 +1,346 @@ +// +// Created by Adomas Baliuka on 18.04.24. +// +// See unit tests (`test_encoder_advanced.cpp`) for an example of how to use this. +// Note: this file uses C++20 features! + +#ifndef QKD_POSTPROC_BOB_ENCODER_ADVANCED_HPP +#define QKD_POSTPROC_BOB_ENCODER_ADVANCED_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "autogen_ldpc_QC.hpp" + +void noise_bitstring_inplace(auto &src, double err_prob, unsigned int seed = 0) { + std::mt19937_64 rng{seed}; // hard-coded seed for testing purposes. + + std::bernoulli_distribution distribution(err_prob); + + for (std::size_t i = 0; i < src.size(); i++) { + if (distribution(rng)) { + src[i] = !src[i]; + } else { + src[i] = src[i]; + } + } +} + +template +constexpr auto bits_needed() { + auto n = N; + std::size_t number_of_bits{0ul}; + while (n > 0) { + n >>= 1; + number_of_bits++; + } + return number_of_bits; +} + +template +using smallest_type = std::conditional_t() <= 8 * sizeof(std::uint8_t), std::uint8_t, + std::conditional_t() <= 8 * sizeof(std::uint16_t), std::uint16_t, + std::conditional_t() <= 8 * sizeof(std::uint32_t), std::uint32_t, std::uint64_t>>>; + + +namespace LDPC4QKD { + + template + constexpr bool xor_as_bools(Bit1 lhs, Bit2 rhs) { + return (static_cast(lhs) != static_cast(rhs)); + } + + struct FixedSizeInputOutput { + [[nodiscard]] virtual std::size_t get_input_size() const = 0; + + [[nodiscard]] virtual std::size_t get_output_size() const = 0; + + virtual constexpr ~FixedSizeInputOutput() = default; + }; + + template + struct ComputablePosVar : public FixedSizeInputOutput { + [[nodiscard]] virtual std::vector> get_pos_varn() const = 0; + + constexpr ~ComputablePosVar() override = default; + }; + + + template< + typename bit_type, /// e.g. bool, or std::uint8_t + std::size_t output_size, std::size_t input_size> + struct FixedSizeEncoder : public ComputablePosVar> { + static constexpr std::size_t outputSize = output_size; + static constexpr std::size_t inputSize = input_size; + + [[nodiscard]] std::size_t get_input_size() const override { + return inputSize; + } + + [[nodiscard]] std::size_t get_output_size() const override { + return output_size; + } + + /// performant but no size check (user has to provide valid std::span) + /// key shall not be changed! + /// also: inputs/outputs have to be contiguous in memory. + virtual void encode_span( + std::span key, + std::span syndrome) const = 0; + + /// general, with runtime size check + /// (a runtime cost usually not worth worrying about) + void encode(auto const &key, auto &syndrome) const { + if (key.size() == input_size && syndrome.size() == output_size) { + encode_span( + std::span{key}, + std::span{syndrome}); + } else { + std::stringstream s; + s << "LDPC encoder: incorrect sizes of intput / output arrays\n" + << "RECEIVED: key.size() = " << key.size() << ". " << "syndrome.size() = " << syndrome.size() << ".\n" + << "EXPECTED: key.size() = " << input_size << ". " << "syndrome.size() = " << output_size << ".\n"; + throw std::out_of_range(s.str()); + } + } + + // Have to write explicitly for some gcc versions. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413 + constexpr ~FixedSizeEncoder() override = default; + }; + + template< + typename bit_type, /// e.g. bool, or std::uint8_t + std::size_t M, std::size_t N, std::size_t expansion_factor, std::size_t num_nz, + std::integral coptr_uintx_t, std::integral row_idx_uintx_t, std::integral values_uintx_t> + struct FixedSizeEncoderQC : public FixedSizeEncoder { + + constexpr FixedSizeEncoderQC(std::array colptr, + std::array row_idx, + std::array values) : + colptr(colptr), row_idx(row_idx), values(values) { + if (!matrix_consistent_with_input_size()) { + // Note: this will show up as a compile-time error if the constructor is called at compile time! + // The error will not have the exception text, just say that throwing is disallowed at compile time! + // If you get "error: expression ‘’ is not a constant expression", + // this is still the error though! + throw std::runtime_error("Inputs would make encoder that performs out-of-memory access!"); + } + } + + void encode_span( + std::span key, + std::span syndrome) const override { + encode_qc(key, syndrome); + } + + // Have to write explicitly for some gcc versions. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413 + constexpr ~FixedSizeEncoderQC() override = default; + + + using idx_t = smallest_type; + + [[nodiscard]] std::vector> get_pos_varn() const override { + const auto n_cols = FixedSizeEncoderQC::inputSize; + const auto n_rows = FixedSizeEncoderQC::outputSize; + + std::vector> pos_varn{}; + pos_varn.assign(n_rows, std::vector{}); + + for (idx_t col = 0; col < n_cols; col++) { + auto QCcol = col / expansion_factor; // column index into matrix of exponents + for (auto j = colptr[QCcol]; j < colptr[QCcol + 1]; j++) { + auto shiftVal = values[j]; + auto QCrow = row_idx[j]; // row index into matrix of exponents + // computes `outIdx`, which is the unique row index (into full matrix) at which there is a `1` + // arising from the current sub-block. + // The sub-block is determined by the QC-exponent `shiftVal`. + // Add the base row-index of the current sub-block to the shift + auto outIdx = (expansion_factor * QCrow) + ((col - shiftVal) % expansion_factor); + pos_varn[outIdx].push_back(static_cast(col)); + } + } + + return pos_varn; + } + + private: + /// checks that a constexpr QC-encoder will never access input or output arrays outside bounds. + /// I.e., for the input the size is `expansion_factor*N` while output size is `expansion_factor*M`. + /// NOTE: IF THIS RETURNS FALSE, THE OBJECT IS INVALID!!! + [[nodiscard]] constexpr bool matrix_consistent_with_input_size() const { + for (std::size_t col = 0; col < N; col++) { + auto QCcol = col / expansion_factor; // column index into matrix of exponents + for (std::size_t j = colptr[QCcol]; j < colptr[QCcol + 1]; j++) { + auto shiftVal = values[j]; + auto QCrow = row_idx[j]; // row index into matrix of exponents + // computes `outIdx`, which is the unique row index (into full matrix) at which there is a `1` + // arising from the current sub-block. + // The sub-block is determined by the QC-exponent `shiftVal`. + // Add the base row-index of the current sub-block to the shift + auto outIdx = (expansion_factor * QCrow) + ((col - shiftVal) % expansion_factor); + + if (outIdx >= M * expansion_factor || col >= N * expansion_factor) { + return false; + } + } + } + return true; + } + + void encode_qc( + std::span in, + std::span out) const { + + static_assert(N >= M, "The syndrome should be shorter than the input bitstring."); + + for (std::size_t col = 0; col < in.size(); col++) { + auto QCcol = col / expansion_factor; // column index into matrix of exponents + for (std::size_t j = colptr[QCcol]; j < colptr[QCcol + 1]; j++) { + auto shiftVal = values[j]; + auto QCrow = row_idx[j]; // row index into matrix of exponents + // computes `outIdx`, which is the unique row index (into full matrix) at which there is a `1` + // arising from the current sub-block. + // The sub-block is determined by the QC-exponent `shiftVal`. + // Add the base row-index of the current sub-block to the shift + auto outIdx = (expansion_factor * QCrow) + ((col - shiftVal) % expansion_factor); + + out[outIdx] = xor_as_bools(out[outIdx], in[col]); + } + } + } + + std::array colptr; + std::array row_idx; + std::array values; + }; + + +/// don't waste your time reading this... +/// just reduces the number of templates that needs to be specified for the `FixedSizeEncoderQC<...>` constructor + template + consteval auto helper_create_FixedSizeEncoderQC(auto colptr, auto row_idx, auto values) { + using bit_type = std::uint8_t; + constexpr auto num_nz = values.size(); + constexpr auto N = colptr.size() - 1; + using FixedSizeEncoderQC_inst = FixedSizeEncoderQC; + return FixedSizeEncoderQC_inst{colptr, row_idx, values}; + } + + // TODO rename + constexpr auto encoder1 = helper_create_FixedSizeEncoderQC< + AutogenLDPC_QC::M, AutogenLDPC_QC::expansion_factor>( + AutogenLDPC_QC::colptr, AutogenLDPC_QC::row_idx, AutogenLDPC_QC::values); + + constexpr auto encoder2 = helper_create_FixedSizeEncoderQC< + AutogenLDPC_QC_Rate33_block6k::M, AutogenLDPC_QC_Rate33_block6k::expansion_factor>( + AutogenLDPC_QC_Rate33_block6k::colptr, AutogenLDPC_QC_Rate33_block6k::row_idx, + AutogenLDPC_QC_Rate33_block6k::values); + + constexpr auto encoder_1M = helper_create_FixedSizeEncoderQC< + AutogenLDPC_QC_1MRhalf::M, AutogenLDPC_QC_1MRhalf::expansion_factor>( + AutogenLDPC_QC_1MRhalf::colptr, AutogenLDPC_QC_1MRhalf::row_idx, AutogenLDPC_QC_1MRhalf::values); + + +// TODO add all other codes + + + constexpr std::tuple all_encoders_tuple{encoder1, encoder2, encoder_1M}; + + //! Encodes the `key` using the LDPC code specified by the `code_id`. The result is the syndrome. + //! Note: if `code_id` known at compile time, use templated version instead! + //! + //! NOTE: Containers will be converted to a `std::span` internally. + //! Sizes of `key` and `result` are checked at runtime and must match exactly, otherwise an exception is thrown. + //! + //! For **containers with compile-time known sizes**, using an incorrect size may also give a COMPILE ERROR. + //! (something like "no matching function for call to ‘std::span<...>::span(...)"). + //! When using such containers, the input and output sizes must exactly match ALL available codes + //! (which is usually impossible when there are several different codes). + //! If `code_id` is known at compile time, use templated version instead! + //! If `code_id` isn't known at compile time, + //! then use `std::vector` and `FixedSizeEncoder::outputSize`, `FixedSizeEncoder::inputSize` + //! to get the size exactly right. + //! Alternatively, to avoid this check completely, use `std::span` + //! and make sure manually that you own enough memory (otherwise you get out-of-memory access!). + //! + //! \tparam N internal implementation detail, need not use. + //! \param code_id integer index into tuple of codes. Make sure both sides agree on these! + //! \param key Contiguous container (e.g. `std::vector`, `std::array`, `std::span`) of bits (e.g. `bool` or `uint8_t`). + //! \param result Contiguous container (e.g. `std::vector`, `std::array`, `std::span`) of bits (e.g. `bool` or `uint8_t`). + // Used to store syndrome. Must already be sized correctly for the given code! + template + void encode_with(std::size_t code_id, auto const &key, auto &result) { + if (N == code_id) { + std::get(all_encoders_tuple).encode(key, result); + return; + } + + if constexpr (N + 1 < std::tuple_size_v) { + return encode_with(code_id, key, result); + } + } + + //! Encodes the `key` using the LDPC code specified by the `code_id`. + //! The result is the syndrome. Note: code id must be known at compile time. + //! For runtime inference, use `encode_with(std::size_t code_id, auto const& key, auto &result)` + //! + //! NOTE: Containers will be converted to a `std::span` internally. + //! Sizes of `key` and `result` are checked at runtime and must match exactly, otherwise an exception is thrown. + //! For containers with compile-time known sizes, using an incorrect size may also give a COMPILE ERROR. + //! Use `FixedSizeEncoder::outputSize` and `FixedSizeEncoder::inputSize` to allocate correctly sized arrays + //! (to avoid this check, use `std::span` and make sure manually that you own enough memory!). + //! + //! \tparam code_id integer index into tuple of codes. Make sure both sides agree on these! + //! \param key Contiguous container (e.g. `std::vector`, `std::array`, `std::span`) of bits (e.g. `bool` or `uint8_t`). + //! \param result Contiguous container (e.g. `std::vector`, `std::array`, `std::span`) of bits (e.g. `bool` or `uint8_t`). + //! Used to store syndrome. Must already be sized correctly for the given code! + template + void encode_with(auto const &key, auto &result) { + std::get(all_encoders_tuple).encode(key, result); + } + + //! Get input size of code with given ID. + //! + //! \tparam N internal implementation detail, need not use. + //! \param code_id integer index into tuple of codes. Make sure both sides agree on these! + template + std::size_t get_input_size(std::size_t code_id) { + if (N == code_id) { + return std::get(all_encoders_tuple).get_input_size(); + } + + if constexpr (N + 1 < std::tuple_size_v) { + return get_input_size(code_id); + } + return 0; // this should never happen + } + + //! Get input size of code with given ID. + //! + //! \tparam N internal implementation detail, need not use. + //! \param code_id integer index into tuple of codes. Make sure both sides agree on these! + template + std::size_t get_output_size(std::size_t code_id) { + if (N == code_id) { + return std::get(all_encoders_tuple).get_output_size(); + } + + if constexpr (N + 1 < std::tuple_size_v) { + return get_output_size(code_id); + } + return 0; // this should never happen + } + +} + + +#endif //QKD_POSTPROC_BOB_ENCODER_ADVANCED_HPP diff --git a/src/rate_adaptive_code.hpp b/src/rate_adaptive_code.hpp index fbf19a3..1da5252 100644 --- a/src/rate_adaptive_code.hpp +++ b/src/rate_adaptive_code.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include @@ -31,32 +30,41 @@ namespace LDPC4QKD { + /*! + * compute log-likelihood-ratios for a given keye and channel parameter + * @tparam Bit e.g. std::uint8_t or bool + * @param bitstring string of bits + * @param bsc_channel_parameter bit-flip probability of binary symmetric channel (BSC) + * @return log-likelyhoods corresponding to input bitstring + */ + template + std::vector llrs_bsc(const std::vector &bitstring, const double bsc_channel_parameter) { + double vlog = log((1 - bsc_channel_parameter) / bsc_channel_parameter); + std::vector llrs(bitstring.size()); + for (std::size_t i{}; i < llrs.size(); ++i) { + llrs[i] = vlog * (1 - 2 * bitstring[i]); // log likelihood ratios + } + return llrs; + } + /*! * Belief propagation (BP) decoder for binary low density parity check (LDPC) codes. * Supports rate adaption (reducing the number of LDPC matrix rows). * Intended for distributed source coding (a.k.a. Slepian-Wolf coding). * LDPC code is stored in sparse column storage (CSC) format. * - * @tparam Bit type of matrix entires (only values zero and one are used, default to bool) - * @tparam colptr_t unsigned integer type that fits ("number of non-zero matrix entries" + 1) + * (TODO use concept `std::unsigned_integral` when using C++20) + * + * @tparam colptr_t unsigned integert type that fits ("number of non-zero matrix entries" + 1) * @tparam idx_t unsigned integer type fitting number of columns N (thus also number of rows M) */ - template class RateAdaptiveCode { public: // ------------------------------------------------------------------------------------------------ type aliases - using NodeDegree = std::uint16_t; using MatrixIndex = idx_t; using ColumnPointer = colptr_t; - using MatrixEntry = Bit; - - static_assert(!std::numeric_limits::is_signed, "Expecting unsigned integer types"); - static_assert(!std::numeric_limits::is_signed, "Expecting unsigned integer types"); - static_assert(!std::numeric_limits::is_signed, "Expecting unsigned integer types"); - static_assert(std::numeric_limits::is_integer, - "Expecting integer type (e.g. bool or uint8) for the bit values. Will use only 'true' and 'false'."); // ------------------------------------------------------------------------------------------------ constructors /*! @@ -67,16 +75,14 @@ namespace LDPC4QKD { * @param rowIdx row index array for specifying mother parity check matrix. */ RateAdaptiveCode(const std::vector &colptr, const std::vector &rowIdx) - : colptr(colptr), row_idx(rowIdx), rows_to_combine({}), - n_mother_rows(*std::max_element(rowIdx.begin(), rowIdx.end()) + 1u), + : n_mother_rows(*std::max_element(rowIdx.begin(), rowIdx.end()) + 1u), n_cols(colptr.size() - 1), - n_ra_rows(n_mother_rows) { - constexpr std::size_t n_line_combs = 0; - + mother_pos_varn(compute_mother_pos_varn(colptr, rowIdx)), // computed here and henceforth `const`! + rows_to_combine({}) { + constexpr idx_t n_line_combs = 0; recompute_pos_vn_cn(n_line_combs); } - /*! * Constructor for using the code with rate adaption. * The mother parity check matrix is stored using Compressed Sparse Column (CSC) format. @@ -89,54 +95,104 @@ namespace LDPC4QKD { * TODO add extra checks for validity of `rows_to_combine_rate_adapt` * * note: there used to be a parameter `do_elimination_check` to check for repeated node indices after rate adaption. - * Such indices are now removed during `recompute_pos_vn_cn`. Consequentially, node elliminations are allowed. + * Such indices are now removed during `recompute_pos_vn_cn`. Consequentially, node eliminations are allowed. * TODO reconsider this and remove commented-out function `has_var_node_eliminations` below * - * @param colptr colptr column pointer array for specifying mother parity check matrix. - * @param rowIdx rowIdx row index array for specifying mother parity check matrix. - * @param rows_to_combine_rate_adapt rows_to_combine_rate_adapt array of marix line indices to be combined for rate adaption - * @param initial_row_combs initial_row_combs number of line indices to combine initially + * @param colptr column pointer array for specifying mother parity check matrix. + * @param rowIdx row index array for specifying mother parity check matrix. + * @param rows_to_combine_rate_adapt array of mother-matrix line indices to be combined for rate adaption + * @param initial_row_combs number of line indices to combine initially */ - RateAdaptiveCode(const std::vector &colptr, - const std::vector &rowIdx, - const std::vector &rows_to_combine_rate_adapt, - std::size_t initial_row_combs = 0) - : colptr(colptr), row_idx(rowIdx), rows_to_combine(rows_to_combine_rate_adapt), - n_mother_rows(*std::max_element(rowIdx.begin(), rowIdx.end()) + 1u), + RateAdaptiveCode(std::vector colptr, + std::vector rowIdx, + std::vector rows_to_combine_rate_adapt, + idx_t initial_row_combs = 0) + : n_mother_rows(*std::max_element(rowIdx.begin(), rowIdx.end()) + 1u), n_cols(colptr.size() - 1), - n_ra_rows(*std::max_element(rowIdx.begin(), rowIdx.end()) + 1u - initial_row_combs / 2) { + mother_pos_varn(compute_mother_pos_varn(colptr, rowIdx)), // computed here and henceforth `const`! + rows_to_combine(std::move(rows_to_combine_rate_adapt)) { if (rows_to_combine.size() % 2 != 0) { throw std::domain_error("The number of rows to combine for rate adaption " "(size of argument array) is an odd number (expected even)."); } - if (initial_row_combs >= rows_to_combine.size() / 2) { + if (initial_row_combs > rows_to_combine.size() / 2) { throw std::domain_error("The number of desired initial row combinations for rate adaption " "is larger than the given array of lines to combine."); } + // compute current `pos_varn` and `pos_checkn` from `mother_pos_varn` recompute_pos_vn_cn(initial_row_combs); + // if (do_elimination_check && has_var_node_eliminations()) { // throw std::domain_error("Given rate adaption implies variable node eliminations. " // "Rate adaption with eliminations degrades performance. Do not use!"); // } } + /*! + * Constructor for using the code with rate adaption. + * The mother parity check matrix is stored in `mother_pos_varn` + * The rate adaption is stored as an array of matrix row indices, which are combined for rate adaption. + * + * @param mother_pos_varn input check nodes to each variable node of the mother matrix + * @param rows_to_combine_rate_adapt array of mother-matrix line indices to be combined for rate adaption + * @param initial_row_combs number of line indices to combine initially + */ + RateAdaptiveCode(std::vector> mother_pos_varn, + std::vector rows_to_combine_rate_adapt, + idx_t initial_row_combs = 0) + : n_mother_rows(mother_pos_varn.size()), + n_cols(compute_n_cols(mother_pos_varn)), + mother_pos_varn(std::move(mother_pos_varn)), // computed here and henceforth `const`! + rows_to_combine(std::move(rows_to_combine_rate_adapt)), + n_ra_rows(n_mother_rows - initial_row_combs) { + if (rows_to_combine.size() % 2 != 0) { + throw std::domain_error("The number of rows to combine for rate adaption " + "(size of argument array) is an odd number (expected even)."); + } + + if (initial_row_combs > rows_to_combine.size() / 2) { + throw std::domain_error("The number of desired initial row combinations for rate adaption " + "is larger than the given array of lines to combine."); + } + + // compute current `pos_varn` and `pos_checkn` from `mother_pos_varn` + recompute_pos_vn_cn(initial_row_combs); + } // ---------------------------------------------------------------------------------------------- public methods - constexpr void encode_no_ra(const std::vector &in, std::vector &out) const { + + + /*! + * Encode (i.e., compute syndrome) using mother matrix + * @tparam BitL e.g. std::uint8_t or bool. + * @tparam BitRBitR allowed to be signed, to enable the "mark combined by -1" trick in `encode_with_ra`. + * @param in input bitvector + * @param out output bitvector + */ + template + constexpr void encode_no_ra(const std::vector &in, std::vector &out) const { if (in.size() != n_cols) { - LDPC4QKD_DEBUG_MESSAGE("Encoder (encode_no_ra) received invalid input length."); // TODO maybe use exception? - return; + throw std::domain_error("Encoder (encode_no_ra) received invalid input length."); } out.assign(n_mother_rows, 0); - for (std::size_t col = 0; col < in.size(); col++) - for (std::size_t j = colptr[col]; j < colptr[col + 1]; j++) - out[row_idx[j]] = xor_as_bools(out[row_idx[j]], in[col]); + for (std::size_t i{}; i < mother_pos_varn.size(); ++i) { + for (auto &var_node: mother_pos_varn[i]) { + out[i] = xor_as_bools(out[i], in[var_node]); + } + } } - /// does not change internal rate adaption state! + /*! + * Compute syndrome using given rate adaption. Does not change internal rate adaption state! + * @tparam Bit e.g. std::uint8_t or bool. TODO use concept `std::unsigned_integral` when using C++20 + * @param in input array + * @param out Vector to store syndrome. Will be resized to `output_syndrome_length` + * @param output_syndrome_length Desired length of syndrome (exception is thrown if not satisfiable) + */ + template void encode_with_ra( const std::vector &in, std::vector &out, std::size_t output_syndrome_length) const { if (in.size() != n_cols) { @@ -149,13 +205,9 @@ namespace LDPC4QKD { throw std::domain_error("Requested syndrome is smaller than supported by the specified rate adaption."); } - // int8_t because value -1 is used to mark bits included into final output - std::vector non_ra_encoding(n_mother_rows); - - // first encode without rate adaption - for (std::size_t col = 0; col < in.size(); col++) - for (std::size_t j = colptr[col]; j < colptr[col + 1]; j++) - non_ra_encoding[row_idx[j]] = xor_as_bools(non_ra_encoding[row_idx[j]], in[col]); + // HAVE to use !!!SIGNED!!! `int8_t`!!! Value `-1` is used below to mark bits included into final output! + std::vector non_ra_encoding; + encode_no_ra(in, non_ra_encoding); // now use the non-rate adapted syndrome to compute the rate adapted syndrome const std::size_t n_line_combinations = n_mother_rows - output_syndrome_length; @@ -182,19 +234,11 @@ namespace LDPC4QKD { } } - /// compute log-likelihood-ratios for a given keye and channel parameter - static std::vector llrs_bsc(const std::vector &bitstring, const double bsc_channel_parameter) { - double vlog = log((1 - bsc_channel_parameter) / bsc_channel_parameter); - std::vector llrs(bitstring.size()); - for (std::size_t i{}; i < llrs.size(); ++i) { - llrs[i] = vlog * (1 - 2 * bitstring[i]); // log likelihood ratios - } - return llrs; - } - /// decoder infers rate from the length of the syndrome and changes the internal decoder state to match this rate. /// Note: since this function modifies the code (by performing rate adaption), it is NOT CONST. /// this change may be somewhat computationally expensive TODO benchmark this + /// `Bit` should be e.g. std::uint8_t or bool. TODO use concept `std::unsigned_integral` when using C++20 + template bool decode_infer_rate(const std::vector &llrs, const std::vector &syndrome, std::vector &out, @@ -206,9 +250,11 @@ namespace LDPC4QKD { return decode_at_current_rate(llrs, syndrome, out, max_num_iter, vsat); } + /*! - * Belief propagation decoder + * Decode using belief propagation * + * @tparam Bit: e.g. std::uint8_t or bool TODO use concept `std::unsigned_integral` when using C++20 * @param llrs: Log likelihood ratios representing the received message * @param syndrome: Syndrome of the sent message * @param out: Buffer to which the function writes its prediction for the sent message. @@ -216,9 +262,9 @@ namespace LDPC4QKD { * Note that the algorithm always terminates automatically when the current prediction matches * the syndrome (early termination), which means that the actual number of iterations cannot be controlled. * @param vsat: Cut-off value for messages. - * @return true if and only if the syndrome of buffer `out` matches given `syndrome`, - * i.e., in case the decoder converged. + * @return true if and only if the syndrome of buffer `out` matches given `syndrome` (i.e., decoder converged). */ + template bool decode_at_current_rate(const std::vector &llrs, const std::vector &syndrome, std::vector &out, @@ -272,8 +318,8 @@ namespace LDPC4QKD { } // check for diverging decoder - for (const auto &m : msg_v) { - for (const auto &v : m) { + for (const auto &m: msg_v) { + for (const auto &v: m) { if (std::isnan(v)) { // TODO maybe use exception? LDPC4QKD_DEBUG_MESSAGE("Decoder Diverged at iteration " << it_unused); @@ -292,11 +338,9 @@ namespace LDPC4QKD { recompute_pos_vn_cn(n_line_combs); } - - /// TODO make private - // TODO benchmark and compare performance to using pos_cn, as well as csc format + template constexpr void encode_at_current_rate( - const std::vector &in, std::vector &out) const { + const std::vector &in, std::vector &out) const { if (in.size() != n_cols) { LDPC4QKD_DEBUG_MESSAGE("Encoder received invalid input length."); // TODO maybe use exception? return; @@ -305,27 +349,22 @@ namespace LDPC4QKD { out.assign(pos_varn.size(), 0); for (std::size_t i{}; i < pos_varn.size(); ++i) { - for (auto &var_node : pos_varn[i]) { + for (auto &var_node: pos_varn[i]) { out[i] = xor_as_bools(out[i], in[var_node]); } } } bool operator==(const RateAdaptiveCode &rhs) const { - return colptr == rhs.colptr && - row_idx == rhs.row_idx && + return n_mother_rows == rhs.n_mother_rows && + n_cols == rhs.n_cols && + mother_pos_varn == rhs.mother_pos_varn && rows_to_combine == rhs.rows_to_combine && pos_checkn == rhs.pos_checkn && pos_varn == rhs.pos_varn && - n_mother_rows == rhs.n_mother_rows && - n_cols == rhs.n_cols && n_ra_rows == rhs.n_ra_rows; } - bool operator!=(const RateAdaptiveCode &rhs) const { - return !(rhs == *this); // Note: do not "simplify". The call to == is required. - } - // ----------------------------------------------------------------------------------------- getters and setters [[nodiscard]] const std::vector> &getPosCheckn() const { @@ -338,34 +377,67 @@ namespace LDPC4QKD { } /// ignores rate adaption! Only gives number of rows in the mother matrix. - [[nodiscard]] std::size_t get_n_rows_mother_matrix() const { + [[nodiscard]] auto get_n_rows_mother_matrix() const { return n_mother_rows; } /// Includes rate adaption. Access to internal state! - [[nodiscard]] std::size_t get_n_rows_after_rate_adaption() const { + [[nodiscard]] auto get_n_rows_after_rate_adaption() const { return n_ra_rows; } - [[nodiscard]] std::size_t getNCols() const { + [[nodiscard]] auto getNCols() const { return n_cols; } - [[nodiscard]] std::size_t get_max_ra_steps() const { + [[nodiscard]] auto get_max_ra_steps() const { return rows_to_combine.size() / 2; } private: // -------------------------------------------------------------------------------------- private members - - constexpr static bool xor_as_bools(Bit lhs, Bit rhs) { + template + constexpr static bool xor_as_bools(BitL lhs, BitR rhs) { return (static_cast(lhs) != static_cast(rhs)); } + template + static Idx compute_n_cols(std::vector> mother_pos_varn) { + if (mother_pos_varn.empty()) { + return 0; + } else { + Idx result{}; + for (const auto &v: mother_pos_varn) { + auto current_max = *std::max_element(v.cbegin(), v.cend()); + result = std::max(result, current_max); + } + return result + 1; // add one because indices in `mother_pos_varn` are zero-based. + } + } + + /// compute `mother_pos_varn` from `colptr` and `rowIdx` + static std::vector> compute_mother_pos_varn( + const std::vector &colptr, + const std::vector &rowIdx) { + // number of columns in full matrix represented by given compressed sparse column (CSC) storage + const auto n_cols = colptr.size() - 1; + // number of rows in full matrix represented by given compressed sparse column (CSC) storage + const auto n_mother_rows = *std::max_element(rowIdx.begin(), rowIdx.end()) + 1u; + + std::vector> pos_varn_tmp{n_mother_rows, std::vector{}}; + for (idx_t col = 0; col < n_cols; col++) { + for (auto j = colptr[col]; j < colptr[col + 1u]; j++) { + pos_varn_tmp[rowIdx[j]].push_back(col); + } + } + return pos_varn_tmp; + } + + template void check_node_update(std::vector> &msg_c, const std::vector> &msg_v, const std::vector &syndrome) const { double msg_part{}; - std::vector mc_position(n_cols); + std::vector mc_position(n_cols); for (std::size_t m{}; m < n_ra_rows; ++m) { // product of incoming messages @@ -403,7 +475,7 @@ namespace LDPC4QKD { void var_node_update(std::vector> &msg_v, const std::vector> &msg_c, const std::vector &llrs) const { - std::vector mv_position(n_cols); + std::vector mv_position(n_cols); for (std::size_t m{}; m < llrs.size(); ++m) { const double mv_sum = std::accumulate(msg_c[m].begin(), msg_c[m].end(), llrs[m]); @@ -420,6 +492,7 @@ namespace LDPC4QKD { } } + template void hard_decision( std::vector &out, const std::vector &llrs, @@ -443,9 +516,9 @@ namespace LDPC4QKD { } } - /*! - * Recompute inner representation of rate adapted LDPC code (pos_varn and pos_cn). + * Recompute inner representation of rate adapted LDPC code (`pos_varn` and `pos_cn`), + * starting from the mother code represented by `mother_pos_varn`. * Note: this function "deals incorrectly" with variable node elimination during rate adaption. * variable node elimination should not happen in the first place * @@ -464,26 +537,14 @@ namespace LDPC4QKD { n_ra_rows = n_mother_rows - n_line_combs; pos_varn.assign(n_ra_rows, std::vector{}); - std::vector> pos_varn_nora{}; - if (n_line_combs == 0) { - for (idx_t col = 0; col < n_cols; col++) { - for (std::size_t j = colptr[col]; j < colptr[col + 1u]; j++) { - pos_varn[row_idx[j]].push_back(col); - } - } + pos_varn = mother_pos_varn; } else { - // first compute non-rate adapted variable nodes and store in temporary vector - pos_varn_nora.resize(n_mother_rows); - - for (idx_t col = 0; col < n_cols; col++) { - for (std::size_t j = colptr[col]; j < colptr[col + 1u]; j++) { - pos_varn_nora[row_idx[j]].push_back(col); - } - } + // Make temporary copy of `mother_pos_varn` + std::vector> pos_varn_nora{mother_pos_varn}; // put results of combined lines at the back of the new LDPC code - const std::size_t start_of_ra_part = n_mother_rows - 2 * n_line_combs; + const auto start_of_ra_part = n_mother_rows - 2 * n_line_combs; for (std::size_t i{}; i < n_line_combs; ++i) { auto &curr_varn_vec = pos_varn[start_of_ra_part + i]; @@ -530,21 +591,28 @@ namespace LDPC4QKD { } // end recompute pos_checkn } - // ---------------------------------------------------------------------------------------------- private fields - // colptr and row_idx define the mother matrix, which each rate adaption starts from. - const std::vector colptr; - const std::vector row_idx; - const std::vector rows_to_combine; // stores specification of rate adaption - - // these two (pos_checkn and pos_varn) store the current rate adapted code. - std::vector> pos_checkn; // Input check nodes to each variable node - std::vector> pos_varn; // Input variable nodes to each check node - - const std::size_t n_mother_rows; - const std::size_t n_cols; - std::size_t n_ra_rows; - + // TODO consider making these of type `idx_t`. + const std::size_t n_mother_rows; // const because it's not possible to change the mother matrix + const std::size_t n_cols; // const because it's not possible to change the mother matrix + + /// Input variable nodes to each check node of the mother matrix. + /// Rate adaption always starts from here. + /// Can be obtained from + /// (1) arrays `colptr` and `row_idx`, which represent the binary LDPC matrix in CSC format + /// (2) an "encoder" implementing `ComputablePosVar`. + const std::vector> mother_pos_varn; + + /// stores specification of rate adaption. + /// Each rate adaption is re-computed using `mother_pos_checkn` and `rows_to_combine`. + const std::vector rows_to_combine; // TODO if empty, use random rate adaption + + /// `pos_checkn` and `pos_varn` store the current rate adapted code, which is actually used for decoding. + std::vector> pos_checkn; /// Input check nodes to each variable node + std::vector> pos_varn; /// Input variable nodes to each check node + + /// current number of matrix rows (given current rate adaption). + std::size_t n_ra_rows{}; }; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b615484..f365a0d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,23 +21,21 @@ FetchContent_MakeAvailable(googletest) add_executable(unit_tests_error_correction test_main.cpp # -------- Actual Unit tests -------- test_encoder.cpp + test_encoder_advanced.cpp + test_rate_adaptive_code.cpp test_read_ldpc_from_files.cpp # Static data LDPC code used for tests: fortest_autogen_ldpc_matrix_csc.hpp fortest_autogen_rate_adaption.hpp - ) - -target_compile_features(unit_tests_error_correction - PUBLIC cxx_std_17 - ) +) target_include_directories(unit_tests_error_correction PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/.." - ) +) target_link_libraries(unit_tests_error_correction PRIVATE @@ -51,7 +49,7 @@ target_link_libraries(unit_tests_error_correction # To be tested: LDPC4QKD::LDPC4QKD - ) +) # adds tests via CTest @@ -67,40 +65,40 @@ add_executable(look_at_executable_size look_at_executable_size.cpp) target_include_directories(look_at_executable_size PRIVATE "${LDPC4QKD_SRC_DIR}/" - ) +) target_compile_features(look_at_executable_size PUBLIC cxx_std_17 - ) +) # Copy the .cscmat file into the directory containing the tests binary. # This is required to test the .cscmat reader code. get_filename_component(CSCMAT_TEST_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/LDPC_code_for_testing_2048x6144.cscmat REALPATH - ) +) message(STATUS "Copying file ${CSCMAT_TEST_FILE_PATH} into directory ${CMAKE_CURRENT_BINARY_DIR}.") file(COPY ${CSCMAT_TEST_FILE_PATH} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} - ) +) # Copy the rate adaption .csv file into the directory containing the tests binary. # This is required to test the .cscmat reader code. get_filename_component(CSV_RATE_ADAPTION_TEST_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/rate_adaption_2x6_block_6144_for_testing.csv REALPATH - ) +) message(STATUS "Copying file ${CSV_RATE_ADAPTION_TEST_FILE_PATH} into directory ${CMAKE_CURRENT_BINARY_DIR}.") file(COPY ${CSV_RATE_ADAPTION_TEST_FILE_PATH} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} - ) +) # Copy the bincsc.json file into the directory containing the tests binary. # This is required to test the json reader code. get_filename_component(CSV_RATE_ADAPTION_TEST_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/test_reading_bincscjson_format_block_6144_proto_2x6_313422410401.bincsc.json REALPATH - ) +) message(STATUS "Copying file ${CSV_RATE_ADAPTION_TEST_FILE_PATH} into directory ${CMAKE_CURRENT_BINARY_DIR}.") file(COPY ${CSV_RATE_ADAPTION_TEST_FILE_PATH} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} - ) +) diff --git a/tests/helpers_for_testing.hpp b/tests/helpers_for_testing.hpp index 148ef8c..e785333 100644 --- a/tests/helpers_for_testing.hpp +++ b/tests/helpers_for_testing.hpp @@ -41,12 +41,12 @@ namespace HelpersForTests { return seed end ``` - * @tparam T should be primitive type std::uint[x]_t - * @param vec Vector + * @tparam Vec should be something like `std::array` or `std::vector`. + * @param vec input data * @return a hash of all vector entries */ - template - std::uint32_t hash_vector(const std::vector &vec) { + template + std::uint32_t hash_vector(const Vec &vec) { auto seed = static_cast(vec.size()); for (auto i : vec) { seed ^= static_cast(i) + 0x9e3779b9 + (seed << 6u) + (seed >> 2u); @@ -54,7 +54,6 @@ namespace HelpersForTests { return seed; } - template void write_vector_to_csv(const std::string &filepath, const std::vector &vec, bool cast_to_long) { std::ofstream myfile(filepath); @@ -157,15 +156,6 @@ namespace HelpersForTests { } } - template - std::uint32_t hash_vector(const T &vec) { - auto seed = static_cast(vec.size()); - for (auto i : vec) { - seed ^= static_cast(i) + 0x9e3779b9 + (seed << 6u) + (seed >> 2u); - } - return seed; - } - template std::vector arr_to_vec(std::array const &in) { std::vector out(N); diff --git a/tests/test_encoder_advanced.cpp b/tests/test_encoder_advanced.cpp new file mode 100644 index 0000000..e4401f5 --- /dev/null +++ b/tests/test_encoder_advanced.cpp @@ -0,0 +1,76 @@ +// +// Created by Adomas Baliuka on 02.05.24. +// + +// Google Test framework +#include +#include "helpers_for_testing.hpp" + +// Standard library +#include + +// To be tested +#include "encoder_advanced.hpp" + +using namespace LDPC4QKD; + +TEST(test_encoder_advanced, basic_example) { + std::cout << "hello\n"; + unsigned seed = 42; // seed for PRNG + + // If the code choice is done at RUNTIME (will usually be the case, e.g. because QBER is known only at runtime), + // need to provide containers for input and output with correct sizes, otherwise an exception will be thrown. + { + // if we don't know the size of `key` at runtime, it will probably be a `std::vector`, not a `std::array`. + + std::size_t code_id = 0; // Not `constexpr`, let's say we only know this value at runtime! + + std::vector key_vec{}; + key_vec.resize(LDPC4QKD::get_input_size(code_id)); // get size at runtime! + noise_bitstring_inplace(key_vec, 0.5, seed); // create a random key + + // allocate a buffer for the syndrome (runtime-known length). + std::vector syndrome_vec{}; + syndrome_vec.resize(LDPC4QKD::get_output_size(code_id)); + + // However, if `key_vec.size()` and `syndrome_vec.size()` aren't exactly right for the chosen code, + // then the above will throw `std::out_of_range` exception! + LDPC4QKD::encode_with(code_id, key_vec, syndrome_vec); + + // Use the non-generic version of `encode_with`: + std::cout << "Syndrome of runtime known size " << syndrome_vec.size() << std::endl; + for (auto v: syndrome_vec) { + std::cout << static_cast(v) << ' '; // print syndrome bits + } + std::cout << std::endl; + } + + { // If the block size and syndrome size are known at compile time, we can use fixed-length buffers (`std::array`) + // If any of the containers used for key or syndrome has a compile-time known size + // (e.g. `std::array` or `std::span`), then this method MUST be used! + std::array key_arr{}; + noise_bitstring_inplace(key_arr, 0.5, seed); // create a random key + + // allocate a buffer for the syndrome + std::array syndrome{}; + +// encoder1.encode(key_arr, syndrome); // this would work. It's just using a concrete encoder object. + + // This also works and does the same thing. + // Use this way when LDPC code choice is known at compile time. + // That's because `key` and `syndrome` have the correct sizes for `code_id = 0` (which is precisely `encoder1`) + constexpr int code_id = 0; // HAS to be `constexpr`! + LDPC4QKD::encode_with(key_arr, syndrome); + + // this will not compile because `key` has a compile-time known size. + // The template instantiation will try to compile against **each** available code (although only one is used). + // Since the codes have different sizes, this will fail on most codes and give a compiler error. +// LDPC4QKD::encode_with(code_id, key, syndrome); // error: no matching function for call to ‘std::span<...>::span(...) + + std::cout << "Syndrome of compile-time known size " << syndrome.size() << std::endl; + for (auto v: syndrome) { + std::cout << static_cast(v) << ' '; // print syndrome bits + } + std::cout << std::endl; + } +} diff --git a/tests/test_rate_adaptive_code.cpp b/tests/test_rate_adaptive_code.cpp index f832def..e6c49be 100644 --- a/tests/test_rate_adaptive_code.cpp +++ b/tests/test_rate_adaptive_code.cpp @@ -11,6 +11,7 @@ // To be tested #include "rate_adaptive_code.hpp" +#include "encoder_advanced.hpp" // Test cases test against constants known to be correct for the LDPC-matrix defined here: #include "fortest_autogen_ldpc_matrix_csc.hpp" @@ -21,39 +22,31 @@ using namespace LDPC4QKD; namespace { - RateAdaptiveCode get_code_big_nora() { + auto get_code_big_nora() { std::vector colptr(AutogenLDPC::colptr.begin(), AutogenLDPC::colptr.end()); std::vector row_idx(AutogenLDPC::row_idx.begin(), AutogenLDPC::row_idx.end()); - return RateAdaptiveCode(colptr, row_idx); + return RateAdaptiveCode(colptr, row_idx); } - RateAdaptiveCode get_code_big_wra() { + auto get_code_big_wra() { std::vector colptr(AutogenLDPC::colptr.begin(), AutogenLDPC::colptr.end()); std::vector row_idx(AutogenLDPC::row_idx.begin(), AutogenLDPC::row_idx.end()); std::vector rows_to_combine(AutogenRateAdapt::rows.begin(), AutogenRateAdapt::rows.end()); - return RateAdaptiveCode(colptr, row_idx, rows_to_combine); + return RateAdaptiveCode(colptr, row_idx, rows_to_combine); } - - RateAdaptiveCode get_code_small() { + auto get_code_small() { // H = [1 0 1 0 1 0 1 // 0 1 1 0 0 1 1 // 0 0 0 1 1 1 1] std::vector colptr{0, 1, 2, 4, 5, 7, 9, 12}; std::vector row_idx{0, 1, 0, 1, 2, 0, 2, 1, 2, 0, 1, 2}; - return RateAdaptiveCode(colptr, row_idx); + return RateAdaptiveCode(colptr, row_idx); } } -//TEST(rate_adaptive_code, TMPTMPTMPTMTPTMP) { // this test accesses private fields. -// auto H = get_code_big(); -// EXPECT_EQ(hash_vector(H.colptr), 736283749); -// EXPECT_EQ(hash_vector(H.row_idx), 4281948431); -//} - - -TEST(rate_adaptive_code, decode_test_small) { +TEST(rate_adaptive_code_from_colptr_rowIdx, decode_test_small) { auto H = get_code_small(); std::vector x{1, 1, 1, 1, 0, 0, 0}; // true data to be sent @@ -75,7 +68,7 @@ TEST(rate_adaptive_code, decode_test_small) { EXPECT_EQ(solution, x); } -TEST(rate_adaptive_code, decode_test_big) { +TEST(rate_adaptive_code_from_colptr_rowIdx, decode_test_big) { auto H = get_code_big_nora(); std::vector x = get_bitstring(H.getNCols()); // true data to be sent @@ -104,7 +97,7 @@ TEST(rate_adaptive_code, decode_test_big) { } -TEST(rate_adaptive_code, encode_no_ra) { +TEST(rate_adaptive_code_from_colptr_rowIdx, encode_no_ra) { auto H = get_code_big_nora(); std::vector in = get_bitstring(H.getNCols()); std::vector out(H.get_n_rows_mother_matrix()); @@ -116,7 +109,7 @@ TEST(rate_adaptive_code, encode_no_ra) { } -TEST(rate_adaptive_code, encode_current_rate) { +TEST(rate_adaptive_code_from_colptr_rowIdx, encode_current_rate) { auto H = get_code_big_wra(); std::vector in = get_bitstring(H.getNCols()); std::vector out(H.get_n_rows_mother_matrix()); @@ -133,13 +126,13 @@ TEST(rate_adaptive_code, encode_current_rate) { } -TEST(rate_adaptive_code, no_ra_if_no_linecombs) { +TEST(rate_adaptive_code_from_colptr_rowIdx, no_ra_if_no_linecombs) { auto H = get_code_big_nora(); EXPECT_ANY_THROW(H.set_rate(H.get_n_rows_mother_matrix() - 5)); } -TEST(rate_adaptive_code, init_pos_CN_pos_VN) { +TEST(rate_adaptive_code_from_colptr_rowIdx, init_pos_CN_pos_VN) { auto H = get_code_small(); std::vector> expect_posCN{{0}, @@ -158,14 +151,14 @@ TEST(rate_adaptive_code, init_pos_CN_pos_VN) { // vn eliminations are allowed now! TODO reconsider this. -//TEST(rate_adaptive_code, dont_allow_vn_elimination) { +//TEST(rate_adaptive_code_from_colptr_rowIdx, dont_allow_vn_elimination) { // std::vector colptr{0, 1, 2, 4, 5, 7, 9, 12}; // std::vector row_idx{0, 1, 0, 1, 2, 0, 2, 1, 2, 0, 1, 2}; -// EXPECT_ANY_THROW(RateAdaptiveCode(colptr, row_idx, {0,1})); +// EXPECT_ANY_THROW(RateAdaptiveCode(colptr, row_idx, {0,1})); //} -TEST(rate_adaptive_code, getters) { +TEST(rate_adaptive_code_from_colptr_rowIdx, getters) { auto H = get_code_big_nora(); EXPECT_EQ(H.get_n_rows_mother_matrix(), 2048); EXPECT_EQ(H.getNCols(), 6144); @@ -173,7 +166,7 @@ TEST(rate_adaptive_code, getters) { EXPECT_EQ(H.get_n_rows_after_rate_adaption(), H.get_n_rows_mother_matrix()); } -TEST(rate_adaptive_code, encode_with_ra) { +TEST(rate_adaptive_code_from_colptr_rowIdx, encode_with_ra) { auto H = get_code_big_wra(); std::vector input = get_bitstring(H.getNCols()); // true data to be sent @@ -200,7 +193,7 @@ TEST(rate_adaptive_code, encode_with_ra) { } -TEST(rate_adaptive_code, ra_reported_size) { +TEST(rate_adaptive_code_from_colptr_rowIdx, ra_reported_size) { auto H = get_code_big_wra(); { @@ -215,7 +208,7 @@ TEST(rate_adaptive_code, ra_reported_size) { } } -TEST(rate_adaptive_code, decode_infer_rate) { +TEST(rate_adaptive_code_from_colptr_rowIdx, decode_infer_rate) { auto H = get_code_big_wra(); std::vector x = get_bitstring(H.getNCols()); // true data to be sent @@ -246,7 +239,7 @@ TEST(rate_adaptive_code, decode_infer_rate) { } -TEST(rate_adaptive_code, rate_adapted_fer) { +TEST(rate_adaptive_code_from_colptr_rowIdx, rate_adapted_fer) { // assert that the rate adapted FER (at set fraction of mother syndrome) is small. std::mt19937_64 rng(42); auto H = get_code_big_wra(); @@ -298,7 +291,7 @@ TEST(rate_adaptive_code, rate_adapted_fer) { ASSERT_EQ(fer, 0.); } -TEST(rate_adaptive_code, llrs_bsc) { +TEST(rate_adaptive_code_from_colptr_rowIdx, llrs_bsc) { std::vector x{1, 1, 1, 1, 0, 0, 0}; double p = 0.01; @@ -308,12 +301,12 @@ TEST(rate_adaptive_code, llrs_bsc) { llrs[i] = vlog * (1 - 2 * x[i]); // log likelihood ratios } - std::vector llrs_convenience = LDPC4QKD::RateAdaptiveCode::llrs_bsc(x, p); + std::vector llrs_convenience = LDPC4QKD::llrs_bsc(x, p); EXPECT_EQ(llrs_convenience, llrs); } -TEST(rate_adaptive_code, equals_not_equals_operators) { +TEST(rate_adaptive_code_from_colptr_rowIdx, equals_not_equals_operators) { auto H1 = get_code_big_wra(); auto H2 = get_code_big_wra(); EXPECT_FALSE(H1 != H2); @@ -322,3 +315,41 @@ TEST(rate_adaptive_code, equals_not_equals_operators) { EXPECT_FALSE(H1 == H2); EXPECT_TRUE(H1 != H2); } + +TEST(rate_adaptive_code_from_decoder, obtain_from_advanced_encoder_behaviour) { + std::vector rows_to_combine{}; // not used here! + RateAdaptiveCode H1(encoder2.get_pos_varn(), rows_to_combine); + + auto H2 = get_code_big_wra(); + + { + std::vector in = get_bitstring(H1.getNCols()); + std::vector out(H1.get_n_rows_mother_matrix()); + + std::cout << "input hash: " << hash_vector(in) << std::endl; + H1.encode_no_ra(in, out); + std::cout << "output hash: " << hash_vector(out) << std::endl; + + EXPECT_EQ(hash_vector(out), 2814594723); + } + { + std::vector in = get_bitstring(H2.getNCols()); + std::vector out(H2.get_n_rows_mother_matrix()); + + write_vector_to_csv("tmp_randkey_6144.csv", in, true); + std::cout << "input hash: " << hash_vector(in) << std::endl; + H2.encode_no_ra(in, out); + std::cout << "output hash: " << hash_vector(out) << std::endl; + + EXPECT_EQ(hash_vector(out), 2814594723); + } +} + +TEST(rate_adaptive_code_from_decoder, obtain_from_advanced_encoder_equals) { + std::vector rows_to_combine(AutogenRateAdapt::rows.begin(), AutogenRateAdapt::rows.end()); + RateAdaptiveCode H1(encoder2.get_pos_varn(), rows_to_combine); + + // TODO add random rate adaption for comparison + auto H2 = get_code_big_wra(); + EXPECT_TRUE(H1 == H2); +} diff --git a/tests/test_read_ldpc_from_files.cpp b/tests/test_read_ldpc_from_files.cpp index 1cf7ad6..80d2969 100644 --- a/tests/test_read_ldpc_from_files.cpp +++ b/tests/test_read_ldpc_from_files.cpp @@ -22,7 +22,7 @@ namespace { auto get_code_big_nora() { std::vector colptr(AutogenLDPC::colptr.begin(), AutogenLDPC::colptr.end()); std::vector row_idx(AutogenLDPC::row_idx.begin(), AutogenLDPC::row_idx.end()); - return RateAdaptiveCode(colptr, row_idx); + return RateAdaptiveCode(colptr, row_idx); } }