Skip to content

Commit

Permalink
fix (de)serialization for general filtration values
Browse files Browse the repository at this point in the history
  • Loading branch information
hschreiber committed Oct 29, 2024
1 parent 95210dc commit af820c5
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 26 deletions.
30 changes: 30 additions & 0 deletions src/Simplex_tree/concept/FiltrationValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
49 changes: 36 additions & 13 deletions src/Simplex_tree/include/gudhi/Simplex_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_; }

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
*
Expand All @@ -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
Expand Down Expand Up @@ -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<Vertex_handle>(sib->members().size()), ptr);
ptr = serialize_trivial(static_cast<Vertex_handle>(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
Expand All @@ -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<Vertex_handle>(0), ptr);
ptr = serialize_trivial(static_cast<Vertex_handle>(0), ptr);
#ifdef DEBUG_TRACES
std::cout << "\n0 : ";
#endif // DEBUG_TRACES
Expand All @@ -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<std::size_t>(ptr - buffer) != buffer_size) {
throw std::invalid_argument("Deserialization does not match end of buffer");
Expand All @@ -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.
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#define SIMPLEX_TREE_SERIALIZATION_UTILS_H_

#include <cstring> // for memcpy and std::size_t
#include <iostream>

namespace Gudhi {

Expand Down Expand Up @@ -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<class ArgumentType>
constexpr std::size_t get_serialization_size_of([[maybe_unused]] ArgumentType value) {
return sizeof(ArgumentType);
}

} // namespace simplex_tree

} // namespace Gudhi
Expand Down
68 changes: 56 additions & 12 deletions src/Simplex_tree/test/simplex_tree_serialization_unit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <cstring> // for std::size_t and strncmp
#include <random>
#include <iterator> // for std::distance
#include <type_traits>
#include <vector>
#include <cstdint> // for std::uint8_t
#include <iomanip> // for std::setfill, setw
Expand All @@ -26,6 +27,8 @@
#include <gudhi/Simplex_tree/serialization_utils.h> // for de/serialize_trivial
#include <gudhi/Unitary_tests_utils.h> // for GUDHI_TEST_FLOAT_EQUALITY_CHECK

#include "test_vector_filtration_simplex_tree.h"

using namespace Gudhi;
using namespace Gudhi::simplex_tree;

Expand All @@ -49,17 +52,57 @@ typedef boost::mpl::list<Simplex_tree<>,
Simplex_tree<Simplex_tree_options_fast_persistence>,
Simplex_tree<Low_options>,
Simplex_tree<Simplex_tree_options_full_featured>,
Simplex_tree<Stable_options> > list_of_tested_variants;

template<class Filtration_type>
Filtration_type random_filtration(Filtration_type lower_bound = 0, Filtration_type upper_bound = 1) {
Simplex_tree<Stable_options>,
Simplex_tree<Simplex_tree_options_custom_fil_values_default>,
Simplex_tree<Simplex_tree_options_custom_fil_values_fast_persistence>,
Simplex_tree<Simplex_tree_options_custom_fil_values_full_featured> > list_of_tested_variants;

template <class Filtration_type>
Filtration_type random_filtration_ar(Filtration_type lower_bound = 0,
Filtration_type upper_bound = 1)
{
std::uniform_real_distribution<Filtration_type> unif(lower_bound, upper_bound);
std::random_device rand_dev;
std::mt19937 rand_engine(rand_dev());

return unif(rand_engine);
}

template <class Filtration_type>
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<typename Filtration_type::value_type> 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 <class Filtration_type>
Filtration_type random_filtration()
{
if constexpr (std::is_arithmetic_v<Filtration_type>) {
return random_filtration_ar<Filtration_type>();
} else {
return random_filtration_vec<Filtration_type>();
}
}

template <class Filtration_type>
void test_equality(const Filtration_type& filt1, const Filtration_type& filt2)
{
if constexpr (std::is_arithmetic_v<Filtration_type>) {
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;
Expand Down Expand Up @@ -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<Filtration_type>()) : 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
Expand All @@ -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);
Expand All @@ -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);
Expand Down
41 changes: 41 additions & 0 deletions src/Simplex_tree/test/test_vector_filtration_simplex_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#ifndef SIMPLEX_TREE_TEST_CUSTOM_SIMPLEX_TREE_H_
#define SIMPLEX_TREE_TEST_CUSTOM_SIMPLEX_TREE_H_

#include <cstddef>
#include <stdexcept>
#include <vector>
#include <limits>
Expand All @@ -28,6 +29,7 @@ class Vector_filtration_value : public std::vector<int>
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<int> init) : Base(init) {}
Vector_filtration_value(const_iterator start, const_iterator end) : Base(start, end) {}

Expand Down Expand Up @@ -66,6 +68,45 @@ class Vector_filtration_value : public std::vector<int>
}
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 {
Expand Down

0 comments on commit af820c5

Please sign in to comment.