.. _program_listing_file_include_dx2_reflection.hpp: Program Listing for File reflection.hpp ======================================= |exhale_lsh| :ref:`Return to documentation for file ` (``include/dx2/reflection.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #pragma once #include "dx2/h5/h5dispatch.hpp" #include "dx2/h5/h5read_processed.hpp" #include "dx2/h5/h5utils.hpp" #include "dx2/h5/h5write.hpp" #include "dx2/logging.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma region Type Helpers /* * Type aliases for centralised control of storage types. */ // Internal storage type for the data template using storage_type = std::vector; // Type alias for mdspan (currently using backport std::experimental::mdspan) template using mdspan_type = std::experimental::mdspan>; #pragma endregion #pragma region Column Definition template struct TypedColumn { using value_type = T; TypedColumn(std::string name, std::vector shape, storage_type data) : name(std::move(name)), shape(std::move(shape)), data(std::move(data)) { if (this->shape.size() != 1 && this->shape.size() != 2) { std::string got = "{"; for (size_t i = 0; i < this->shape.size(); ++i) { got += (i ? ", " : "") + std::to_string(this->shape[i]); } throw std::runtime_error( "TypedColumn: column shape must be {N} or {N, M}, got " + got + "}"); } } size_t rows() const { return shape[0]; } size_t cols() const { return shape.size() > 1 ? shape[1] : 1; } mdspan_type span() { return mdspan_type(data.data(), rows(), cols()); } mdspan_type span() const { return mdspan_type(data.data(), rows(), cols()); } TypedColumn filtered(const std::vector &selected_rows) const { storage_type new_data; new_data.reserve(selected_rows.size() * cols()); for (size_t row : selected_rows) { auto first = data.begin() + row * cols(); new_data.insert(new_data.end(), first, first + cols()); } return TypedColumn(name, {selected_rows.size(), cols()}, std::move(new_data)); } std::string name; std::vector shape; storage_type data; }; #pragma endregion #pragma region Predicate Helpers template struct ColumnPredicate { std::string column_name; std::function &, size_t)> predicate; }; namespace logic { enum class LogicalOp { Or, And }; } // namespace logic #pragma endregion #pragma region ReflectionTable class ReflectionTable { private: using Column = h5dispatch::variant_of_t; // Views returned by column() point into each column's buffer, which // survives the column vector reallocating only if columns are moved // rather than copied. static_assert(std::is_nothrow_move_constructible_v); std::vector data; std::string h5_filepath; const std::string DEFAULT_REFL_GROUP = "/dials/processing/group_0"; std::vector experiment_ids; std::vector identifiers; uint64_t max_experiment_id = 0; size_t get_row_count() const; static const std::string &column_name(const Column &col) { return std::visit( [](const auto &c) -> const std::string & { return c.name; }, col); } static size_t column_rows(const Column &col) { return std::visit([](const auto &c) { return c.rows(); }, col); } template static auto find_column(Self &self, const std::string &name) -> std::conditional_t, const TypedColumn, TypedColumn> * { static_assert(h5dispatch::is_column_type_v, "Unsupported column type; see h5dispatch::ColumnTypes"); for (auto &col : self.data) { auto *typed = std::get_if>(&col); if (typed && typed->name == name) { return typed; } } return nullptr; } void merge_into_set(std::unordered_set &set, const std::vector &rows) const; template std::vector evaluate_column_predicate(const ColumnPredicate &condition) const { auto opt = column(condition.column_name); if (!opt) throw std::runtime_error("Column not found or wrong type: " + condition.column_name); const mdspan_type &span = *opt; std::vector matches; for (size_t i = 0; i < span.extent(0); ++i) { if (condition.predicate(span, i)) { matches.push_back(i); } } return matches; } template struct is_column_predicate : std::false_type {}; template struct is_column_predicate> : std::true_type {}; public: #pragma region Constructors using BoolEnum = h5dispatch::BoolEnum; ReflectionTable(); ReflectionTable(const std::vector &experiment_ids, const std::vector &identifiers); ReflectionTable(const std::string &h5_filepath); #pragma endregion #pragma region Metadata Access const std::vector &get_experiment_ids() const; void set_experiment_ids(const std::vector &ids); const std::vector &get_identifiers() const; void set_identifiers(const std::vector &ids); std::pair generate_new_attributes(); std::vector get_column_names() const; size_t size() const; #pragma endregion #pragma region Column Access template std::optional> column(const std::string &name) { if (auto *typed = find_column(*this, name)) { return typed->span(); } return std::nullopt; } template std::optional> column(const std::string &name) const { if (const auto *typed = find_column(*this, name)) { return typed->span(); } return std::nullopt; } template std::vector find_rows(logic::LogicalOp op, const Predicates &...conds) const { // Ensure all predicates are of the correct type static_assert((is_column_predicate::value && ...), "All arguments to find_rows must be ColumnPredicate"); // Expand the parameter pack into a vector of row-index vectors std::vector> all_matches = { evaluate_column_predicate(conds)...}; std::unordered_set result_set; if (op == logic::LogicalOp::Or) { // Combine all matching row indices (set union) for (const auto &matches : all_matches) { result_set.insert(matches.begin(), matches.end()); } } else if (op == logic::LogicalOp::And) { // Combine only common row indices (set intersection) if (all_matches.empty()) return {}; // Start with the first match set as the initial intersection base std::unordered_set intersection(all_matches[0].begin(), all_matches[0].end()); // Iteratively refine the intersection by retaining only elements // that are also present in the next predicate's results for (size_t i = 1; i < all_matches.size(); ++i) { std::unordered_set next(all_matches[i].begin(), all_matches[i].end()); // Iterate over current intersection set for (auto it = intersection.begin(); it != intersection.end();) { // If the current value is not present in the next result set, // erase it from the intersection set if (next.find(*it) == next.end()) { it = intersection.erase(it); // erase returns the next iterator } else { ++it; // move to the next element if it's still present } } } result_set = std::move(intersection); } return std::vector(result_set.begin(), result_set.end()); } template std::vector find_rows(const Predicates &...conds) const { return find_rows(logic::LogicalOp::And, conds...); } ReflectionTable select(const std::vector &selected_rows) const; ReflectionTable select(const std::vector &mask) const; #pragma endregion #pragma region Column Modification template void add_column(const std::string &name, const std::vector &shape, const std::vector &column_data) { // Check if the type T is a bool. If so, convert to BoolEnum and add column. if constexpr (std::is_same_v) { std::vector converted(column_data.size()); for (size_t i = 0; i < column_data.size(); ++i) { converted[i] = column_data[i] ? h5dispatch::BoolEnum::TRUE : h5dispatch::BoolEnum::FALSE; } add_column(name, shape, converted); } else { static_assert(h5dispatch::is_column_type_v, "Unsupported column type; see h5dispatch::ColumnTypes"); TypedColumn col(name, shape, column_data); // Check for duplicate column names for (const auto &existing_col : data) { if (column_name(existing_col) == name) { throw std::runtime_error("Column with name already exists: " + name); } } // Ensure row count consistency if (!data.empty() && col.rows() != get_row_count()) { throw std::runtime_error("Row count mismatch when adding column: " + name); } // Add the new column to the table data.emplace_back(std::move(col)); } } template void add_column(const std::string &name, const std::vector &column_data) { add_column(name, std::vector{column_data.size()}, column_data); } template void add_column(const std::string &name, const size_t rows, const size_t cols, const std::vector &column_data) { add_column(name, std::vector{rows, cols}, column_data); } template void update_column(const std::string &name, const std::vector &shape, const std::vector &column_data) { // Check if the type T is a bool. If so, convert to BoolEnum and update. if constexpr (std::is_same_v) { std::vector converted(column_data.size()); for (size_t i = 0; i < column_data.size(); ++i) { converted[i] = column_data[i] ? h5dispatch::BoolEnum::TRUE : h5dispatch::BoolEnum::FALSE; } update_column(name, shape, converted); } else { static_assert(h5dispatch::is_column_type_v, "Unsupported column type; see h5dispatch::ColumnTypes"); // Locate the existing column (strict: it must already exist) auto it = std::find_if(data.begin(), data.end(), [&](const Column &c) { return column_name(c) == name; }); if (it == data.end()) { throw std::runtime_error("Column not found for update: " + name); } TypedColumn col(name, shape, column_data); // Ensure row count consistency with the other columns in the table for (const auto &other : data) { if (column_name(other) != name) { if (col.rows() != column_rows(other)) { throw std::runtime_error( "Row count mismatch when updating column: " + name); } break; } } // Replace in place, preserving the column's position *it = std::move(col); } } template void update_column(const std::string &name, const std::vector &column_data) { update_column(name, std::vector{column_data.size()}, column_data); } template void update_column(const std::string &name, const size_t rows, const size_t cols, const std::vector &column_data) { update_column(name, std::vector{rows, cols}, column_data); } #pragma endregion #pragma region Write void write(std::string_view filename, std::string_view group = "/dials/processing/group_0") const; #pragma endregion }; #pragma endregion