Skip to content

Commit

Permalink
Enable using vector<bool> with code selection at the cost of currentl…
Browse files Browse the repository at this point in the history
…y only supporting QC codes in the tuple
  • Loading branch information
adomasbaliuka committed May 27, 2024
1 parent 79908eb commit 4c31fea
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 59 deletions.
71 changes: 53 additions & 18 deletions src/encoder_advanced.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ namespace LDPC4QKD {
}
}

/// Note: vector<bool> cannot be supported in the interface specified here.
/// This is due to the template specialization for vector<bool>,
/// which does not allow direct conversion to span, as in `std::span<bool, N>{vec}`.
void encode(std::vector<bool> const &key, std::vector<bool> &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;
};
Expand Down Expand Up @@ -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<FixedSizeEncoderQC::inputSize>;

[[nodiscard]] std::vector<std::vector<idx_t>> get_pos_varn() const override {
Expand All @@ -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<bit_type const, N * expansion_factor> in,
std::span<bit_type, M * expansion_factor> 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];
Expand All @@ -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<bit_type const, N * expansion_factor> in,
std::span<bit_type, M * expansion_factor> 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
Expand All @@ -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<coptr_uintx_t, N + 1> colptr;
std::array<row_idx_uintx_t, num_nz> row_idx;
std::array<values_uintx_t, num_nz> values;
Expand Down Expand Up @@ -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<std::size_t N = 0>
void encode_with(std::size_t code_id, auto const &key, auto &result) {
if (N == code_id) {
std::get<N>(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<bool>`
std::get<N>(all_encoders_tuple).encode_qc(key, result);
return;
}

Expand Down
107 changes: 66 additions & 41 deletions tests/test_encoder_advanced.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> syndrome_vec{};
syndrome_vec.resize(LDPC4QKD::get_output_size(code_id));

// allocate a buffer for the syndrome (runtime-known length).
std::vector<std::uint8_t> 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<int>(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<int>(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<bool>`
std::size_t code_id = 0; // Not `constexpr`, let's say we only know this value at runtime!

std::vector<bool> 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<bool> 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<int>(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<std::uint8_t, AutogenLDPC_QC::N * AutogenLDPC_QC::expansion_factor> 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<std::uint8_t, AutogenLDPC_QC::N * AutogenLDPC_QC::expansion_factor> key_arr{};
noise_bitstring_inplace(key_arr, 0.5, seed); // create a random key

// allocate a buffer for the syndrome
std::array<std::uint8_t, AutogenLDPC_QC::M * AutogenLDPC_QC::expansion_factor> syndrome{};
// allocate a buffer for the syndrome
std::array<std::uint8_t, AutogenLDPC_QC::M * AutogenLDPC_QC::expansion_factor> 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<code_id>(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<code_id>(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<int>(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<int>(v) << ' '; // print syndrome bits
}
std::cout << std::endl;
}

0 comments on commit 4c31fea

Please sign in to comment.