From af820c5e4211bb2f96cfc860236b4fb2929f9d88 Mon Sep 17 00:00:00 2001 From: hschreiber Date: Tue, 29 Oct 2024 13:38:02 +0100 Subject: [PATCH] fix (de)serialization for general filtration values --- src/Simplex_tree/concept/FiltrationValue.h | 30 ++++++++ src/Simplex_tree/include/gudhi/Simplex_tree.h | 49 +++++++++---- .../gudhi/Simplex_tree/serialization_utils.h | 9 ++- .../simplex_tree_serialization_unit_test.cpp | 68 +++++++++++++++---- .../test_vector_filtration_simplex_tree.h | 41 +++++++++++ 5 files changed, 171 insertions(+), 26 deletions(-) diff --git a/src/Simplex_tree/concept/FiltrationValue.h b/src/Simplex_tree/concept/FiltrationValue.h index 2b1cc03b22..7764cf3192 100644 --- a/src/Simplex_tree/concept/FiltrationValue.h +++ b/src/Simplex_tree/concept/FiltrationValue.h @@ -68,4 +68,34 @@ struct FiltrationValue { * @return True if and only if the values in @p f1 were actually modified. */ friend bool intersect_lifetimes(FiltrationValue& f1, const FiltrationValue& f2); + + /** + * @brief Only necessary when serializing the simplex tree. Serialize the given value and insert it at start position. + * Overloads for native arithmetic types or other simple types are already implemented with + * @ref Gudhi::simplex_tree::serialize_trivial "". + * + * @param value The value to serialize. + * @param start Start position where the value is serialized. + * @return The new position in the array of char for the next serialization. + */ + friend char* serialize_trivial(const FiltrationValue& value, char* start); + + /** + * @brief Only necessary when deserializing the simplex tree. Deserialize at the start position in an array of char + * and sets the value with it. + * Overloads for native arithmetic types or other simple types are already implemented with + * @ref Gudhi::simplex_tree::deserialize_trivial "". + * + * @param value The value where to deserialize based on its type. + * @param start Start position where the value is serialized. + * @return The new position in the array of char for the next deserialization. + */ + friend const char* deserialize_trivial(FiltrationValue& value, const char* start); + + /** + * @brief Only necessary when (de)serializing the simplex tree. Returns the serialization size of the given object. + * Overloads for native arithmetic types or other simple types are already implemented with + * @ref Gudhi::simplex_tree::get_serialization_size_of "". + */ + friend std::size_t get_serialization_size_of(const FiltrationValue& value); }; diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree.h b/src/Simplex_tree/include/gudhi/Simplex_tree.h index b5235531e0..704714fc5e 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree.h @@ -176,11 +176,12 @@ class Simplex_tree { struct Filtration_simplex_base_dummy { Filtration_simplex_base_dummy() {} Filtration_simplex_base_dummy(Filtration_value GUDHI_CHECK_code(f)) { - GUDHI_CHECK(f == 0, "filtration value specified for a complex that does not store them"); + GUDHI_CHECK(f == Filtration_value(), + "filtration value specified in the constructor for a complex that does not store them"); } void assign_filtration(const Filtration_value& GUDHI_CHECK_code(f)) { - GUDHI_CHECK(f == Filtration_value(), "filtration value specified for a complex that does not store them"); + GUDHI_CHECK(f == Filtration_value(), "filtration value assigned for a complex that does not store them"); } const Filtration_value& filtration() const { return null_; } @@ -2290,7 +2291,7 @@ class Simplex_tree { * retrieves the original values and outputs the extended simplex type. * * @warning Currently only works for @ref SimplexTreeOptions::Filtration_value which are - * float types like `float` or `double`, or **signed** integers. + * float types like `float` or `double`. * * @exception std::invalid_argument In debug mode if the Simplex tree contains a vertex with the largest * Vertex_handle, as this method requires to create an extra vertex internally. @@ -2550,6 +2551,22 @@ class Simplex_tree { } } + std::size_t num_simplices_and_filtration_size(Siblings* sib, std::size_t& fv_byte_size) const { + using namespace Gudhi::simplex_tree; + + auto sib_begin = sib->members().begin(); + auto sib_end = sib->members().end(); + size_t simplices_number = sib->members().size(); + for (auto sh = sib_begin; sh != sib_end; ++sh) { + if constexpr (SimplexTreeOptions::store_filtration) + fv_byte_size += get_serialization_size_of(sh->second.filtration()); + if (has_children(sh)) { + simplices_number += num_simplices_and_filtration_size(sh->second.children(), fv_byte_size); + } + } + return simplices_number; + } + public: /** @private @brief Returns the serialization required buffer size. * @@ -2559,9 +2576,12 @@ class Simplex_tree { * architecture. */ std::size_t get_serialization_size() { + using namespace Gudhi::simplex_tree; + const std::size_t vh_byte_size = sizeof(Vertex_handle); - const std::size_t fv_byte_size = SimplexTreeOptions::store_filtration ? sizeof(Filtration_value) : 0; - const std::size_t buffer_byte_size = vh_byte_size + num_simplices() * (fv_byte_size + 2 * vh_byte_size); + std::size_t fv_byte_size = 0; + const std::size_t tree_size = num_simplices_and_filtration_size(&root_, fv_byte_size); + const std::size_t buffer_byte_size = vh_byte_size + fv_byte_size + tree_size * 2 * vh_byte_size; #ifdef DEBUG_TRACES std::clog << "Gudhi::simplex_tree::get_serialization_size - buffer size = " << buffer_byte_size << std::endl; #endif // DEBUG_TRACES @@ -2606,15 +2626,16 @@ class Simplex_tree { private: /** \brief Serialize each element of the sibling and recursively call serialization. */ char* rec_serialize(Siblings *sib, char* buffer) { + using namespace Gudhi::simplex_tree; char* ptr = buffer; - ptr = Gudhi::simplex_tree::serialize_trivial(static_cast(sib->members().size()), ptr); + ptr = serialize_trivial(static_cast(sib->members().size()), ptr); #ifdef DEBUG_TRACES std::clog << "\n" << sib->members().size() << " : "; #endif // DEBUG_TRACES for (auto& map_el : sib->members()) { - ptr = Gudhi::simplex_tree::serialize_trivial(map_el.first, ptr); // Vertex + ptr = serialize_trivial(map_el.first, ptr); // Vertex if (Options::store_filtration) - ptr = Gudhi::simplex_tree::serialize_trivial(map_el.second.filtration(), ptr); // Filtration + ptr = serialize_trivial(map_el.second.filtration(), ptr); // Filtration #ifdef DEBUG_TRACES std::clog << " [ " << map_el.first << " | " << map_el.second.filtration() << " ] "; #endif // DEBUG_TRACES @@ -2623,7 +2644,7 @@ class Simplex_tree { if (has_children(&map_el)) { ptr = rec_serialize(map_el.second.children(), ptr); } else { - ptr = Gudhi::simplex_tree::serialize_trivial(static_cast(0), ptr); + ptr = serialize_trivial(static_cast(0), ptr); #ifdef DEBUG_TRACES std::cout << "\n0 : "; #endif // DEBUG_TRACES @@ -2647,11 +2668,12 @@ class Simplex_tree { * */ void deserialize(const char* buffer, const std::size_t buffer_size) { + using namespace Gudhi::simplex_tree; GUDHI_CHECK(num_vertices() == 0, std::logic_error("Simplex_tree::deserialize - Simplex_tree must be empty")); const char* ptr = buffer; // Needs to read size before recursivity to manage new siblings for children Vertex_handle members_size; - ptr = Gudhi::simplex_tree::deserialize_trivial(members_size, ptr); + ptr = deserialize_trivial(members_size, ptr); ptr = rec_deserialize(&root_, members_size, ptr, 0); if (static_cast(ptr - buffer) != buffer_size) { throw std::invalid_argument("Deserialization does not match end of buffer"); @@ -2661,15 +2683,16 @@ class Simplex_tree { private: /** \brief Serialize each element of the sibling and recursively call serialization. */ const char* rec_deserialize(Siblings *sib, Vertex_handle members_size, const char* ptr, int dim) { + using namespace Gudhi::simplex_tree; // In case buffer is just a 0 char if (members_size > 0) { if constexpr (!Options::stable_simplex_handles) sib->members_.reserve(members_size); Vertex_handle vertex; Filtration_value filtration(0); for (Vertex_handle idx = 0; idx < members_size; idx++) { - ptr = Gudhi::simplex_tree::deserialize_trivial(vertex, ptr); + ptr = deserialize_trivial(vertex, ptr); if (Options::store_filtration) { - ptr = Gudhi::simplex_tree::deserialize_trivial(filtration, ptr); + ptr = deserialize_trivial(filtration, ptr); } // Default is no children // If store_filtration is false, `filtration` is ignored. @@ -2678,7 +2701,7 @@ class Simplex_tree { Vertex_handle child_size; for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { update_simplex_tree_after_node_insertion(sh); - ptr = Gudhi::simplex_tree::deserialize_trivial(child_size, ptr); + ptr = deserialize_trivial(child_size, ptr); if (child_size > 0) { Siblings* child = new Siblings(sib, sh->first); sh->second.assign_children(child); diff --git a/src/Simplex_tree/include/gudhi/Simplex_tree/serialization_utils.h b/src/Simplex_tree/include/gudhi/Simplex_tree/serialization_utils.h index c0bb045c45..bff7d87eee 100644 --- a/src/Simplex_tree/include/gudhi/Simplex_tree/serialization_utils.h +++ b/src/Simplex_tree/include/gudhi/Simplex_tree/serialization_utils.h @@ -12,7 +12,6 @@ #define SIMPLEX_TREE_SERIALIZATION_UTILS_H_ #include // for memcpy and std::size_t -#include namespace Gudhi { @@ -48,6 +47,14 @@ const char* deserialize_trivial(ArgumentType& value, const char* start) { return (start + arg_size); } +/** + * @brief Returns the size of the serialization of the given object. + */ +template +constexpr std::size_t get_serialization_size_of([[maybe_unused]] ArgumentType value) { + return sizeof(ArgumentType); +} + } // namespace simplex_tree } // namespace Gudhi diff --git a/src/Simplex_tree/test/simplex_tree_serialization_unit_test.cpp b/src/Simplex_tree/test/simplex_tree_serialization_unit_test.cpp index a8450cfa72..d64fb06bbe 100644 --- a/src/Simplex_tree/test/simplex_tree_serialization_unit_test.cpp +++ b/src/Simplex_tree/test/simplex_tree_serialization_unit_test.cpp @@ -12,6 +12,7 @@ #include // for std::size_t and strncmp #include #include // for std::distance +#include #include #include // for std::uint8_t #include // for std::setfill, setw @@ -26,6 +27,8 @@ #include // for de/serialize_trivial #include // for GUDHI_TEST_FLOAT_EQUALITY_CHECK +#include "test_vector_filtration_simplex_tree.h" + using namespace Gudhi; using namespace Gudhi::simplex_tree; @@ -49,17 +52,57 @@ typedef boost::mpl::list, Simplex_tree, Simplex_tree, Simplex_tree, - Simplex_tree > list_of_tested_variants; - -template -Filtration_type random_filtration(Filtration_type lower_bound = 0, Filtration_type upper_bound = 1) { + Simplex_tree, + Simplex_tree, + Simplex_tree, + Simplex_tree > list_of_tested_variants; + +template +Filtration_type random_filtration_ar(Filtration_type lower_bound = 0, + Filtration_type upper_bound = 1) +{ std::uniform_real_distribution unif(lower_bound, upper_bound); std::random_device rand_dev; std::mt19937 rand_engine(rand_dev()); - + return unif(rand_engine); } +template +Filtration_type random_filtration_vec(typename Filtration_type::value_type lower_bound = 0, + typename Filtration_type::value_type upper_bound = 10, + unsigned int number_of_parameters = 2) +{ + std::uniform_int_distribution unif(lower_bound, upper_bound); + std::random_device rand_dev; + std::mt19937 rand_engine(rand_dev()); + + Filtration_type res(number_of_parameters); + for (unsigned int i = 0; i < number_of_parameters; ++i) res[i] = unif(rand_engine); + + return res; +} + +template +Filtration_type random_filtration() +{ + if constexpr (std::is_arithmetic_v) { + return random_filtration_ar(); + } else { + return random_filtration_vec(); + } +} + +template +void test_equality(const Filtration_type& filt1, const Filtration_type& filt2) +{ + if constexpr (std::is_arithmetic_v) { + GUDHI_TEST_FLOAT_EQUALITY_CHECK(filt1, filt2); + } else { + BOOST_CHECK(filt1 == filt2); + } +} + BOOST_AUTO_TEST_CASE_TEMPLATE(basic_simplex_tree_serialization, Stree, list_of_tested_variants) { std::clog << "********************************************************************" << std::endl; std::clog << "BASIC SIMPLEX TREE SERIALIZATION/DESERIALIZATION" << std::endl; @@ -111,12 +154,13 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(basic_simplex_tree_serialization, Stree, list_of_t std::clog << "Serialization size in bytes = " << buffer_size << std::endl; // Sizes are expressed in bytes const std::size_t vertex_size = sizeof(Vertex_type); - const std::size_t filtration_size = Stree::Options::store_filtration ? sizeof(Filtration_type) : 0; + const std::size_t filtration_size = + Stree::Options::store_filtration ? get_serialization_size_of(random_filtration()) : 0; const std::size_t serialization_size = vertex_size + st.num_simplices() * (2 * vertex_size + filtration_size); - BOOST_CHECK(serialization_size == buffer_size); + BOOST_CHECK_EQUAL(serialization_size, buffer_size); Vertex_type vertex = 0; - Filtration_type filtration = 0; + Filtration_type filtration(0); // Reset position pointer at start const char* c_ptr = buffer; // 3 simplices ({0}, {1}, {2}) and its filtration values @@ -126,19 +170,19 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(basic_simplex_tree_serialization, Stree, list_of_t BOOST_CHECK(vertex == 0); if (Stree::Options::store_filtration) { c_ptr = deserialize_trivial(filtration, c_ptr); - GUDHI_TEST_FLOAT_EQUALITY_CHECK(filtration, st.filtration(st.find({0}))); + test_equality(filtration, st.filtration(st.find({0}))); } c_ptr = deserialize_trivial(vertex, c_ptr); BOOST_CHECK(vertex == 1); if (Stree::Options::store_filtration) { c_ptr = deserialize_trivial(filtration, c_ptr); - GUDHI_TEST_FLOAT_EQUALITY_CHECK(filtration, st.filtration(st.find({1}))); + test_equality(filtration, st.filtration(st.find({1}))); } c_ptr = deserialize_trivial(vertex, c_ptr); BOOST_CHECK(vertex == 2); if (Stree::Options::store_filtration) { c_ptr = deserialize_trivial(filtration, c_ptr); - GUDHI_TEST_FLOAT_EQUALITY_CHECK(filtration, st.filtration(st.find({2}))); + test_equality(filtration, st.filtration(st.find({2}))); } // 1 simplex (2) from {0, 2} and its filtration values c_ptr = deserialize_trivial(vertex, c_ptr); @@ -147,7 +191,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(basic_simplex_tree_serialization, Stree, list_of_t BOOST_CHECK(vertex == 2); if (Stree::Options::store_filtration) { c_ptr = deserialize_trivial(filtration, c_ptr); - GUDHI_TEST_FLOAT_EQUALITY_CHECK(filtration, st.filtration(st.find({0, 2}))); + test_equality(filtration, st.filtration(st.find({0, 2}))); } c_ptr = deserialize_trivial(vertex, c_ptr); // (0, 2) end of leaf BOOST_CHECK(vertex == 0); diff --git a/src/Simplex_tree/test/test_vector_filtration_simplex_tree.h b/src/Simplex_tree/test/test_vector_filtration_simplex_tree.h index 1bfc4cafef..c826cce49f 100644 --- a/src/Simplex_tree/test/test_vector_filtration_simplex_tree.h +++ b/src/Simplex_tree/test/test_vector_filtration_simplex_tree.h @@ -11,6 +11,7 @@ #ifndef SIMPLEX_TREE_TEST_CUSTOM_SIMPLEX_TREE_H_ #define SIMPLEX_TREE_TEST_CUSTOM_SIMPLEX_TREE_H_ +#include #include #include #include @@ -28,6 +29,7 @@ class Vector_filtration_value : public std::vector using const_iterator = Base::const_iterator; Vector_filtration_value() : Base() {} + Vector_filtration_value(std::size_t count) : Base(count) {} Vector_filtration_value(std::initializer_list init) : Base(init) {} Vector_filtration_value(const_iterator start, const_iterator end) : Base(start, end) {} @@ -66,6 +68,45 @@ class Vector_filtration_value : public std::vector } return false; } + + friend char* serialize_trivial(const Vector_filtration_value& value, char* start) + { + const auto length = value.size(); + const std::size_t arg_size = sizeof(int) * length; + const std::size_t type_size = sizeof(Vector_filtration_value::size_type); + memcpy(start, &length, type_size); + memcpy(start + type_size, value.data(), arg_size); + return start + arg_size + type_size; + } + + friend const char* deserialize_trivial(Vector_filtration_value& value, const char* start) + { + const std::size_t type_size = sizeof(Vector_filtration_value::size_type); + Vector_filtration_value::size_type length; + memcpy(&length, start, type_size); + std::size_t arg_size = sizeof(int) * length; + value.resize(length); + memcpy(value.data(), start + type_size, arg_size); + return start + arg_size + type_size; + } + + friend std::size_t get_serialization_size_of(const Vector_filtration_value& value) { + return sizeof(Vector_filtration_value::size_type) + sizeof(int) * value.size(); + } + + friend std::ostream &operator<<(std::ostream &stream, const Vector_filtration_value &f) { + if (f.empty()) { + stream << "[]"; + return stream; + } + stream << "["; + for (std::size_t i = 0; i < f.size() - 1; i++) { + stream << f[i] << ", "; + } + stream << f.back(); + stream << "]"; + return stream; + } }; struct Simplex_tree_options_custom_fil_values_default {