From 3ba9ef05d84875b8d2931aa8fdf5b4e62c54c209 Mon Sep 17 00:00:00 2001 From: Alice Date: Thu, 25 Jun 2026 17:00:50 +0200 Subject: [PATCH] added SparseMask class --- CMakeLists.txt | 9 +++-- include/aare/utils/SparseMask.hpp | 56 +++++++++++++++++++++++++++ src/utils/SparseMask.cpp | 64 +++++++++++++++++++++++++++++++ src/utils/SparseMask.test.cpp | 49 +++++++++++++++++++++++ 4 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 include/aare/utils/SparseMask.hpp create mode 100644 src/utils/SparseMask.cpp create mode 100644 src/utils/SparseMask.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c8c31b..f5049e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,7 +419,8 @@ set(PUBLICHEADERS include/aare/RawMasterFile.hpp include/aare/RawSubFile.hpp include/aare/VarClusterFinder.hpp - include/aare/utils/task.hpp) + include/aare/utils/task.hpp + include/aare/utils/SparseMask.hpp) set(SourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/src/calibration.cpp @@ -444,7 +445,8 @@ set(SourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/task.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ifstream_helpers.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ifstream_helpers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/SparseMask.cpp) add_library(aare_core STATIC ${SourceFiles} ${PUBLICHEADERS}) target_include_directories( @@ -512,7 +514,8 @@ if(AARE_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/src/RawFile.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RawSubFile.test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/task.test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.test.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/src/to_string.test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/SparseMask.test.cpp) target_sources(tests PRIVATE ${TestSources}) endif() diff --git a/include/aare/utils/SparseMask.hpp b/include/aare/utils/SparseMask.hpp new file mode 100644 index 0000000..95b6966 --- /dev/null +++ b/include/aare/utils/SparseMask.hpp @@ -0,0 +1,56 @@ +#include "aare/NDView.hpp" +#include +#include +#include + +namespace aare { + +enum STORAGEFORMAT : uint8_t { ROWMAJOR = 0, COLUMNMAJOR = 1 }; + +/** + * @brief A class representing a sparse mask for a 2D array. + * + * The SparseMask class allows for efficient storage and retrieval of non-zero + * elements in a 2D array. It supports both row-major and column-major storage + * formats. + */ +class SparseMask { + + public: + SparseMask(const STORAGEFORMAT storage_format, const size_t rows, + const size_t cols); + + SparseMask(NDView mask, const STORAGEFORMAT storage_format); + + // TODO: think of storage hdf5 probably the best innerindices, outerindices + // as datasets and storage_format as attribute + SparseMask(const std::filesystem::path &filename); + + void insert(const size_t row, const size_t col); + + /** + * @brief Check if the element at (row, col) is masked (non-zero). + * @param row Row index of the element. + * @param col Column index of the element. + * @return true if the element is masked (non-zero), false otherwise. + */ + bool is_masked(const size_t row, const size_t col) const; + + void write_to_file(const std::filesystem::path &filename) const; + + private: + /// @brief stoarge format of the sparse mask, either row major or column + /// major + STORAGEFORMAT storage_format_; + + /// @brief for column major stores row indices of non-zero elements, for row + /// major stores column indices of non-zero elements + std::vector innerindices_; + + /// @brief for column major outerindices[j] gives the index in innerindices_ + /// of the first non-zero element in column j, for row major outerindices[i] + /// gives the index in innerindices_ of the first non-zero element in row i + std::vector outerindices_; +}; + +} // namespace aare \ No newline at end of file diff --git a/src/utils/SparseMask.cpp b/src/utils/SparseMask.cpp new file mode 100644 index 0000000..cbbbc3b --- /dev/null +++ b/src/utils/SparseMask.cpp @@ -0,0 +1,64 @@ +#include "aare/utils/SparseMask.hpp" + +namespace aare { + +SparseMask::SparseMask(const STORAGEFORMAT storage_format, const size_t rows, + const size_t cols) + : storage_format_(storage_format) { + if (storage_format_ == STORAGEFORMAT::ROWMAJOR) { + outerindices_.resize(rows + 1, 0); + } else if (storage_format_ == STORAGEFORMAT::COLUMNMAJOR) { + outerindices_.resize(cols + 1, 0); + } else { + throw std::invalid_argument( + "Invalid storage format: must be either ROWMAJOR or COLUMNMAJOR"); + } + + innerindices_.reserve(rows * cols); // Reserve maximum possible size +} + +void SparseMask::insert(const size_t row, const size_t col) { + + // TODO: can be very inefficient -> most generic so does not depend on row, + // col order of insertion + // -> maybe better std::vector> for each row, col flatten after + // inserting all -> create innerindices from size + + if (storage_format_ == STORAGEFORMAT::ROWMAJOR) { + std::for_each(outerindices_.begin() + row + 1, outerindices_.end(), + [](uint32_t &x) { ++x; }); + innerindices_.insert(innerindices_.begin() + outerindices_[row], + static_cast(col)); + } else { + std::for_each(outerindices_.begin() + col + 1, outerindices_.end(), + [](uint32_t &x) { ++x; }); + innerindices_.insert(innerindices_.begin() + outerindices_[col], + static_cast(row)); + } +} + +bool SparseMask::is_masked(const size_t row, const size_t col) const { + + const size_t index_outer_indices = + storage_format_ == STORAGEFORMAT::ROWMAJOR ? row : col; + + const size_t nonzero_index = + storage_format_ == STORAGEFORMAT::ROWMAJOR ? col : row; + + if (outerindices_[index_outer_indices + 1] - + outerindices_[index_outer_indices] == + 0) { + return false; // No non-zero elements in this row + } else { + auto start = outerindices_[index_outer_indices]; + auto end = outerindices_[index_outer_indices + 1]; + for (size_t i = start; i < end; ++i) { + if (innerindices_[i] == nonzero_index) { + return true; // Found a non-zero element at (row, col) + } + } + return false; // No non-zero element found at (row, col) + } +} + +} // namespace aare \ No newline at end of file diff --git a/src/utils/SparseMask.test.cpp b/src/utils/SparseMask.test.cpp new file mode 100644 index 0000000..c5c4348 --- /dev/null +++ b/src/utils/SparseMask.test.cpp @@ -0,0 +1,49 @@ +#include "aare/utils/SparseMask.hpp" + +#include +#include + +TEST_CASE("Create Sparse Mask") { + + std::vector> masked_pairs = { + {1, 1}, {1, 2}, {1, 4}, {2, 0}, {2, 2}, {2, 4}, {3, 4}, {4, 1}}; + + SECTION("Row Major") { + aare::SparseMask mask(aare::STORAGEFORMAT::ROWMAJOR, 5, 5); + + std::for_each(masked_pairs.begin(), masked_pairs.end(), + [&mask](const std::pair &p) { + mask.insert(p.first, p.second); + }); + + for (size_t row = 0; row < 5; ++row) { + for (size_t col = 0; col < 5; ++col) { + if (std::find(masked_pairs.begin(), masked_pairs.end(), + std::make_pair(row, col)) != masked_pairs.end()) { + CHECK(mask.is_masked(row, col) == true); + } else { + CHECK(mask.is_masked(row, col) == false); + } + } + } + } + SECTION("Column Major") { + aare::SparseMask mask(aare::STORAGEFORMAT::COLUMNMAJOR, 5, 5); + + std::for_each(masked_pairs.begin(), masked_pairs.end(), + [&mask](const std::pair &p) { + mask.insert(p.first, p.second); + }); + + for (size_t row = 0; row < 5; ++row) { + for (size_t col = 0; col < 5; ++col) { + if (std::find(masked_pairs.begin(), masked_pairs.end(), + std::make_pair(row, col)) != masked_pairs.end()) { + CHECK(mask.is_masked(row, col) == true); + } else { + CHECK(mask.is_masked(row, col) == false); + } + } + } + } +}