diff --git a/src/encoder_advanced.hpp b/src/encoder_advanced.hpp index 7f10c22..64ec030 100644 --- a/src/encoder_advanced.hpp +++ b/src/encoder_advanced.hpp @@ -96,6 +96,11 @@ namespace LDPC4QKD { } } + /// Note: vector cannot be supported in the interface specified here. + /// This is due to the template specialization for vector, + /// which does not allow direct conversion to span, as in `std::span{vec}`. + void encode(std::vector const &key, std::vector &syndrome) const = delete; + // Have to write explicitly for some gcc versions. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413 constexpr ~FixedSizeEncoder() override = default; }; @@ -128,7 +133,6 @@ namespace LDPC4QKD { // 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 { @@ -155,12 +159,15 @@ namespace LDPC4QKD { 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++) { + /// Avoiding runtime length-check from the types. + /// TODO this template overload does not match things like `std::array`, although it would be nice! + 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]; @@ -171,19 +178,21 @@ namespace LDPC4QKD { // 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; - } + out[outIdx] = xor_as_bools(out[outIdx], in[col]); } } - 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."); + /// General overload, which does not assume spans, but does a **runtime length check!**. + void encode_qc(auto const &in, auto &out) const { + if (std::size(in) != N * expansion_factor || std::size(out) != M * expansion_factor) { + std::stringstream s; + s << "LDPC encoder: incorrect sizes of intput / output arrays\n" + << "RECEIVED: key.size() = " << in.size() << ". " << "syndrome.size() = " << out.size() << ".\n" + << "EXPECTED: key.size() = " << N * expansion_factor << ". " + << "syndrome.size() = " << M * expansion_factor << ".\n"; + throw std::out_of_range(s.str()); + } for (std::size_t col = 0; col < in.size(); col++) { auto QCcol = col / expansion_factor; // column index into matrix of exponents @@ -201,6 +210,30 @@ namespace LDPC4QKD { } } + 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; + } + std::array colptr; std::array row_idx; std::array values; @@ -262,11 +295,13 @@ namespace LDPC4QKD { //! \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! + //! 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); + // if/when `all_encoders_tuple` contains non-QC matrices, this needs to change! + // Originally, this was using `encode` instead of `encode_qc` but then it doesn't work with `vector` + std::get(all_encoders_tuple).encode_qc(key, result); return; } diff --git a/tests/test_encoder_advanced.cpp b/tests/test_encoder_advanced.cpp index 10cd9f8..bd5f0fe 100644 --- a/tests/test_encoder_advanced.cpp +++ b/tests/test_encoder_advanced.cpp @@ -30,63 +30,88 @@ namespace { } } -TEST(test_encoder_advanced, basic_example) { - std::cout << "hello\n"; +TEST(test_encoder_advanced, basic_example_code_choice_runtime) { 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`. + // 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::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 - 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)); - // 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); - // 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; +} - // 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; +TEST(test_encoder_advanced, basic_example_code_choice_runtime_vectorbool) { + unsigned seed = 42; // seed for PRNG + + // same thing with `vector` + 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 +TEST(test_encoder_advanced, basic_example_code_choicecomptime) { + unsigned seed = 42; // seed for PRNG + + // 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{}; + // 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. +// 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 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. + // 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; + 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; }